From b39b551a53604398ba37b861bee2498c1fa4ca26 Mon Sep 17 00:00:00 2001 From: David Salvisberg Date: Thu, 6 Jun 2024 11:46:29 +0200 Subject: [PATCH 1/6] Fix incorrect truthyness for Enum types and literals --- mypy/typeops.py | 8 ++++++++ mypy/types.py | 32 ++++++++++++++++++++++++++++++++ test-data/unit/check-enum.test | 25 +++++++++++++++++++++++++ test-data/unit/lib-stub/enum.pyi | 2 ++ 4 files changed, 67 insertions(+) diff --git a/mypy/typeops.py b/mypy/typeops.py index a59bd3739562..ce5d1d0abae9 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -572,8 +572,16 @@ def _remove_redundant_union_items(items: list[Type], keep_erased: bool) -> list[ def _get_type_method_ret_type(t: Type, *, name: str) -> Type | None: t = get_proper_type(t) + # For Enum literals the ret_type can change based on the Enum + # we need to check the type of the enum rather than the literal + if isinstance(t, LiteralType) and t.is_enum_literal(): + t = t.fallback + if isinstance(t, Instance): sym = t.type.get(name) + # Fallback to the metaclass for the lookup when necessary + if not sym and (m := t.type.metaclass_type): + sym = m.type.get(name) if sym: sym_type = get_proper_type(sym.type) if isinstance(sym_type, CallableType): diff --git a/mypy/types.py b/mypy/types.py index cdcb26f435b8..0fae24c35ef5 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -2754,6 +2754,38 @@ def can_be_false_default(self) -> bool: def can_be_true_default(self) -> bool: return bool(self.value) + # TODO: Check whether it's faster to override this property + # so `true_only`/`false_only` have to determine the + # method return type less frequently, or to override + # the two methods above instead and just take the hit + # of the static truthyness being recalculated more + # often than necessary + @property + def can_be_true(self) -> bool: + if self.fallback.type.is_enum: + return self.fallback.can_be_true + return super().can_be_true + + @can_be_true.setter + def can_be_true(self, v: bool) -> None: + if self.fallback.type.is_enum: + self.fallback.can_be_true = v + else: + self._can_be_true = v + + @property + def can_be_false(self) -> bool: + if self.fallback.type.is_enum: + return self.fallback.can_be_false + return super().can_be_false + + @can_be_false.setter + def can_be_false(self, v: bool) -> None: + if self.fallback.type.is_enum: + self.fallback.can_be_false = v + else: + self._can_be_false = v + def accept(self, visitor: TypeVisitor[T]) -> T: return visitor.visit_literal_type(self) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index e8e65f464eaf..fc0fddb3e84f 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -175,6 +175,31 @@ def infer_truth(truth: Truth) -> None: reveal_type(truth.value) # N: Revealed type is "builtins.bool" [builtins fixtures/bool.pyi] +[case testEnumTruthyness] +# mypy: warn-unreachable +import enum +class E(enum.Enum): + x = 0 +if not E.x: + "noop" +[builtins fixtures/tuple.pyi] +[out] +main:6: error: Statement is unreachable + +[case testEnumTruthynessCustomDunderBool] +# mypy: warn-unreachable +import enum +from typing_extensions import Literal +class E(enum.Enum): + x = 0 + def __bool__(self) -> Literal[False]: + return False +if E.x: + "noop" +[builtins fixtures/tuple.pyi] +[out] +main:9: error: Statement is unreachable + [case testEnumUnique] import enum @enum.unique diff --git a/test-data/unit/lib-stub/enum.pyi b/test-data/unit/lib-stub/enum.pyi index 11adfc597955..4e609208b144 100644 --- a/test-data/unit/lib-stub/enum.pyi +++ b/test-data/unit/lib-stub/enum.pyi @@ -1,4 +1,5 @@ from typing import Any, TypeVar, Union, Type, Sized, Iterator +from typing_extensions import Literal _T = TypeVar('_T') @@ -7,6 +8,7 @@ class EnumMeta(type, Sized): def __iter__(self: Type[_T]) -> Iterator[_T]: pass def __reversed__(self: Type[_T]) -> Iterator[_T]: pass def __getitem__(self: Type[_T], name: str) -> _T: pass + def __bool__(self) -> Literal[True]: pass class Enum(metaclass=EnumMeta): def __new__(cls: Type[_T], value: object) -> _T: pass From 03794f7f5fff40eb9b245cfe1635037b43cc950b Mon Sep 17 00:00:00 2001 From: David Salvisberg Date: Thu, 6 Jun 2024 13:16:46 +0200 Subject: [PATCH 2/6] Fixes broken tests --- test-data/unit/check-custom-plugin.test | 1 + test-data/unit/check-enum.test | 47 ++++++++++++++++++++++-- test-data/unit/check-incremental.test | 1 + test-data/unit/check-newsemanal.test | 1 + test-data/unit/deps-classes.test | 1 + test-data/unit/deps-types.test | 1 + test-data/unit/diff.test | 2 + test-data/unit/fine-grained.test | 8 ++++ test-data/unit/fixtures/staticmethod.pyi | 1 + test-data/unit/merge.test | 1 + 10 files changed, 61 insertions(+), 3 deletions(-) diff --git a/test-data/unit/check-custom-plugin.test b/test-data/unit/check-custom-plugin.test index 63529cf165ce..a5b7f8c1db36 100644 --- a/test-data/unit/check-custom-plugin.test +++ b/test-data/unit/check-custom-plugin.test @@ -964,6 +964,7 @@ class Cls(enum.Enum): attr = 'test' reveal_type(Cls.attr) # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] [file mypy.ini] \[mypy] plugins=/test-data/unit/plugins/class_attr_hook.py diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index fc0fddb3e84f..11ea6ed6bff9 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -11,6 +11,8 @@ m = Medal.gold if int(): m = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Medal") +[builtins fixtures/tuple.pyi] + -- Creation from Enum call -- ----------------------- @@ -79,6 +81,7 @@ from typing import Generic, TypeVar T = TypeVar("T") class Medal(Generic[T], metaclass=EnumMeta): # E: Enum class cannot be generic q = None +[builtins fixtures/tuple.pyi] [case testEnumNameAndValue] from enum import Enum @@ -208,6 +211,7 @@ class E(enum.Enum): y = 1 # NOTE: This duplicate value is not detected by mypy at the moment x = 1 x = E.x +[builtins fixtures/tuple.pyi] [out] main:7: error: Incompatible types in assignment (expression has type "E", variable has type "int") @@ -222,6 +226,7 @@ if int(): s = '' if int(): s = N.y # E: Incompatible types in assignment (expression has type "N", variable has type "str") +[builtins fixtures/tuple.pyi] [case testIntEnum_functionTakingIntEnum] from enum import IntEnum @@ -232,6 +237,7 @@ def takes_some_int_enum(n: SomeIntEnum): takes_some_int_enum(SomeIntEnum.x) takes_some_int_enum(1) # Error takes_some_int_enum(SomeIntEnum(1)) # How to deal with the above +[builtins fixtures/tuple.pyi] [out] main:7: error: Argument 1 to "takes_some_int_enum" has incompatible type "int"; expected "SomeIntEnum" @@ -243,6 +249,7 @@ def takes_int(i: int): pass takes_int(SomeIntEnum.x) takes_int(2) +[builtins fixtures/tuple.pyi] [case testIntEnum_functionReturningIntEnum] from enum import IntEnum @@ -255,6 +262,7 @@ an_int = returns_some_int_enum() an_enum = SomeIntEnum.x an_enum = returns_some_int_enum() +[builtins fixtures/tuple.pyi] [out] [case testStrEnumCreation] @@ -269,6 +277,7 @@ reveal_type(MyStrEnum.x) # N: Revealed type is "Literal[__main__.MyStrEnum.x]?" reveal_type(MyStrEnum.x.value) # N: Revealed type is "Literal['x']?" reveal_type(MyStrEnum.y) # N: Revealed type is "Literal[__main__.MyStrEnum.y]?" reveal_type(MyStrEnum.y.value) # N: Revealed type is "Literal['y']?" +[builtins fixtures/tuple.pyi] [out] [case testEnumMethods] @@ -303,6 +312,7 @@ takes_int(SomeExtIntEnum.x) def takes_some_ext_int_enum(s: SomeExtIntEnum): pass takes_some_ext_int_enum(SomeExtIntEnum.x) +[builtins fixtures/tuple.pyi] [case testNamedTupleEnum] from typing import NamedTuple @@ -324,6 +334,7 @@ class E(IntEnum): a = 1 x: int reveal_type(E(x)) +[builtins fixtures/tuple.pyi] [out] main:5: note: Revealed type is "__main__.E" @@ -333,6 +344,7 @@ class E(IntEnum): a = 1 s: str reveal_type(E[s]) +[builtins fixtures/tuple.pyi] [out] main:5: note: Revealed type is "__main__.E" @@ -342,6 +354,7 @@ class E(IntEnum): a = 1 E[1] # E: Enum index should be a string (actual index type "int") x = E[1] # E: Enum index should be a string (actual index type "int") +[builtins fixtures/tuple.pyi] [case testEnumIndexIsNotAnAlias] from enum import Enum @@ -359,6 +372,7 @@ def get_member(name: str) -> E: return val reveal_type(get_member('a')) # N: Revealed type is "__main__.E" +[builtins fixtures/tuple.pyi] [case testGenericEnum] from enum import Enum @@ -371,6 +385,7 @@ class F(Generic[T], Enum): # E: Enum class cannot be generic y: T reveal_type(F[int].x) # N: Revealed type is "__main__.F[builtins.int]" +[builtins fixtures/tuple.pyi] [case testEnumFlag] from enum import Flag @@ -382,6 +397,7 @@ if int(): x = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "C") if int(): x = x | C.b +[builtins fixtures/tuple.pyi] [case testEnumIntFlag] from enum import IntFlag @@ -393,6 +409,7 @@ if int(): x = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "C") if int(): x = x | C.b +[builtins fixtures/tuple.pyi] [case testAnonymousEnum] from enum import Enum @@ -403,6 +420,7 @@ class A: self.x = E.a a = A() reveal_type(a.x) +[builtins fixtures/tuple.pyi] [out] main:8: note: Revealed type is "__main__.E@4" @@ -418,6 +436,7 @@ x = A.E.a y = B.E.a if int(): x = y # E: Incompatible types in assignment (expression has type "__main__.B.E", variable has type "__main__.A.E") +[builtins fixtures/tuple.pyi] [case testFunctionalEnumString] from enum import Enum, IntEnum @@ -427,6 +446,7 @@ reveal_type(E.foo) reveal_type(E.bar.value) reveal_type(I.bar) reveal_type(I.baz.value) +[builtins fixtures/tuple.pyi] [out] main:4: note: Revealed type is "Literal[__main__.E.foo]?" main:5: note: Revealed type is "Any" @@ -439,6 +459,7 @@ E = Enum('E', ('foo', 'bar')) F = IntEnum('F', ['bar', 'baz']) reveal_type(E.foo) reveal_type(F.baz) +[builtins fixtures/tuple.pyi] [out] main:4: note: Revealed type is "Literal[__main__.E.foo]?" main:5: note: Revealed type is "Literal[__main__.F.baz]?" @@ -451,6 +472,7 @@ reveal_type(E.foo) reveal_type(F.baz) reveal_type(E.foo.value) reveal_type(F.bar.name) +[builtins fixtures/tuple.pyi] [out] main:4: note: Revealed type is "Literal[__main__.E.foo]?" main:5: note: Revealed type is "Literal[__main__.F.baz]?" @@ -465,6 +487,7 @@ reveal_type(E.foo) reveal_type(F.baz) reveal_type(E.foo.value) reveal_type(F.bar.name) +[builtins fixtures/tuple.pyi] [out] main:4: note: Revealed type is "Literal[__main__.E.foo]?" main:5: note: Revealed type is "Literal[__main__.F.baz]?" @@ -480,6 +503,7 @@ fake_enum1 = Enum('fake_enum1', ['a', 'b']) fake_enum2 = Enum('fake_enum2', names=['a', 'b']) fake_enum3 = Enum(value='fake_enum3', names=['a', 'b']) fake_enum4 = Enum(value='fake_enum4', names=['a', 'b'] , module=__name__) +[builtins fixtures/tuple.pyi] [case testFunctionalEnumErrors] from enum import Enum, IntEnum @@ -509,6 +533,7 @@ X = Enum('Something', 'a b') # E: String argument 1 "Something" to enum.Enum(.. reveal_type(X.a) # N: Revealed type is "Literal[__main__.Something@23.a]?" X.asdf # E: "Type[Something@23]" has no attribute "asdf" +[builtins fixtures/tuple.pyi] [typing fixtures/typing-medium.pyi] [case testFunctionalEnumFlag] @@ -523,6 +548,7 @@ reveal_type(B.a.name) # N: Revealed type is "Literal['a']?" # TODO: The revealed type should be 'int' here reveal_type(A.x.value) # N: Revealed type is "Any" reveal_type(B.a.value) # N: Revealed type is "Any" +[builtins fixtures/tuple.pyi] [case testAnonymousFunctionalEnum] from enum import Enum @@ -532,6 +558,7 @@ class A: self.x = E.a a = A() reveal_type(a.x) +[builtins fixtures/tuple.pyi] [out] main:7: note: Revealed type is "__main__.A.E@4" @@ -545,6 +572,7 @@ x = A.E.a y = B.E.a if int(): x = y # E: Incompatible types in assignment (expression has type "__main__.B.E", variable has type "__main__.A.E") +[builtins fixtures/tuple.pyi] [case testFunctionalEnumProtocols] from enum import IntEnum @@ -562,6 +590,7 @@ a: E = E.x # type: ignore[used-before-def] class E(Enum): x = 1 y = 2 +[builtins fixtures/tuple.pyi] [out] [case testEnumWorkWithForward2] @@ -572,6 +601,7 @@ F = Enum('F', {'x': 1, 'y': 2}) def fn(x: F) -> None: pass fn(b) +[builtins fixtures/tuple.pyi] [out] [case testFunctionalEnum] @@ -589,6 +619,7 @@ Gu.a Gb.a Hu.a Hb.a +[builtins fixtures/tuple.pyi] [out] [case testEnumIncremental] @@ -601,6 +632,7 @@ class E(Enum): a = 1 b = 2 F = Enum('F', 'a b') +[builtins fixtures/tuple.pyi] [rechecked] [stale] [out1] @@ -759,6 +791,7 @@ class SomeEnum(Enum): from enum import Enum class SomeEnum(Enum): a = "foo" +[builtins fixtures/tuple.pyi] [out] main:2: note: Revealed type is "Literal[1]?" [out2] @@ -974,7 +1007,7 @@ x: Optional[Foo] if x: reveal_type(x) # N: Revealed type is "__main__.Foo" else: - reveal_type(x) # N: Revealed type is "Union[__main__.Foo, None]" + reveal_type(x) # N: Revealed type is "None" if x is not None: reveal_type(x) # N: Revealed type is "__main__.Foo" @@ -986,7 +1019,7 @@ if x is Foo.A: else: reveal_type(x) # N: Revealed type is "Union[Literal[__main__.Foo.B], Literal[__main__.Foo.C], None]" reveal_type(x) # N: Revealed type is "Union[__main__.Foo, None]" -[builtins fixtures/bool.pyi] +[builtins fixtures/tuple.pyi] [case testEnumReachabilityWithMultipleEnums] from enum import Enum @@ -1129,6 +1162,7 @@ class A: self.b = Enum("b", [("foo", "bar")]) # E: Enum type as attribute is not supported reveal_type(A().b) # N: Revealed type is "Any" +[builtins fixtures/tuple.pyi] [case testEnumReachabilityWithChaining] from enum import Enum @@ -1332,6 +1366,7 @@ class Foo(Enum): a = Foo.A reveal_type(a.value) # N: Revealed type is "Union[Literal[1]?, Literal[2]?]" reveal_type(a._value_) # N: Revealed type is "Union[Literal[1]?, Literal[2]?]" +[builtins fixtures/tuple.pyi] [case testNewSetsUnexpectedValueType] from enum import Enum @@ -1350,7 +1385,7 @@ class Foo(bytes, Enum): a = Foo.A reveal_type(a.value) # N: Revealed type is "Any" reveal_type(a._value_) # N: Revealed type is "Any" -[builtins fixtures/primitives.pyi] +[builtins fixtures/tuple.pyi] [typing fixtures/typing-medium.pyi] [case testValueTypeWithNewInParentClass] @@ -1423,6 +1458,7 @@ class E(IntEnum): A = N(0) reveal_type(E.A.value) # N: Revealed type is "__main__.N" +[builtins fixtures/tuple.pyi] [case testEnumFinalValues] @@ -1435,6 +1471,7 @@ class Medal(Enum): Medal.gold = 0 # E: Cannot assign to final attribute "gold" # Same value: Medal.silver = 2 # E: Cannot assign to final attribute "silver" +[builtins fixtures/tuple.pyi] [case testEnumFinalValuesCannotRedefineValueProp] @@ -1442,6 +1479,7 @@ from enum import Enum class Types(Enum): key = 0 value = 1 +[builtins fixtures/tuple.pyi] [case testEnumReusedKeys] @@ -2056,6 +2094,7 @@ class C(Enum): C._ignore_ # E: "Type[C]" has no attribute "_ignore_" [typing fixtures/typing-medium.pyi] +[builtins fixtures/tuple.pyi] [case testCanOverrideDunderAttributes] import typing @@ -2134,6 +2173,7 @@ class AllPartialList(Enum): def check(self) -> None: reveal_type(self.value) # N: Revealed type is "builtins.list[Any]" +[builtins fixtures/tuple.pyi] [case testEnumPrivateAttributeNotMember] from enum import Enum @@ -2145,6 +2185,7 @@ class MyEnum(Enum): # TODO: change the next line to use MyEnum._MyEnum__my_dict when mypy implements name mangling x: MyEnum = MyEnum.__my_dict # E: Incompatible types in assignment (expression has type "Dict[int, str]", variable has type "MyEnum") +[builtins fixtures/tuple.pyi] [case testEnumWithPrivateAttributeReachability] # flags: --warn-unreachable diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index ead896b8e458..de525ae0d8d9 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -6571,6 +6571,7 @@ class TheClass: for x in names }) self.enum_type = pyenum +[builtins fixtures/tuple.pyi] [out] [out2] tmp/a.py:3: note: Revealed type is "def (value: builtins.object) -> lib.TheClass.pyenum@6" diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 7cbed5637c3a..a7056678b5b9 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -2071,6 +2071,7 @@ from enum import Enum A = Enum('A', ['x', 'y']) A = Enum('A', ['z', 't']) # E: Name "A" already defined on line 3 +[builtins fixtures/tuple.pyi] [case testNewAnalyzerNewTypeRedefinition] from typing import NewType diff --git a/test-data/unit/deps-classes.test b/test-data/unit/deps-classes.test index ebe2e9caed02..858fbd72779a 100644 --- a/test-data/unit/deps-classes.test +++ b/test-data/unit/deps-classes.test @@ -178,6 +178,7 @@ def g() -> None: A.X [file m.py] class B: pass +[builtins fixtures/tuple.pyi] [out] -> m.g -> , m.A, m.f, m.g diff --git a/test-data/unit/deps-types.test b/test-data/unit/deps-types.test index def117fe04df..6992a5bdec00 100644 --- a/test-data/unit/deps-types.test +++ b/test-data/unit/deps-types.test @@ -905,6 +905,7 @@ def g() -> None: A.X [file mod.py] class B: pass +[builtins fixtures/tuple.pyi] [out] -> m.g -> , m.f, m.g diff --git a/test-data/unit/diff.test b/test-data/unit/diff.test index 9212d902e8b2..b23f4a39ae3c 100644 --- a/test-data/unit/diff.test +++ b/test-data/unit/diff.test @@ -566,6 +566,7 @@ A = Enum('A', 'x') B = Enum('B', 'y') C = IntEnum('C', 'x') D = IntEnum('D', 'x y') +[builtins fixtures/tuple.pyi] [out] __main__.B.x __main__.B.y @@ -605,6 +606,7 @@ class D(Enum): Y = 'b' class F(Enum): X = 0 +[builtins fixtures/tuple.pyi] [out] __main__.B.Y __main__.B.Z diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 9c379d8f60da..ec53afeeb34c 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -5327,6 +5327,7 @@ c: C c = C.X if int(): c = 1 +[builtins fixtures/tuple.pyi] [out] == == @@ -5358,6 +5359,7 @@ if int(): n = C.X if int(): n = c +[builtins fixtures/tuple.pyi] [out] == == @@ -5382,6 +5384,7 @@ from enum import Enum class C(Enum): X = 0 +[builtins fixtures/tuple.pyi] [typing fixtures/typing-medium.pyi] [out] == @@ -5404,6 +5407,7 @@ from enum import Enum class C(Enum): X = 0 Y = 1 +[builtins fixtures/tuple.pyi] [out] == a.py:4: error: Argument 1 to "f" has incompatible type "C"; expected "int" @@ -5428,6 +5432,7 @@ c: C c = C.X if int(): c = 1 +[builtins fixtures/tuple.pyi] [out] == == @@ -5457,6 +5462,7 @@ if int(): n: int n = C.X n = c +[builtins fixtures/tuple.pyi] [out] == == @@ -5478,6 +5484,7 @@ C = Enum('C', 'X Y') from enum import Enum C = Enum('C', 'X') +[builtins fixtures/tuple.pyi] [typing fixtures/typing-medium.pyi] [out] == @@ -5498,6 +5505,7 @@ class C: [file b.py.2] from enum import Enum C = Enum('C', [('X', 0), ('Y', 1)]) +[builtins fixtures/tuple.pyi] [out] == a.py:4: error: Argument 1 to "f" has incompatible type "C"; expected "int" diff --git a/test-data/unit/fixtures/staticmethod.pyi b/test-data/unit/fixtures/staticmethod.pyi index 8a87121b2a71..a0ca831c7527 100644 --- a/test-data/unit/fixtures/staticmethod.pyi +++ b/test-data/unit/fixtures/staticmethod.pyi @@ -19,3 +19,4 @@ class str: pass class bytes: pass class ellipsis: pass class dict: pass +class tuple: pass diff --git a/test-data/unit/merge.test b/test-data/unit/merge.test index 19b1839f86c0..d5bc28b5d02a 100644 --- a/test-data/unit/merge.test +++ b/test-data/unit/merge.test @@ -1490,6 +1490,7 @@ from enum import Enum class A(Enum): X = 0 Y = 1 +[builtins fixtures/tuple.pyi] [out] TypeInfo<0>( Name(target.A) From cda21cdbfeaa3e6e4f2f143ec9137fe6d6780ec0 Mon Sep 17 00:00:00 2001 From: David Salvisberg Date: Thu, 6 Jun 2024 13:37:40 +0200 Subject: [PATCH 3/6] Fixes the broken 3.10+ tests I missed --- test-data/unit/check-python310.test | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 5ecc69dc7c32..3faf27c1e8a3 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -1498,6 +1498,7 @@ def g(m: Medal) -> int: case Medal.bronze: reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.bronze]" return 2 +[builtins fixtures/tuple.pyi] [case testMatchLiteralPatternEnumCustomEquals-skip] from enum import Enum @@ -1515,6 +1516,7 @@ match m: reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.gold]" case _: reveal_type(m) # N: Revealed type is "__main__.Medal" +[builtins fixtures/tuple.pyi] [case testMatchNarrowUsingPatternGuardSpecialCase] def f(x: int | str) -> int: @@ -1592,6 +1594,7 @@ def union(x: str | bool) -> None: case True: return reveal_type(x) # N: Revealed type is "Union[builtins.str, Literal[False]]" +[builtins fixtures/tuple.pyi] [case testMatchAssertFalseToSilenceFalsePositives] class C: From 4a649d648b0a568e6897dbb22ee6b74fa5f41f58 Mon Sep 17 00:00:00 2001 From: David Salvisberg Date: Tue, 11 Jun 2024 17:52:22 +0200 Subject: [PATCH 4/6] Switches to branching in the `default` methods to match `TypeAliasType`. --- mypy/types.py | 48 +++++++++++++++++------------------------------- 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/mypy/types.py b/mypy/types.py index 0fae24c35ef5..bafaa374255f 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -2748,43 +2748,29 @@ def __init__( self.fallback = fallback self._hash = -1 # Cached hash value + # NOTE: Enum types are always truthy by default, but this can be changed + # in subclasses, so we need to get the truthyness from the Enum + # type rather than base it on the value (which is a non-empty + # string for enums, so always truthy) + # TODO: We should consider moving this branch to the `can_be_true` + # `can_be_false` properties instead, so the truthyness only + # needs to be determined once per set of Enum literals. + # However, the same can be said for `TypeAliasType` in some + # cases and we only set the default based on the type it is + # aliasing. So if we decide to change this, we may want to + # change that as well. perf_compare output was inconclusive + # but slightly favored this version, probably because we have + # almost no test cases where we would redundantly compute + # `can_be_false`/`can_be_true`. def can_be_false_default(self) -> bool: + if self.fallback.type.is_enum: + return self.fallback.can_be_false return not self.value def can_be_true_default(self) -> bool: - return bool(self.value) - - # TODO: Check whether it's faster to override this property - # so `true_only`/`false_only` have to determine the - # method return type less frequently, or to override - # the two methods above instead and just take the hit - # of the static truthyness being recalculated more - # often than necessary - @property - def can_be_true(self) -> bool: if self.fallback.type.is_enum: return self.fallback.can_be_true - return super().can_be_true - - @can_be_true.setter - def can_be_true(self, v: bool) -> None: - if self.fallback.type.is_enum: - self.fallback.can_be_true = v - else: - self._can_be_true = v - - @property - def can_be_false(self) -> bool: - if self.fallback.type.is_enum: - return self.fallback.can_be_false - return super().can_be_false - - @can_be_false.setter - def can_be_false(self, v: bool) -> None: - if self.fallback.type.is_enum: - self.fallback.can_be_false = v - else: - self._can_be_false = v + return bool(self.value) def accept(self, visitor: TypeVisitor[T]) -> T: return visitor.visit_literal_type(self) From c5d13c694babb644b4596cacac05baa3cc0be4f1 Mon Sep 17 00:00:00 2001 From: David Salvisberg Date: Thu, 13 Jun 2024 11:19:03 +0200 Subject: [PATCH 5/6] Adds more minimal enum builtin fixture and uses it where possible --- test-data/unit/check-custom-plugin.test | 2 +- test-data/unit/check-enum.test | 79 ++++++++++++------------- test-data/unit/check-python310.test | 4 +- test-data/unit/deps-classes.test | 2 +- test-data/unit/diff.test | 4 +- test-data/unit/fine-grained.test | 8 +-- test-data/unit/fixtures/enum.pyi | 16 +++++ test-data/unit/merge.test | 2 +- 8 files changed, 66 insertions(+), 51 deletions(-) create mode 100644 test-data/unit/fixtures/enum.pyi diff --git a/test-data/unit/check-custom-plugin.test b/test-data/unit/check-custom-plugin.test index a5b7f8c1db36..ac16064adc33 100644 --- a/test-data/unit/check-custom-plugin.test +++ b/test-data/unit/check-custom-plugin.test @@ -964,7 +964,7 @@ class Cls(enum.Enum): attr = 'test' reveal_type(Cls.attr) # N: Revealed type is "builtins.int" -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [file mypy.ini] \[mypy] plugins=/test-data/unit/plugins/class_attr_hook.py diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index 11ea6ed6bff9..d56a160c23ae 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -11,7 +11,7 @@ m = Medal.gold if int(): m = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Medal") -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] -- Creation from Enum call -- ----------------------- @@ -81,7 +81,7 @@ from typing import Generic, TypeVar T = TypeVar("T") class Medal(Generic[T], metaclass=EnumMeta): # E: Enum class cannot be generic q = None -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testEnumNameAndValue] from enum import Enum @@ -199,7 +199,7 @@ class E(enum.Enum): return False if E.x: "noop" -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] main:9: error: Statement is unreachable @@ -211,7 +211,7 @@ class E(enum.Enum): y = 1 # NOTE: This duplicate value is not detected by mypy at the moment x = 1 x = E.x -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] main:7: error: Incompatible types in assignment (expression has type "E", variable has type "int") @@ -226,7 +226,7 @@ if int(): s = '' if int(): s = N.y # E: Incompatible types in assignment (expression has type "N", variable has type "str") -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testIntEnum_functionTakingIntEnum] from enum import IntEnum @@ -237,7 +237,7 @@ def takes_some_int_enum(n: SomeIntEnum): takes_some_int_enum(SomeIntEnum.x) takes_some_int_enum(1) # Error takes_some_int_enum(SomeIntEnum(1)) # How to deal with the above -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] main:7: error: Argument 1 to "takes_some_int_enum" has incompatible type "int"; expected "SomeIntEnum" @@ -249,7 +249,7 @@ def takes_int(i: int): pass takes_int(SomeIntEnum.x) takes_int(2) -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testIntEnum_functionReturningIntEnum] from enum import IntEnum @@ -262,7 +262,7 @@ an_int = returns_some_int_enum() an_enum = SomeIntEnum.x an_enum = returns_some_int_enum() -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] [case testStrEnumCreation] @@ -277,7 +277,7 @@ reveal_type(MyStrEnum.x) # N: Revealed type is "Literal[__main__.MyStrEnum.x]?" reveal_type(MyStrEnum.x.value) # N: Revealed type is "Literal['x']?" reveal_type(MyStrEnum.y) # N: Revealed type is "Literal[__main__.MyStrEnum.y]?" reveal_type(MyStrEnum.y.value) # N: Revealed type is "Literal['y']?" -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] [case testEnumMethods] @@ -312,7 +312,7 @@ takes_int(SomeExtIntEnum.x) def takes_some_ext_int_enum(s: SomeExtIntEnum): pass takes_some_ext_int_enum(SomeExtIntEnum.x) -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testNamedTupleEnum] from typing import NamedTuple @@ -344,7 +344,7 @@ class E(IntEnum): a = 1 s: str reveal_type(E[s]) -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] main:5: note: Revealed type is "__main__.E" @@ -354,7 +354,7 @@ class E(IntEnum): a = 1 E[1] # E: Enum index should be a string (actual index type "int") x = E[1] # E: Enum index should be a string (actual index type "int") -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testEnumIndexIsNotAnAlias] from enum import Enum @@ -372,7 +372,7 @@ def get_member(name: str) -> E: return val reveal_type(get_member('a')) # N: Revealed type is "__main__.E" -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testGenericEnum] from enum import Enum @@ -385,7 +385,7 @@ class F(Generic[T], Enum): # E: Enum class cannot be generic y: T reveal_type(F[int].x) # N: Revealed type is "__main__.F[builtins.int]" -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testEnumFlag] from enum import Flag @@ -397,7 +397,7 @@ if int(): x = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "C") if int(): x = x | C.b -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testEnumIntFlag] from enum import IntFlag @@ -409,7 +409,7 @@ if int(): x = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "C") if int(): x = x | C.b -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testAnonymousEnum] from enum import Enum @@ -420,7 +420,7 @@ class A: self.x = E.a a = A() reveal_type(a.x) -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] main:8: note: Revealed type is "__main__.E@4" @@ -436,7 +436,7 @@ x = A.E.a y = B.E.a if int(): x = y # E: Incompatible types in assignment (expression has type "__main__.B.E", variable has type "__main__.A.E") -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testFunctionalEnumString] from enum import Enum, IntEnum @@ -446,7 +446,7 @@ reveal_type(E.foo) reveal_type(E.bar.value) reveal_type(I.bar) reveal_type(I.baz.value) -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] main:4: note: Revealed type is "Literal[__main__.E.foo]?" main:5: note: Revealed type is "Any" @@ -459,7 +459,7 @@ E = Enum('E', ('foo', 'bar')) F = IntEnum('F', ['bar', 'baz']) reveal_type(E.foo) reveal_type(F.baz) -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] main:4: note: Revealed type is "Literal[__main__.E.foo]?" main:5: note: Revealed type is "Literal[__main__.F.baz]?" @@ -472,7 +472,7 @@ reveal_type(E.foo) reveal_type(F.baz) reveal_type(E.foo.value) reveal_type(F.bar.name) -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] main:4: note: Revealed type is "Literal[__main__.E.foo]?" main:5: note: Revealed type is "Literal[__main__.F.baz]?" @@ -487,7 +487,7 @@ reveal_type(E.foo) reveal_type(F.baz) reveal_type(E.foo.value) reveal_type(F.bar.name) -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] main:4: note: Revealed type is "Literal[__main__.E.foo]?" main:5: note: Revealed type is "Literal[__main__.F.baz]?" @@ -503,7 +503,7 @@ fake_enum1 = Enum('fake_enum1', ['a', 'b']) fake_enum2 = Enum('fake_enum2', names=['a', 'b']) fake_enum3 = Enum(value='fake_enum3', names=['a', 'b']) fake_enum4 = Enum(value='fake_enum4', names=['a', 'b'] , module=__name__) -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testFunctionalEnumErrors] from enum import Enum, IntEnum @@ -548,7 +548,7 @@ reveal_type(B.a.name) # N: Revealed type is "Literal['a']?" # TODO: The revealed type should be 'int' here reveal_type(A.x.value) # N: Revealed type is "Any" reveal_type(B.a.value) # N: Revealed type is "Any" -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testAnonymousFunctionalEnum] from enum import Enum @@ -558,7 +558,7 @@ class A: self.x = E.a a = A() reveal_type(a.x) -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] main:7: note: Revealed type is "__main__.A.E@4" @@ -572,7 +572,7 @@ x = A.E.a y = B.E.a if int(): x = y # E: Incompatible types in assignment (expression has type "__main__.B.E", variable has type "__main__.A.E") -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testFunctionalEnumProtocols] from enum import IntEnum @@ -590,7 +590,7 @@ a: E = E.x # type: ignore[used-before-def] class E(Enum): x = 1 y = 2 -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] [case testEnumWorkWithForward2] @@ -601,7 +601,7 @@ F = Enum('F', {'x': 1, 'y': 2}) def fn(x: F) -> None: pass fn(b) -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] [case testFunctionalEnum] @@ -619,7 +619,7 @@ Gu.a Gb.a Hu.a Hb.a -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] [case testEnumIncremental] @@ -632,7 +632,7 @@ class E(Enum): a = 1 b = 2 F = Enum('F', 'a b') -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [rechecked] [stale] [out1] @@ -791,7 +791,7 @@ class SomeEnum(Enum): from enum import Enum class SomeEnum(Enum): a = "foo" -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] main:2: note: Revealed type is "Literal[1]?" [out2] @@ -1019,7 +1019,7 @@ if x is Foo.A: else: reveal_type(x) # N: Revealed type is "Union[Literal[__main__.Foo.B], Literal[__main__.Foo.C], None]" reveal_type(x) # N: Revealed type is "Union[__main__.Foo, None]" -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testEnumReachabilityWithMultipleEnums] from enum import Enum @@ -1162,7 +1162,7 @@ class A: self.b = Enum("b", [("foo", "bar")]) # E: Enum type as attribute is not supported reveal_type(A().b) # N: Revealed type is "Any" -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testEnumReachabilityWithChaining] from enum import Enum @@ -1366,7 +1366,7 @@ class Foo(Enum): a = Foo.A reveal_type(a.value) # N: Revealed type is "Union[Literal[1]?, Literal[2]?]" reveal_type(a._value_) # N: Revealed type is "Union[Literal[1]?, Literal[2]?]" -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testNewSetsUnexpectedValueType] from enum import Enum @@ -1458,7 +1458,7 @@ class E(IntEnum): A = N(0) reveal_type(E.A.value) # N: Revealed type is "__main__.N" -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testEnumFinalValues] @@ -1471,7 +1471,7 @@ class Medal(Enum): Medal.gold = 0 # E: Cannot assign to final attribute "gold" # Same value: Medal.silver = 2 # E: Cannot assign to final attribute "silver" -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testEnumFinalValuesCannotRedefineValueProp] @@ -1479,7 +1479,7 @@ from enum import Enum class Types(Enum): key = 0 value = 1 -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testEnumReusedKeys] @@ -2093,8 +2093,7 @@ class C(Enum): _ignore_ = 'X' C._ignore_ # E: "Type[C]" has no attribute "_ignore_" -[typing fixtures/typing-medium.pyi] -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testCanOverrideDunderAttributes] import typing @@ -2185,7 +2184,7 @@ class MyEnum(Enum): # TODO: change the next line to use MyEnum._MyEnum__my_dict when mypy implements name mangling x: MyEnum = MyEnum.__my_dict # E: Incompatible types in assignment (expression has type "Dict[int, str]", variable has type "MyEnum") -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testEnumWithPrivateAttributeReachability] # flags: --warn-unreachable diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 3faf27c1e8a3..3c989f486118 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -1498,7 +1498,7 @@ def g(m: Medal) -> int: case Medal.bronze: reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.bronze]" return 2 -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testMatchLiteralPatternEnumCustomEquals-skip] from enum import Enum @@ -1516,7 +1516,7 @@ match m: reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.gold]" case _: reveal_type(m) # N: Revealed type is "__main__.Medal" -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [case testMatchNarrowUsingPatternGuardSpecialCase] def f(x: int | str) -> int: diff --git a/test-data/unit/deps-classes.test b/test-data/unit/deps-classes.test index 858fbd72779a..a8fc5d629491 100644 --- a/test-data/unit/deps-classes.test +++ b/test-data/unit/deps-classes.test @@ -178,7 +178,7 @@ def g() -> None: A.X [file m.py] class B: pass -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] -> m.g -> , m.A, m.f, m.g diff --git a/test-data/unit/diff.test b/test-data/unit/diff.test index b23f4a39ae3c..9af3e1b3daaf 100644 --- a/test-data/unit/diff.test +++ b/test-data/unit/diff.test @@ -566,7 +566,7 @@ A = Enum('A', 'x') B = Enum('B', 'y') C = IntEnum('C', 'x') D = IntEnum('D', 'x y') -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] __main__.B.x __main__.B.y @@ -606,7 +606,7 @@ class D(Enum): Y = 'b' class F(Enum): X = 0 -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] __main__.B.Y __main__.B.Z diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index ec53afeeb34c..16e23af0f0da 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -5327,7 +5327,7 @@ c: C c = C.X if int(): c = 1 -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] == == @@ -5359,7 +5359,7 @@ if int(): n = C.X if int(): n = c -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] == == @@ -5407,7 +5407,7 @@ from enum import Enum class C(Enum): X = 0 Y = 1 -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] == a.py:4: error: Argument 1 to "f" has incompatible type "C"; expected "int" @@ -5462,7 +5462,7 @@ if int(): n: int n = C.X n = c -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] == == diff --git a/test-data/unit/fixtures/enum.pyi b/test-data/unit/fixtures/enum.pyi new file mode 100644 index 000000000000..debffacf8f32 --- /dev/null +++ b/test-data/unit/fixtures/enum.pyi @@ -0,0 +1,16 @@ +# Minimal set of builtins required to work with Enums +from typing import TypeVar, Generic + +T = TypeVar('T') + +class object: + def __init__(self): pass + +class type: pass +class tuple(Generic[T]): + def __getitem__(self, x: int) -> T: pass + +class int: pass +class str: pass +class dict: pass +class ellipsis: pass diff --git a/test-data/unit/merge.test b/test-data/unit/merge.test index d5bc28b5d02a..a6a64c75b2a3 100644 --- a/test-data/unit/merge.test +++ b/test-data/unit/merge.test @@ -1490,7 +1490,7 @@ from enum import Enum class A(Enum): X = 0 Y = 1 -[builtins fixtures/tuple.pyi] +[builtins fixtures/enum.pyi] [out] TypeInfo<0>( Name(target.A) From 636e22d7c6b23c24217b4f36b99583b860c23781 Mon Sep 17 00:00:00 2001 From: Daverball Date: Sun, 10 Nov 2024 09:59:29 +0100 Subject: [PATCH 6/6] Adds missing enum fixture to new test cases --- test-data/unit/check-enum.test | 2 ++ test-data/unit/check-python310.test | 1 + 2 files changed, 3 insertions(+) diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index fee15f6e1c53..422beb5c82f0 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -2267,6 +2267,7 @@ class Medal(Enum): # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members \ # E: Incompatible types in assignment (expression has type "int", variable has type "str") bronze = 3 +[builtins fixtures/enum.pyi] [case testEnumMemberWithPlaceholder] from enum import Enum @@ -2276,6 +2277,7 @@ class Pet(Enum): DOG: str = ... # E: Enum members must be left unannotated \ # N: See https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members \ # E: Incompatible types in assignment (expression has type "ellipsis", variable has type "str") +[builtins fixtures/enum.pyi] [case testEnumValueWithPlaceholderNodeType] # https://github.com/python/mypy/issues/11971 diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 3f63b3549114..b31d4f5a61ce 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -1579,6 +1579,7 @@ match m: reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.bronze]" case _ as unreachable: assert_never(unreachable) +[builtins fixtures/enum.pyi] [case testMatchLiteralPatternEnumCustomEquals-skip] from enum import Enum