[case testNarrowingParentWithStrsBasic] # flags: --strict-equality --warn-unreachable from dataclasses import dataclass from typing import Literal, NamedTuple, Tuple, TypedDict, Union class Object1: key: Literal["A"] foo: int class Object2: key: Literal["B"] bar: str @dataclass class Dataclass1: key: Literal["A"] foo: int @dataclass class Dataclass2: key: Literal["B"] foo: str class NamedTuple1(NamedTuple): key: Literal["A"] foo: int class NamedTuple2(NamedTuple): key: Literal["B"] foo: str Tuple1 = Tuple[Literal["A"], int] Tuple2 = Tuple[Literal["B"], str] class TypedDict1(TypedDict): key: Literal["A"] foo: int class TypedDict2(TypedDict): key: Literal["B"] foo: str x1: Union[Object1, Object2] if x1.key == "A": reveal_type(x1) # N: Revealed type is "__main__.Object1" reveal_type(x1.key) # N: Revealed type is "Literal['A']" else: reveal_type(x1) # N: Revealed type is "__main__.Object2" reveal_type(x1.key) # N: Revealed type is "Literal['B']" x2: Union[Dataclass1, Dataclass2] if x2.key == "A": reveal_type(x2) # N: Revealed type is "__main__.Dataclass1" reveal_type(x2.key) # N: Revealed type is "Literal['A']" else: reveal_type(x2) # N: Revealed type is "__main__.Dataclass2" reveal_type(x2.key) # N: Revealed type is "Literal['B']" x3: Union[NamedTuple1, NamedTuple2] if x3.key == "A": reveal_type(x3) # N: Revealed type is "tuple[Literal['A'], builtins.int, fallback=__main__.NamedTuple1]" reveal_type(x3.key) # N: Revealed type is "Literal['A']" else: reveal_type(x3) # N: Revealed type is "tuple[Literal['B'], builtins.str, fallback=__main__.NamedTuple2]" reveal_type(x3.key) # N: Revealed type is "Literal['B']" if x3[0] == "A": reveal_type(x3) # N: Revealed type is "tuple[Literal['A'], builtins.int, fallback=__main__.NamedTuple1]" reveal_type(x3[0]) # N: Revealed type is "Literal['A']" else: reveal_type(x3) # N: Revealed type is "tuple[Literal['B'], builtins.str, fallback=__main__.NamedTuple2]" reveal_type(x3[0]) # N: Revealed type is "Literal['B']" x4: Union[Tuple1, Tuple2] if x4[0] == "A": reveal_type(x4) # N: Revealed type is "tuple[Literal['A'], builtins.int]" reveal_type(x4[0]) # N: Revealed type is "Literal['A']" else: reveal_type(x4) # N: Revealed type is "tuple[Literal['B'], builtins.str]" reveal_type(x4[0]) # N: Revealed type is "Literal['B']" x5: Union[TypedDict1, TypedDict2] if x5["key"] == "A": reveal_type(x5) # N: Revealed type is "TypedDict('__main__.TypedDict1', {'key': Literal['A'], 'foo': builtins.int})" else: reveal_type(x5) # N: Revealed type is "TypedDict('__main__.TypedDict2', {'key': Literal['B'], 'foo': builtins.str})" [builtins fixtures/primitives.pyi] [typing fixtures/typing-typeddict.pyi] [case testNarrowingParentWithEnumsBasic] # flags: --strict-equality --warn-unreachable from enum import Enum from dataclasses import dataclass from typing import Literal, NamedTuple, Tuple, TypedDict, Union class Key(Enum): A = 1 B = 2 C = 3 class Object1: key: Literal[Key.A] foo: int class Object2: key: Literal[Key.B] bar: str @dataclass class Dataclass1: key: Literal[Key.A] foo: int @dataclass class Dataclass2: key: Literal[Key.B] foo: str class NamedTuple1(NamedTuple): key: Literal[Key.A] foo: int class NamedTuple2(NamedTuple): key: Literal[Key.B] foo: str Tuple1 = Tuple[Literal[Key.A], int] Tuple2 = Tuple[Literal[Key.B], str] class TypedDict1(TypedDict): key: Literal[Key.A] foo: int class TypedDict2(TypedDict): key: Literal[Key.B] foo: str x1: Union[Object1, Object2] if x1.key is Key.A: reveal_type(x1) # N: Revealed type is "__main__.Object1" reveal_type(x1.key) # N: Revealed type is "Literal[__main__.Key.A]" else: reveal_type(x1) # N: Revealed type is "__main__.Object2" reveal_type(x1.key) # N: Revealed type is "Literal[__main__.Key.B]" x2: Union[Dataclass1, Dataclass2] if x2.key is Key.A: reveal_type(x2) # N: Revealed type is "__main__.Dataclass1" reveal_type(x2.key) # N: Revealed type is "Literal[__main__.Key.A]" else: reveal_type(x2) # N: Revealed type is "__main__.Dataclass2" reveal_type(x2.key) # N: Revealed type is "Literal[__main__.Key.B]" x3: Union[NamedTuple1, NamedTuple2] if x3.key is Key.A: reveal_type(x3) # N: Revealed type is "tuple[Literal[__main__.Key.A], builtins.int, fallback=__main__.NamedTuple1]" reveal_type(x3.key) # N: Revealed type is "Literal[__main__.Key.A]" else: reveal_type(x3) # N: Revealed type is "tuple[Literal[__main__.Key.B], builtins.str, fallback=__main__.NamedTuple2]" reveal_type(x3.key) # N: Revealed type is "Literal[__main__.Key.B]" if x3[0] is Key.A: reveal_type(x3) # N: Revealed type is "tuple[Literal[__main__.Key.A], builtins.int, fallback=__main__.NamedTuple1]" reveal_type(x3[0]) # N: Revealed type is "Literal[__main__.Key.A]" else: reveal_type(x3) # N: Revealed type is "tuple[Literal[__main__.Key.B], builtins.str, fallback=__main__.NamedTuple2]" reveal_type(x3[0]) # N: Revealed type is "Literal[__main__.Key.B]" x4: Union[Tuple1, Tuple2] if x4[0] is Key.A: reveal_type(x4) # N: Revealed type is "tuple[Literal[__main__.Key.A], builtins.int]" reveal_type(x4[0]) # N: Revealed type is "Literal[__main__.Key.A]" else: reveal_type(x4) # N: Revealed type is "tuple[Literal[__main__.Key.B], builtins.str]" reveal_type(x4[0]) # N: Revealed type is "Literal[__main__.Key.B]" x5: Union[TypedDict1, TypedDict2] if x5["key"] is Key.A: reveal_type(x5) # N: Revealed type is "TypedDict('__main__.TypedDict1', {'key': Literal[__main__.Key.A], 'foo': builtins.int})" else: reveal_type(x5) # N: Revealed type is "TypedDict('__main__.TypedDict2', {'key': Literal[__main__.Key.B], 'foo': builtins.str})" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] [case testNarrowingParentWithIsInstanceBasic] # flags: --strict-equality --warn-unreachable from dataclasses import dataclass from typing import NamedTuple, Tuple, TypedDict, Union class Object1: key: int class Object2: key: str @dataclass class Dataclass1: key: int @dataclass class Dataclass2: key: str class NamedTuple1(NamedTuple): key: int class NamedTuple2(NamedTuple): key: str Tuple1 = Tuple[int] Tuple2 = Tuple[str] class TypedDict1(TypedDict): key: int class TypedDict2(TypedDict): key: str x1: Union[Object1, Object2] if isinstance(x1.key, int): reveal_type(x1) # N: Revealed type is "__main__.Object1" else: reveal_type(x1) # N: Revealed type is "__main__.Object2" x2: Union[Dataclass1, Dataclass2] if isinstance(x2.key, int): reveal_type(x2) # N: Revealed type is "__main__.Dataclass1" else: reveal_type(x2) # N: Revealed type is "__main__.Dataclass2" x3: Union[NamedTuple1, NamedTuple2] if isinstance(x3.key, int): reveal_type(x3) # N: Revealed type is "tuple[builtins.int, fallback=__main__.NamedTuple1]" else: reveal_type(x3) # N: Revealed type is "tuple[builtins.str, fallback=__main__.NamedTuple2]" if isinstance(x3[0], int): reveal_type(x3) # N: Revealed type is "tuple[builtins.int, fallback=__main__.NamedTuple1]" else: reveal_type(x3) # N: Revealed type is "tuple[builtins.str, fallback=__main__.NamedTuple2]" x4: Union[Tuple1, Tuple2] if isinstance(x4[0], int): reveal_type(x4) # N: Revealed type is "tuple[builtins.int]" else: reveal_type(x4) # N: Revealed type is "tuple[builtins.str]" x5: Union[TypedDict1, TypedDict2] if isinstance(x5["key"], int): reveal_type(x5) # N: Revealed type is "TypedDict('__main__.TypedDict1', {'key': builtins.int})" else: reveal_type(x5) # N: Revealed type is "TypedDict('__main__.TypedDict2', {'key': builtins.str})" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] [case testNarrowingParentMultipleKeys] # flags: --strict-equality --warn-unreachable from enum import Enum from typing import Literal, Union class Key(Enum): A = 1 B = 2 C = 3 D = 4 class Object1: key: Literal[Key.A, Key.C] class Object2: key: Literal[Key.B, Key.C] x: Union[Object1, Object2] if x.key is Key.A: reveal_type(x) # N: Revealed type is "__main__.Object1" else: reveal_type(x) # N: Revealed type is "__main__.Object1 | __main__.Object2" if x.key is Key.C: reveal_type(x) # N: Revealed type is "__main__.Object1 | __main__.Object2" else: reveal_type(x) # N: Revealed type is "__main__.Object1 | __main__.Object2" if x.key is Key.D: # E: Non-overlapping identity check (left operand type: "Literal[Key.A, Key.C, Key.B]", right operand type: "Literal[Key.D]") reveal_type(x) # E: Statement is unreachable else: reveal_type(x) # N: Revealed type is "__main__.Object1 | __main__.Object2" [builtins fixtures/tuple.pyi] [case testNarrowingTypedDictParentMultipleKeys] # flags: --strict-equality --warn-unreachable from typing import Literal, TypedDict, Union class TypedDict1(TypedDict): key: Literal['A', 'C'] class TypedDict2(TypedDict): key: Literal['B', 'C'] x: Union[TypedDict1, TypedDict2] if x['key'] == 'A': reveal_type(x) # N: Revealed type is "TypedDict('__main__.TypedDict1', {'key': Literal['A'] | Literal['C']})" else: reveal_type(x) # N: Revealed type is "TypedDict('__main__.TypedDict1', {'key': Literal['A'] | Literal['C']}) | TypedDict('__main__.TypedDict2', {'key': Literal['B'] | Literal['C']})" if x['key'] == 'C': reveal_type(x) # N: Revealed type is "TypedDict('__main__.TypedDict1', {'key': Literal['A'] | Literal['C']}) | TypedDict('__main__.TypedDict2', {'key': Literal['B'] | Literal['C']})" else: reveal_type(x) # N: Revealed type is "TypedDict('__main__.TypedDict1', {'key': Literal['A'] | Literal['C']}) | TypedDict('__main__.TypedDict2', {'key': Literal['B'] | Literal['C']})" if x['key'] == 'D': # E: Non-overlapping equality check (left operand type: "Literal['A', 'C', 'B']", right operand type: "Literal['D']") reveal_type(x) # E: Statement is unreachable else: reveal_type(x) # N: Revealed type is "TypedDict('__main__.TypedDict1', {'key': Literal['A'] | Literal['C']}) | TypedDict('__main__.TypedDict2', {'key': Literal['B'] | Literal['C']})" [builtins fixtures/primitives.pyi] [typing fixtures/typing-typeddict.pyi] [case testNarrowingPartialTypedDictParentMultipleKeys] # flags: --strict-equality --warn-unreachable from typing import Literal, TypedDict, Union class TypedDict1(TypedDict, total=False): key: Literal['A', 'C'] class TypedDict2(TypedDict, total=False): key: Literal['B', 'C'] x: Union[TypedDict1, TypedDict2] if x['key'] == 'A': reveal_type(x) # N: Revealed type is "TypedDict('__main__.TypedDict1', {'key'?: Literal['A'] | Literal['C']})" else: reveal_type(x) # N: Revealed type is "TypedDict('__main__.TypedDict1', {'key'?: Literal['A'] | Literal['C']}) | TypedDict('__main__.TypedDict2', {'key'?: Literal['B'] | Literal['C']})" if x['key'] == 'C': reveal_type(x) # N: Revealed type is "TypedDict('__main__.TypedDict1', {'key'?: Literal['A'] | Literal['C']}) | TypedDict('__main__.TypedDict2', {'key'?: Literal['B'] | Literal['C']})" else: reveal_type(x) # N: Revealed type is "TypedDict('__main__.TypedDict1', {'key'?: Literal['A'] | Literal['C']}) | TypedDict('__main__.TypedDict2', {'key'?: Literal['B'] | Literal['C']})" if x['key'] == 'D': # E: Non-overlapping equality check (left operand type: "Literal['A', 'C', 'B']", right operand type: "Literal['D']") reveal_type(x) # E: Statement is unreachable else: reveal_type(x) # N: Revealed type is "TypedDict('__main__.TypedDict1', {'key'?: Literal['A'] | Literal['C']}) | TypedDict('__main__.TypedDict2', {'key'?: Literal['B'] | Literal['C']})" [builtins fixtures/primitives.pyi] [typing fixtures/typing-typeddict.pyi] [case testNarrowingNestedTypedDicts] # flags: --strict-equality --warn-unreachable from typing import Literal, TypedDict, Union class A(TypedDict): key: Literal['A'] class B(TypedDict): key: Literal['B'] class C(TypedDict): key: Literal['C'] class X(TypedDict): inner: Union[A, B] class Y(TypedDict): inner: Union[B, C] unknown: Union[X, Y] if unknown['inner']['key'] == 'A': reveal_type(unknown) # N: Revealed type is "TypedDict('__main__.X', {'inner': TypedDict('__main__.A', {'key': Literal['A']}) | TypedDict('__main__.B', {'key': Literal['B']})})" reveal_type(unknown['inner']) # N: Revealed type is "TypedDict('__main__.A', {'key': Literal['A']})" if unknown['inner']['key'] == 'B': reveal_type(unknown) # N: Revealed type is "TypedDict('__main__.X', {'inner': TypedDict('__main__.A', {'key': Literal['A']}) | TypedDict('__main__.B', {'key': Literal['B']})}) | TypedDict('__main__.Y', {'inner': TypedDict('__main__.B', {'key': Literal['B']}) | TypedDict('__main__.C', {'key': Literal['C']})})" reveal_type(unknown['inner']) # N: Revealed type is "TypedDict('__main__.B', {'key': Literal['B']})" if unknown['inner']['key'] == 'C': reveal_type(unknown) # N: Revealed type is "TypedDict('__main__.Y', {'inner': TypedDict('__main__.B', {'key': Literal['B']}) | TypedDict('__main__.C', {'key': Literal['C']})})" reveal_type(unknown['inner']) # N: Revealed type is "TypedDict('__main__.C', {'key': Literal['C']})" [builtins fixtures/primitives.pyi] [typing fixtures/typing-typeddict.pyi] [case testNarrowingParentWithMultipleParents] # flags: --strict-equality --warn-unreachable from enum import Enum from typing import Literal, Union class Key(Enum): A = 1 B = 2 C = 3 class Object1: key: Literal[Key.A] class Object2: key: Literal[Key.B] class Object3: key: Literal[Key.C] class Object4: key: str x: Union[Object1, Object2, Object3, Object4] if x.key is Key.A: reveal_type(x) # N: Revealed type is "__main__.Object1" else: reveal_type(x) # N: Revealed type is "__main__.Object2 | __main__.Object3 | __main__.Object4" if isinstance(x.key, str): reveal_type(x) # N: Revealed type is "__main__.Object4" else: reveal_type(x) # N: Revealed type is "__main__.Object1 | __main__.Object2 | __main__.Object3" [builtins fixtures/isinstance.pyi] [case testNarrowingParentsWithGenerics] # flags: --strict-equality --warn-unreachable from typing import Union, TypeVar, Generic T = TypeVar('T') class Wrapper(Generic[T]): key: T x: Union[Wrapper[int], Wrapper[str]] if isinstance(x.key, int): reveal_type(x) # N: Revealed type is "__main__.Wrapper[builtins.int]" else: reveal_type(x) # N: Revealed type is "__main__.Wrapper[builtins.str]" [builtins fixtures/isinstance.pyi] [case testNarrowingParentWithParentMixtures] # flags: --strict-equality --warn-unreachable from enum import Enum from typing import Literal, Union, NamedTuple, TypedDict class Key(Enum): A = 1 B = 2 C = 3 class KeyedObject: key: Literal[Key.A] class KeyedTypedDict(TypedDict): key: Literal[Key.B] class KeyedNamedTuple(NamedTuple): key: Literal[Key.C] ok_mixture: Union[KeyedObject, KeyedNamedTuple] if ok_mixture.key is Key.A: reveal_type(ok_mixture) # N: Revealed type is "__main__.KeyedObject" else: reveal_type(ok_mixture) # N: Revealed type is "tuple[Literal[__main__.Key.C], fallback=__main__.KeyedNamedTuple]" impossible_mixture: Union[KeyedObject, KeyedTypedDict] if impossible_mixture.key is Key.A: # E: Item "KeyedTypedDict" of "KeyedObject | KeyedTypedDict" has no attribute "key" reveal_type(impossible_mixture) # N: Revealed type is "__main__.KeyedObject | TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]})" else: reveal_type(impossible_mixture) # N: Revealed type is "__main__.KeyedObject | TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]})" if impossible_mixture["key"] is Key.A: # E: Value of type "KeyedObject | KeyedTypedDict" is not indexable reveal_type(impossible_mixture) # N: Revealed type is "__main__.KeyedObject | TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]})" else: reveal_type(impossible_mixture) # N: Revealed type is "__main__.KeyedObject | TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]})" weird_mixture: Union[KeyedTypedDict, KeyedNamedTuple] if weird_mixture["key"] is Key.B: # E: No overload variant of "__getitem__" of "tuple" matches argument type "str" \ # N: Possible overload variants: \ # N: def __getitem__(self, int, /) -> Literal[Key.C] \ # N: def __getitem__(self, slice, /) -> tuple[Literal[Key.C], ...] reveal_type(weird_mixture) # N: Revealed type is "TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]}) | tuple[Literal[__main__.Key.C], fallback=__main__.KeyedNamedTuple]" else: reveal_type(weird_mixture) # N: Revealed type is "TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]}) | tuple[Literal[__main__.Key.C], fallback=__main__.KeyedNamedTuple]" if weird_mixture[0] is Key.B: # E: TypedDict key must be a string literal; expected one of ("key") reveal_type(weird_mixture) # N: Revealed type is "TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]}) | tuple[Literal[__main__.Key.C], fallback=__main__.KeyedNamedTuple]" else: reveal_type(weird_mixture) # N: Revealed type is "TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]}) | tuple[Literal[__main__.Key.C], fallback=__main__.KeyedNamedTuple]" [builtins fixtures/tuple.pyi] [typing fixtures/typing-full.pyi] [case testNarrowingParentWithProperties] # flags: --strict-equality --warn-unreachable from enum import Enum from typing import Literal, Union class Key(Enum): A = 1 B = 2 C = 3 class Object1: key: Literal[Key.A] class Object2: @property def key(self) -> Literal[Key.A]: ... class Object3: @property def key(self) -> Literal[Key.B]: ... x: Union[Object1, Object2, Object3] if x.key is Key.A: reveal_type(x) # N: Revealed type is "__main__.Object1 | __main__.Object2" else: reveal_type(x) # N: Revealed type is "__main__.Object3" [builtins fixtures/property.pyi] [case testNarrowingParentWithAny] # flags: --strict-equality --warn-unreachable from enum import Enum from typing import Literal, Union, Any class Key(Enum): A = 1 B = 2 C = 3 class Object1: key: Literal[Key.A] class Object2: key: Literal[Key.B] x: Union[Object1, Object2, Any] if x.key is Key.A: reveal_type(x.key) # N: Revealed type is "Literal[__main__.Key.A]" reveal_type(x) # N: Revealed type is "__main__.Object1 | Any" else: # TODO: Is this a bug? Should we skip inferring Any for singleton types? reveal_type(x.key) # N: Revealed type is "Any | Literal[__main__.Key.B]" reveal_type(x) # N: Revealed type is "__main__.Object1 | __main__.Object2 | Any" [builtins fixtures/tuple.pyi] [case testNarrowingParentsHierarchy] # flags: --strict-equality --warn-unreachable from typing import Literal, Union from enum import Enum class Key(Enum): A = 1 B = 2 C = 3 class Parent1: child: Union[Child1, Child2] class Parent2: child: Union[Child2, Child3] class Parent3: child: Union[Child3, Child1] class Child1: main: Literal[Key.A] same_for_1_and_2: Literal[Key.A] class Child2: main: Literal[Key.B] same_for_1_and_2: Literal[Key.A] class Child3: main: Literal[Key.C] same_for_1_and_2: Literal[Key.B] x: Union[Parent1, Parent2, Parent3] if x.child.main is Key.A: reveal_type(x) # N: Revealed type is "__main__.Parent1 | __main__.Parent3" reveal_type(x.child) # N: Revealed type is "__main__.Child1" else: reveal_type(x) # N: Revealed type is "__main__.Parent1 | __main__.Parent2 | __main__.Parent3" reveal_type(x.child) # N: Revealed type is "__main__.Child2 | __main__.Child3" if x.child.same_for_1_and_2 is Key.A: reveal_type(x) # N: Revealed type is "__main__.Parent1 | __main__.Parent2 | __main__.Parent3" reveal_type(x.child) # N: Revealed type is "__main__.Child1 | __main__.Child2" else: reveal_type(x) # N: Revealed type is "__main__.Parent2 | __main__.Parent3" reveal_type(x.child) # N: Revealed type is "__main__.Child3" y: Union[Parent1, Parent2] if y.child.main is Key.A: reveal_type(y) # N: Revealed type is "__main__.Parent1" reveal_type(y.child) # N: Revealed type is "__main__.Child1" else: reveal_type(y) # N: Revealed type is "__main__.Parent1 | __main__.Parent2" reveal_type(y.child) # N: Revealed type is "__main__.Child2 | __main__.Child3" if y.child.same_for_1_and_2 is Key.A: reveal_type(y) # N: Revealed type is "__main__.Parent1 | __main__.Parent2" reveal_type(y.child) # N: Revealed type is "__main__.Child1 | __main__.Child2" else: reveal_type(y) # N: Revealed type is "__main__.Parent2" reveal_type(y.child) # N: Revealed type is "__main__.Child3" [builtins fixtures/tuple.pyi] [case testNarrowingParentsHierarchyGenerics] # flags: --strict-equality --warn-unreachable from typing import Generic, TypeVar, Union T = TypeVar('T') class Model(Generic[T]): attr: T class A: model: Model[int] class B: model: Model[str] x: Union[A, B] if isinstance(x.model.attr, int): reveal_type(x) # N: Revealed type is "__main__.A" reveal_type(x.model) # N: Revealed type is "__main__.Model[builtins.int]" else: reveal_type(x) # N: Revealed type is "__main__.B" reveal_type(x.model) # N: Revealed type is "__main__.Model[builtins.str]" [builtins fixtures/isinstance.pyi] [case testNarrowingParentsHierarchyTypedDict] # flags: --strict-equality --warn-unreachable from typing import Literal, TypedDict, Union from enum import Enum class Key(Enum): A = 1 B = 2 C = 3 class Parent1(TypedDict): model: Model1 foo: int class Parent2(TypedDict): model: Model2 bar: str class Model1(TypedDict): key: Literal[Key.A] class Model2(TypedDict): key: Literal[Key.B] x: Union[Parent1, Parent2] if x["model"]["key"] is Key.A: reveal_type(x) # N: Revealed type is "TypedDict('__main__.Parent1', {'model': TypedDict('__main__.Model1', {'key': Literal[__main__.Key.A]}), 'foo': builtins.int})" reveal_type(x["model"]) # N: Revealed type is "TypedDict('__main__.Model1', {'key': Literal[__main__.Key.A]})" else: reveal_type(x) # N: Revealed type is "TypedDict('__main__.Parent2', {'model': TypedDict('__main__.Model2', {'key': Literal[__main__.Key.B]}), 'bar': builtins.str})" reveal_type(x["model"]) # N: Revealed type is "TypedDict('__main__.Model2', {'key': Literal[__main__.Key.B]})" y: Union[Parent1, Parent2] if y["model"]["key"] is Key.C: # E: Non-overlapping identity check (left operand type: "Literal[Key.A, Key.B]", right operand type: "Literal[Key.C]") reveal_type(y) # E: Statement is unreachable reveal_type(y["model"]) else: reveal_type(y) # N: Revealed type is "TypedDict('__main__.Parent1', {'model': TypedDict('__main__.Model1', {'key': Literal[__main__.Key.A]}), 'foo': builtins.int}) | TypedDict('__main__.Parent2', {'model': TypedDict('__main__.Model2', {'key': Literal[__main__.Key.B]}), 'bar': builtins.str})" reveal_type(y["model"]) # N: Revealed type is "TypedDict('__main__.Model1', {'key': Literal[__main__.Key.A]}) | TypedDict('__main__.Model2', {'key': Literal[__main__.Key.B]})" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] [case testNarrowingParentsHierarchyTypedDictWithStr] # flags: --strict-equality --warn-unreachable from typing import Literal, TypedDict, Union class Parent1(TypedDict): model: Model1 foo: int class Parent2(TypedDict): model: Model2 bar: str class Model1(TypedDict): key: Literal['A'] class Model2(TypedDict): key: Literal['B'] x: Union[Parent1, Parent2] if x["model"]["key"] == 'A': reveal_type(x) # N: Revealed type is "TypedDict('__main__.Parent1', {'model': TypedDict('__main__.Model1', {'key': Literal['A']}), 'foo': builtins.int})" reveal_type(x["model"]) # N: Revealed type is "TypedDict('__main__.Model1', {'key': Literal['A']})" else: reveal_type(x) # N: Revealed type is "TypedDict('__main__.Parent2', {'model': TypedDict('__main__.Model2', {'key': Literal['B']}), 'bar': builtins.str})" reveal_type(x["model"]) # N: Revealed type is "TypedDict('__main__.Model2', {'key': Literal['B']})" y: Union[Parent1, Parent2] if y["model"]["key"] == 'C': # E: Non-overlapping equality check (left operand type: "Literal['A', 'B']", right operand type: "Literal['C']") reveal_type(y) # E: Statement is unreachable reveal_type(y["model"]) else: reveal_type(y) # N: Revealed type is "TypedDict('__main__.Parent1', {'model': TypedDict('__main__.Model1', {'key': Literal['A']}), 'foo': builtins.int}) | TypedDict('__main__.Parent2', {'model': TypedDict('__main__.Model2', {'key': Literal['B']}), 'bar': builtins.str})" reveal_type(y["model"]) # N: Revealed type is "TypedDict('__main__.Model1', {'key': Literal['A']}) | TypedDict('__main__.Model2', {'key': Literal['B']})" [builtins fixtures/primitives.pyi] [typing fixtures/typing-typeddict.pyi] [case testNarrowingExprPropagation] # flags: --strict-equality --warn-unreachable from typing import Literal, Union class A: tag: Literal['A'] class B: tag: Literal['B'] abo: Union[A, B, None] if abo is not None and abo.tag == "A": reveal_type(abo.tag) # N: Revealed type is "Literal['A']" reveal_type(abo) # N: Revealed type is "__main__.A" if not (abo is None or abo.tag != "B"): reveal_type(abo.tag) # N: Revealed type is "Literal['B']" reveal_type(abo) # N: Revealed type is "__main__.B" [builtins fixtures/primitives.pyi] [case testNarrowingEqualityFlipFlop] # flags: --strict-equality --warn-unreachable from typing import Final, Literal from enum import Enum class State(Enum): A = 1 B = 2 class FlipFlopEnum: def __init__(self) -> None: self.state = State.A def mutate(self) -> None: self.state = State.B if self.state == State.A else State.A class FlipFlopStr: def __init__(self) -> None: self.state = "state-1" def mutate(self) -> None: self.state = "state-2" if self.state == "state-1" else "state-1" def test1(switch: FlipFlopStr) -> None: # Naively, we might assume the 'assert' here would narrow the type to # Literal["state-1"]. However, doing this ends up breaking a fair number of real-world # code (usually test cases) that looks similar to this function: e.g. checks # to make sure a field was mutated to some particular value. # # And since mypy can't really reason about state mutation, we take a conservative # approach and avoid narrowing anything here. assert switch.state == "state-1" reveal_type(switch.state) # N: Revealed type is "builtins.str" switch.mutate() assert switch.state == "state-2" reveal_type(switch.state) # N: Revealed type is "builtins.str" def test2(switch: FlipFlopEnum) -> None: # This is the same thing as 'test1', except we use enums, which we allow to be narrowed # to literals. assert switch.state == State.A reveal_type(switch.state) # N: Revealed type is "Literal[__main__.State.A]" switch.mutate() assert switch.state == State.B # E: Non-overlapping equality check (left operand type: "Literal[State.A]", right operand type: "Literal[State.B]") reveal_type(switch.state) # E: Statement is unreachable def test3(switch: FlipFlopEnum) -> None: # Same thing, but using 'is' comparisons. Previously mypy's behaviour differed # here, narrowing when using 'is', but not when using '=='. assert switch.state is State.A reveal_type(switch.state) # N: Revealed type is "Literal[__main__.State.A]" switch.mutate() assert switch.state is State.B # E: Non-overlapping identity check (left operand type: "Literal[State.A]", right operand type: "Literal[State.B]") reveal_type(switch.state) # E: Statement is unreachable [builtins fixtures/primitives.pyi] [case testNarrowingEqualityRequiresExplicitStrLiteral] # flags: --strict-equality --warn-unreachable from typing import Final, Literal A_final: Final = "A" A_literal: Literal["A"] # Neither the LHS nor the RHS are explicit literals, so regrettably nothing # is narrowed here -- see 'testNarrowingEqualityFlipFlop' for an example of # why more precise inference here is problematic. x_str: str if x_str == "A": reveal_type(x_str) # N: Revealed type is "builtins.str" else: reveal_type(x_str) # N: Revealed type is "builtins.str" reveal_type(x_str) # N: Revealed type is "builtins.str" if x_str == A_final: reveal_type(x_str) # N: Revealed type is "builtins.str" else: reveal_type(x_str) # N: Revealed type is "builtins.str" reveal_type(x_str) # N: Revealed type is "builtins.str" # But the RHS is a literal, so we can at least narrow the 'if' case now. if x_str == A_literal: reveal_type(x_str) # N: Revealed type is "Literal['A']" else: reveal_type(x_str) # N: Revealed type is "builtins.str" reveal_type(x_str) # N: Revealed type is "builtins.str" # But in these two cases, the LHS is a literal/literal-like type. So we # assume the user *does* want literal-based narrowing and narrow accordingly # regardless of whether the RHS is an explicit literal or not. x_union: Literal["A", "B", None] if x_union == A_final: reveal_type(x_union) # N: Revealed type is "Literal['A']" else: reveal_type(x_union) # N: Revealed type is "Literal['B'] | None" reveal_type(x_union) # N: Revealed type is "Literal['A'] | Literal['B'] | None" if x_union == A_literal: reveal_type(x_union) # N: Revealed type is "Literal['A']" else: reveal_type(x_union) # N: Revealed type is "Literal['B'] | None" reveal_type(x_union) # N: Revealed type is "Literal['A'] | Literal['B'] | None" [builtins fixtures/primitives.pyi] [case testNarrowingEqualityRequiresExplicitEnumLiteral] # flags: --strict-equality --warn-unreachable from typing import Final, Literal, Union from enum import Enum class Foo(Enum): A = 1 B = 2 A_final: Final = Foo.A A_literal: Literal[Foo.A] # Note this is unlike testNarrowingEqualityRequiresExplicitStrLiteral # See also testNarrowingEqualityFlipFlop x1: Foo if x1 == Foo.A: reveal_type(x1) # N: Revealed type is "Literal[__main__.Foo.A]" else: reveal_type(x1) # N: Revealed type is "Literal[__main__.Foo.B]" x2: Foo if x2 == A_final: reveal_type(x2) # N: Revealed type is "Literal[__main__.Foo.A]" else: reveal_type(x2) # N: Revealed type is "Literal[__main__.Foo.B]" # But we let this narrow since there's an explicit literal in the RHS. x3: Foo if x3 == A_literal: reveal_type(x3) # N: Revealed type is "Literal[__main__.Foo.A]" else: reveal_type(x3) # N: Revealed type is "Literal[__main__.Foo.B]" class SingletonFoo(Enum): A = "A" def bar(x: Union[SingletonFoo, Foo], y: SingletonFoo) -> None: if x == y: reveal_type(x) # N: Revealed type is "Literal[__main__.SingletonFoo.A]" [builtins fixtures/primitives.pyi] [case testNarrowingEqualityCustomEqualityDisabled] # flags: --strict-equality --warn-unreachable from typing import Literal, Union class Custom: def __eq__(self, other: object) -> bool: return True def f1(x: Union[Custom, Literal[1], Literal[2]]): if x == 1: reveal_type(x) # N: Revealed type is "__main__.Custom | Literal[1]" else: reveal_type(x) # N: Revealed type is "__main__.Custom | Literal[2]" class Default: pass def f2(x: Union[Default, Literal[1], Literal[2]]): if x == 1: reveal_type(x) # N: Revealed type is "Literal[1]" else: reveal_type(x) # N: Revealed type is "__main__.Default | Literal[2]" [builtins fixtures/primitives.pyi] [case testNarrowingEqualityCustomEqualityEnum] # flags: --strict-equality --warn-unreachable from typing import Literal, Union from enum import Enum class CustomEnum(Enum): A = 1 B = 2 def __eq__(self, other: object) -> bool: return True x3: CustomEnum key: Literal[CustomEnum.A] if x3 == key: reveal_type(x3) # N: Revealed type is "__main__.CustomEnum" else: reveal_type(x3) # N: Revealed type is "Literal[__main__.CustomEnum.B]" # For comparison, this narrows since we bypass __eq__ if x3 is key: reveal_type(x3) # N: Revealed type is "Literal[__main__.CustomEnum.A]" else: reveal_type(x3) # N: Revealed type is "Literal[__main__.CustomEnum.B]" [builtins fixtures/primitives.pyi] [case testNarrowingEqualityCustomEqualityChainedComparison] # flags: --strict-equality --warn-unreachable from typing import Literal, Union class Custom: def __eq__(self, other: object) -> bool: return True def f1(x: Literal[1, 2, None], y: Custom): if 1 == x == y: reveal_type(x) # N: Revealed type is "Literal[1]" reveal_type(y) # N: Revealed type is "__main__.Custom" else: reveal_type(x) # N: Revealed type is "Literal[1] | Literal[2] | None" reveal_type(y) # N: Revealed type is "__main__.Custom" class Default: pass def f2(x: Literal[1, 2, None], z: Default): if 1 == x == z: # E: Non-overlapping equality check (left operand type: "Literal[1, 2] | None", right operand type: "Default") reveal_type(x) # E: Statement is unreachable reveal_type(z) else: reveal_type(x) # N: Revealed type is "Literal[1] | Literal[2] | None" reveal_type(z) # N: Revealed type is "__main__.Default" [builtins fixtures/primitives.pyi] [case testNarrowingCustomEqualityLiteralElseBranch] # flags: --strict-equality --warn-unreachable from __future__ import annotations from typing import Literal class Custom: def __eq__(self, other: object) -> bool: raise def f(v: Custom | Literal["text"]) -> Custom | None: if v == "text": reveal_type(v) # N: Revealed type is "__main__.Custom | Literal['text']" return None else: reveal_type(v) # N: Revealed type is "__main__.Custom" return v def g(v: Custom | Literal["text"]) -> Custom | None: if v != "text": reveal_type(v) # N: Revealed type is "__main__.Custom" return None else: reveal_type(v) # N: Revealed type is "__main__.Custom | Literal['text']" return v # E: Incompatible return value type (got "Custom | Literal['text']", expected "Custom | None") [builtins fixtures/primitives.pyi] [case testNarrowingCustomEqualityUnion] # flags: --strict-equality --warn-unreachable from __future__ import annotations from typing import Any def realistic(x: dict[str, Any]): val = x.get("hey") if val == 12: reveal_type(val) # N: Revealed type is "Any | Literal[12]?" def f1(x: Any | None): if x == 12: reveal_type(x) # N: Revealed type is "Any | Literal[12]?" class Custom: def __eq__(self, other: object) -> bool: raise def f2(x: Custom | None): if x == 12: reveal_type(x) # N: Revealed type is "__main__.Custom" else: reveal_type(x) # N: Revealed type is "__main__.Custom | None" [builtins fixtures/dict.pyi] [case testNarrowingCustomEqualityUnion2] # flags: --strict-equality --warn-unreachable from __future__ import annotations from typing import Any class Custom: def __eq__(self, other: object) -> bool: raise def f3(x: str | Custom, y: str | int): if x == y: reveal_type(x) # N: Revealed type is "builtins.str | __main__.Custom" reveal_type(y) # N: Revealed type is "builtins.str | builtins.int" else: reveal_type(x) # N: Revealed type is "builtins.str | __main__.Custom" reveal_type(y) # N: Revealed type is "builtins.str | builtins.int" def f4(x: str | Any, y: str | int): if x == y: reveal_type(x) # N: Revealed type is "builtins.str | Any | builtins.int" reveal_type(y) # N: Revealed type is "builtins.str | builtins.int" else: reveal_type(x) # N: Revealed type is "builtins.str | Any" reveal_type(y) # N: Revealed type is "builtins.str | builtins.int" [builtins fixtures/dict.pyi] [case testNarrowingCustomEqualityUnion3] # flags: --strict-equality --warn-unreachable from __future__ import annotations from typing import Any class Custom: def __eq__(self, other: object) -> bool: raise def f(x: Custom | None, y: int | None): if x == y: reveal_type(x) # N: Revealed type is "__main__.Custom | None" reveal_type(y) # N: Revealed type is "builtins.int | None" else: reveal_type(x) # N: Revealed type is "__main__.Custom | None" reveal_type(y) # N: Revealed type is "builtins.int | None" [builtins fixtures/primitives.pyi] [case testNarrowingCustomEqualityUnion4] # flags: --strict-equality --warn-unreachable from __future__ import annotations class Custom: def __eq__(self, other: object) -> bool: return True def f(x: Custom | None, y: Custom): if x == y: # We unsoundly special case None and narrow x to Custom here reveal_type(x) # N: Revealed type is "__main__.Custom" else: reveal_type(x) # N: Revealed type is "__main__.Custom | None" [builtins fixtures/primitives.pyi] [case testNarrowingCustomEqualityUnion5] # flags: --strict-equality --warn-unreachable from __future__ import annotations from typing import Any class Custom1: def __eq__(self, other: object) -> bool: raise class Custom2: def __eq__(self, other: object) -> bool: raise def f(x: Custom1 | int, y: Custom2 | int): if x == y: reveal_type(x) # N: Revealed type is "__main__.Custom1 | builtins.int" reveal_type(y) # N: Revealed type is "__main__.Custom2 | builtins.int" else: reveal_type(x) # N: Revealed type is "__main__.Custom1 | builtins.int" reveal_type(y) # N: Revealed type is "__main__.Custom2 | builtins.int" [builtins fixtures/primitives.pyi] [case testNarrowingCustomEqualitySubclass] # flags: --strict-equality --warn-unreachable from __future__ import annotations from typing import Any class Custom: def __eq__(self, other: object) -> bool: raise class CustomSub(Custom): def __eq__(self, other: object) -> bool: raise def f(x: Custom, y: CustomSub): if x == y: reveal_type(x) # N: Revealed type is "__main__.Custom" reveal_type(y) # N: Revealed type is "__main__.CustomSub" else: reveal_type(x) # N: Revealed type is "__main__.Custom" reveal_type(y) # N: Revealed type is "__main__.CustomSub" [builtins fixtures/tuple.pyi] [case testNarrowingCustomEqualityGeneric] # flags: --strict-equality --warn-unreachable from __future__ import annotations from typing import Union class Custom: def __eq__(self, other: object) -> bool: raise class Default: ... def f1(x: list[Custom] | Default, y: list[int]): if x == y: # E: Non-overlapping equality check (left operand type: "list[Custom] | Default", right operand type: "list[int]") reveal_type(x) # N: Revealed type is "builtins.list[__main__.Custom]" reveal_type(y) # N: Revealed type is "builtins.list[builtins.int]" else: reveal_type(x) # N: Revealed type is "builtins.list[__main__.Custom] | __main__.Default" reveal_type(y) # N: Revealed type is "builtins.list[builtins.int]" f1([], []) def f2(x: list[Custom] | Default, y: list[int] | list[Default]): if x == y: # E: Non-overlapping equality check (left operand type: "list[Custom] | Default", right operand type: "list[int] | list[Default]") reveal_type(x) # N: Revealed type is "builtins.list[__main__.Custom]" reveal_type(y) # N: Revealed type is "builtins.list[builtins.int] | builtins.list[__main__.Default]" else: reveal_type(x) # N: Revealed type is "builtins.list[__main__.Custom] | __main__.Default" reveal_type(y) # N: Revealed type is "builtins.list[builtins.int] | builtins.list[__main__.Default]" listcustom_or_default = Union[list[Custom], Default] listint_or_default = Union[list[int], list[Default]] def f2_with_alias(x: listcustom_or_default, y: listint_or_default): if x == y: # E: Non-overlapping equality check (left operand type: "list[Custom] | Default", right operand type: "list[int] | list[Default]") reveal_type(x) # N: Revealed type is "builtins.list[__main__.Custom]" reveal_type(y) # N: Revealed type is "builtins.list[builtins.int] | builtins.list[__main__.Default]" else: reveal_type(x) # N: Revealed type is "builtins.list[__main__.Custom] | __main__.Default" reveal_type(y) # N: Revealed type is "builtins.list[builtins.int] | builtins.list[__main__.Default]" def f3(x: Custom | dict[str, str], y: dict[int, int]): if x == y: reveal_type(x) # N: Revealed type is "__main__.Custom | builtins.dict[builtins.str, builtins.str]" reveal_type(y) # N: Revealed type is "builtins.dict[builtins.int, builtins.int]" else: reveal_type(x) # N: Revealed type is "__main__.Custom | builtins.dict[builtins.str, builtins.str]" reveal_type(y) # N: Revealed type is "builtins.dict[builtins.int, builtins.int]" [builtins fixtures/primitives.pyi] [case testNarrowingRecursiveCallable] # flags: --strict-equality --warn-unreachable from __future__ import annotations from typing import Callable class A: ... class B: ... T = Callable[[A], "S"] S = Callable[[B], "T"] def f(x: S, y: T): if x == y: # E: Unsupported left operand type for == ("Callable[[B], T]") reveal_type(x) # E: Statement is unreachable reveal_type(y) else: reveal_type(x) # N: Revealed type is "def (__main__.B) -> def (__main__.A) -> ..." reveal_type(y) # N: Revealed type is "def (__main__.A) -> def (__main__.B) -> ..." [builtins fixtures/tuple.pyi] [case testNarrowingRecursiveUnion] # flags: --strict-equality --warn-unreachable from __future__ import annotations from typing import Union class A: ... class B: ... T = Union[A, "S"] S = Union[B, "T"] # E: Invalid recursive alias: a union item of itself def f(x: S, y: T): if x == y: reveal_type(x) # N: Revealed type is "Any" reveal_type(y) # N: Revealed type is "__main__.A | Any" [builtins fixtures/tuple.pyi] [case testNarrowingUnreachableCases] # flags: --strict-equality --warn-unreachable from typing import Literal, Union a: Literal[1] b: Literal[1, 2] c: Literal[2, 3] if a == b == c: reveal_type(a) # E: Statement is unreachable reveal_type(b) reveal_type(c) else: reveal_type(a) # N: Revealed type is "Literal[1]" reveal_type(b) # N: Revealed type is "Literal[1] | Literal[2]" reveal_type(c) # N: Revealed type is "Literal[2] | Literal[3]" if a == a == a: reveal_type(a) # N: Revealed type is "Literal[1]" else: reveal_type(a) # E: Statement is unreachable if a == a == b: reveal_type(a) # N: Revealed type is "Literal[1]" reveal_type(b) # N: Revealed type is "Literal[1]" else: reveal_type(a) # N: Revealed type is "Literal[1]" reveal_type(b) # N: Revealed type is "Literal[2]" # In this case, it's ok for 'b' to narrow down to Literal[1] in the else case # since that's the only way 'b == 2' can be false if b == 2: reveal_type(b) # N: Revealed type is "Literal[2]" else: reveal_type(b) # N: Revealed type is "Literal[1]" # But in this case, we can't conclude anything about the else case. This expression # could end up being either '2 == 2 == 3' or '1 == 2 == 2', which means we can't # conclude anything. if b == 2 == c: reveal_type(b) # N: Revealed type is "Literal[2]" reveal_type(c) # N: Revealed type is "Literal[2]" else: reveal_type(b) # N: Revealed type is "Literal[1] | Literal[2]" reveal_type(c) # N: Revealed type is "Literal[2] | Literal[3]" [builtins fixtures/primitives.pyi] [case testNarrowingUnreachableCases2] # flags: --strict-equality --warn-unreachable from typing import Literal, Union a: Literal[1, 2, 3, 4] b: Literal[1, 2, 3, 4] if a == b == 1: reveal_type(a) # N: Revealed type is "Literal[1]" reveal_type(b) # N: Revealed type is "Literal[1]" elif a == b == 2: reveal_type(a) # N: Revealed type is "Literal[2]" reveal_type(b) # N: Revealed type is "Literal[2]" elif a == b == 3: reveal_type(a) # N: Revealed type is "Literal[3]" reveal_type(b) # N: Revealed type is "Literal[3]" elif a == b == 4: reveal_type(a) # N: Revealed type is "Literal[4]" reveal_type(b) # N: Revealed type is "Literal[4]" else: # This branch is reachable if a == 1 and b == 2, for example. reveal_type(a) # N: Revealed type is "Literal[1] | Literal[2] | Literal[3] | Literal[4]" reveal_type(b) # N: Revealed type is "Literal[1] | Literal[2] | Literal[3] | Literal[4]" if a == a == 1: reveal_type(a) # N: Revealed type is "Literal[1]" elif a == a == 2: reveal_type(a) # N: Revealed type is "Literal[2]" elif a == a == 3: reveal_type(a) # N: Revealed type is "Literal[3]" elif a == a == 4: reveal_type(a) # N: Revealed type is "Literal[4]" else: # In contrast, this branch must be unreachable: we assume (maybe naively) # that 'a' won't be mutated in the middle of the expression. reveal_type(a) # E: Statement is unreachable reveal_type(b) [builtins fixtures/primitives.pyi] [case testNarrowingLiteralTruthiness] # flags: --strict-equality --warn-unreachable from typing import Literal, Union str_or_false: Union[Literal[False], str] if str_or_false: reveal_type(str_or_false) # N: Revealed type is "builtins.str" else: reveal_type(str_or_false) # N: Revealed type is "Literal[False] | Literal['']" true_or_false: Literal[True, False] if true_or_false: reveal_type(true_or_false) # N: Revealed type is "Literal[True]" else: reveal_type(true_or_false) # N: Revealed type is "Literal[False]" [builtins fixtures/primitives.pyi] [case testNarrowingFalseyToLiteral] # flags: --strict-equality --warn-unreachable from typing import Union a: str b: bytes c: int d: Union[str, bytes, int] if not a: reveal_type(a) # N: Revealed type is "Literal['']" if not b: reveal_type(b) # N: Revealed type is "Literal[b'']" if not c: reveal_type(c) # N: Revealed type is "Literal[0]" if not d: reveal_type(d) # N: Revealed type is "Literal[''] | Literal[b''] | Literal[0]" [case testNarrowingIsInstanceFinalSubclass] # flags: --strict-equality --warn-unreachable from typing import final class N: ... @final class F1: ... @final class F2: ... n: N f1: F1 if isinstance(f1, F1): reveal_type(f1) # N: Revealed type is "__main__.F1" else: reveal_type(f1) # E: Statement is unreachable if isinstance(n, F1): # E: Subclass of "N" and "F1" cannot exist: "F1" is final reveal_type(n) # E: Statement is unreachable else: reveal_type(n) # N: Revealed type is "__main__.N" if isinstance(f1, N): # E: Subclass of "F1" and "N" cannot exist: "F1" is final reveal_type(f1) # E: Statement is unreachable else: reveal_type(f1) # N: Revealed type is "__main__.F1" if isinstance(f1, F2): # E: Subclass of "F1" and "F2" cannot exist: "F1" is final \ # E: Subclass of "F1" and "F2" cannot exist: "F2" is final reveal_type(f1) # E: Statement is unreachable else: reveal_type(f1) # N: Revealed type is "__main__.F1" [builtins fixtures/isinstance.pyi] [case testNarrowingIsInstanceFinalSubclassWithUnions] # flags: --strict-equality --warn-unreachable from typing import final, Union class N: ... @final class F1: ... @final class F2: ... n_f1: Union[N, F1] n_f2: Union[N, F2] f1_f2: Union[F1, F2] if isinstance(n_f1, F1): reveal_type(n_f1) # N: Revealed type is "__main__.F1" else: reveal_type(n_f1) # N: Revealed type is "__main__.N" if isinstance(n_f2, F1): # E: Subclass of "N" and "F1" cannot exist: "F1" is final \ # E: Subclass of "F2" and "F1" cannot exist: "F2" is final \ # E: Subclass of "F2" and "F1" cannot exist: "F1" is final reveal_type(n_f2) # E: Statement is unreachable else: reveal_type(n_f2) # N: Revealed type is "__main__.N | __main__.F2" if isinstance(f1_f2, F1): reveal_type(f1_f2) # N: Revealed type is "__main__.F1" else: reveal_type(f1_f2) # N: Revealed type is "__main__.F2" [builtins fixtures/isinstance.pyi] [case testNarrowingIsSubclassFinalSubclassWithTypeVar] # flags: --strict-equality --warn-unreachable from typing import final, Type, TypeVar @final class A: ... @final class B: ... T = TypeVar("T", A, B) def f(cls: Type[T]) -> T: if issubclass(cls, A): reveal_type(cls) # N: Revealed type is "type[__main__.A]" x: bool if x: return A() else: return B() # E: Incompatible return value type (got "B", expected "A") assert False reveal_type(f(A)) # N: Revealed type is "__main__.A" reveal_type(f(B)) # N: Revealed type is "__main__.B" [builtins fixtures/isinstance.pyi] [case testNarrowingLiteralIdentityCheck] # flags: --strict-equality --warn-unreachable from typing import Literal, Union str_or_false: Union[Literal[False], str] if str_or_false is not False: reveal_type(str_or_false) # N: Revealed type is "builtins.str" else: reveal_type(str_or_false) # N: Revealed type is "Literal[False]" if str_or_false is False: reveal_type(str_or_false) # N: Revealed type is "Literal[False]" else: reveal_type(str_or_false) # N: Revealed type is "builtins.str" str_or_true: Union[Literal[True], str] if str_or_true is True: reveal_type(str_or_true) # N: Revealed type is "Literal[True]" else: reveal_type(str_or_true) # N: Revealed type is "builtins.str" if str_or_true is not True: reveal_type(str_or_true) # N: Revealed type is "builtins.str" else: reveal_type(str_or_true) # N: Revealed type is "Literal[True]" str_or_bool_literal: Union[Literal[False], Literal[True], str] if str_or_bool_literal is not True: reveal_type(str_or_bool_literal) # N: Revealed type is "Literal[False] | builtins.str" else: reveal_type(str_or_bool_literal) # N: Revealed type is "Literal[True]" if str_or_bool_literal is not True and str_or_bool_literal is not False: reveal_type(str_or_bool_literal) # N: Revealed type is "builtins.str" else: reveal_type(str_or_bool_literal) # N: Revealed type is "builtins.bool" [builtins fixtures/primitives.pyi] [case testNarrowingBooleanIdentityCheck] # flags: --strict-equality --warn-unreachable from typing import Literal, Optional bool_val: bool if bool_val is not False: reveal_type(bool_val) # N: Revealed type is "Literal[True]" else: reveal_type(bool_val) # N: Revealed type is "Literal[False]" opt_bool_val: Optional[bool] if opt_bool_val is not None: reveal_type(opt_bool_val) # N: Revealed type is "builtins.bool" if opt_bool_val is not False: reveal_type(opt_bool_val) # N: Revealed type is "Literal[True] | None" else: reveal_type(opt_bool_val) # N: Revealed type is "Literal[False]" [builtins fixtures/primitives.pyi] [case testNarrowingBooleanTruthiness] # flags: --strict-equality --warn-unreachable from typing import Literal, Optional bool_val: bool if bool_val: reveal_type(bool_val) # N: Revealed type is "Literal[True]" else: reveal_type(bool_val) # N: Revealed type is "Literal[False]" reveal_type(bool_val) # N: Revealed type is "builtins.bool" opt_bool_val: Optional[bool] if opt_bool_val: reveal_type(opt_bool_val) # N: Revealed type is "Literal[True]" else: reveal_type(opt_bool_val) # N: Revealed type is "Literal[False] | None" reveal_type(opt_bool_val) # N: Revealed type is "builtins.bool | None" [builtins fixtures/primitives.pyi] [case testNarrowingBooleanBoolOp] # flags: --strict-equality --warn-unreachable from typing import Literal, Optional bool_a: bool bool_b: bool if bool_a and bool_b: reveal_type(bool_a) # N: Revealed type is "Literal[True]" reveal_type(bool_b) # N: Revealed type is "Literal[True]" else: reveal_type(bool_a) # N: Revealed type is "builtins.bool" reveal_type(bool_b) # N: Revealed type is "builtins.bool" if not bool_a or bool_b: reveal_type(bool_a) # N: Revealed type is "builtins.bool" reveal_type(bool_b) # N: Revealed type is "builtins.bool" else: reveal_type(bool_a) # N: Revealed type is "Literal[True]" reveal_type(bool_b) # N: Revealed type is "Literal[False]" if True and bool_b: reveal_type(bool_b) # N: Revealed type is "Literal[True]" x = True and bool_b reveal_type(x) # N: Revealed type is "builtins.bool" [builtins fixtures/primitives.pyi] [case testNarrowingTypedDictUsingEnumLiteral] # flags: --strict-equality --warn-unreachable from typing import Literal, TypedDict, Union from enum import Enum class E(Enum): FOO = "a" BAR = "b" class Foo(TypedDict): tag: Literal[E.FOO] x: int class Bar(TypedDict): tag: Literal[E.BAR] y: int def f(d: Union[Foo, Bar]) -> None: assert d['tag'] == E.FOO d['x'] reveal_type(d) # N: Revealed type is "TypedDict('__main__.Foo', {'tag': Literal[__main__.E.FOO], 'x': builtins.int})" [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] [case testNarrowingUsingMetaclass] # flags: --strict-equality --warn-unreachable from typing import Type class M(type): pass class C: pass def f(t: Type[C]) -> None: if type(t) is M: reveal_type(t) # N: Revealed type is "type[__main__.C]" else: reveal_type(t) # N: Revealed type is "type[__main__.C]" if type(t) is not M: reveal_type(t) # N: Revealed type is "type[__main__.C]" else: reveal_type(t) # N: Revealed type is "type[__main__.C]" reveal_type(t) # N: Revealed type is "type[__main__.C]" [case testNarrowingUsingTypeVar] # flags: --strict-equality --warn-unreachable from typing import Type, TypeVar class A: pass class B(A): pass T = TypeVar("T", bound=A) def f(t: Type[T], a: A, b: B) -> None: if type(a) is t: reveal_type(a) # N: Revealed type is "T`-1" else: reveal_type(a) # N: Revealed type is "__main__.A" if type(b) is t: reveal_type(b) # N: Revealed type is "T`-1" else: reveal_type(b) # N: Revealed type is "__main__.B" [case testNarrowingNestedUnionOfTypedDicts] # flags: --strict-equality --warn-unreachable from typing import Literal, TypedDict, Union class A(TypedDict): tag: Literal["A"] a: int class B(TypedDict): tag: Literal["B"] b: int class C(TypedDict): tag: Literal["C"] c: int AB = Union[A, B] ABC = Union[AB, C] abc: ABC if abc["tag"] == "A": reveal_type(abc) # N: Revealed type is "TypedDict('__main__.A', {'tag': Literal['A'], 'a': builtins.int})" elif abc["tag"] == "C": reveal_type(abc) # N: Revealed type is "TypedDict('__main__.C', {'tag': Literal['C'], 'c': builtins.int})" else: reveal_type(abc) # N: Revealed type is "TypedDict('__main__.B', {'tag': Literal['B'], 'b': builtins.int})" [builtins fixtures/primitives.pyi] [typing fixtures/typing-typeddict.pyi] [case testNarrowingRuntimeCover] # flags: --strict-equality --warn-unreachable from typing import Dict, List, Union def unreachable(x: Union[str, List[str]]) -> None: if isinstance(x, str): reveal_type(x) # N: Revealed type is "builtins.str" elif isinstance(x, list): reveal_type(x) # N: Revealed type is "builtins.list[builtins.str]" else: reveal_type(x) # No output: this branch is unreachable # E: Statement is unreachable def all_parts_covered(x: Union[str, List[str], List[int], int]) -> None: if isinstance(x, str): reveal_type(x) # N: Revealed type is "builtins.str" elif isinstance(x, list): reveal_type(x) # N: Revealed type is "builtins.list[builtins.str] | builtins.list[builtins.int]" else: reveal_type(x) # N: Revealed type is "builtins.int" def two_type_vars(x: Union[str, Dict[str, int], Dict[bool, object], int]) -> None: if isinstance(x, str): reveal_type(x) # N: Revealed type is "builtins.str" elif isinstance(x, dict): reveal_type(x) # N: Revealed type is "builtins.dict[builtins.str, builtins.int] | builtins.dict[builtins.bool, builtins.object]" else: reveal_type(x) # N: Revealed type is "builtins.int" [builtins fixtures/dict.pyi] [case testNarrowingWithDef] # flags: --strict-equality --warn-unreachable from typing import Callable, Optional def g() -> None: foo: Optional[Callable[[], None]] = None if foo is None: def foo(): ... foo() [builtins fixtures/dict.pyi] [case testNarrowingOptionalEqualsNone] # flags: --strict-equality --warn-unreachable from typing import Optional class A: ... val: Optional[A] if val == None: reveal_type(val) # N: Revealed type is "None" else: reveal_type(val) # N: Revealed type is "__main__.A" if val != None: reveal_type(val) # N: Revealed type is "__main__.A" else: reveal_type(val) # N: Revealed type is "None" if val in (None,): reveal_type(val) # N: Revealed type is "None" else: reveal_type(val) # N: Revealed type is "__main__.A" if val not in (None,): reveal_type(val) # N: Revealed type is "__main__.A" else: reveal_type(val) # N: Revealed type is "None" [builtins fixtures/primitives.pyi] [case testNarrowingCustomEqualityOptionalEqualsNone] # flags: --strict-equality --warn-unreachable from __future__ import annotations class Custom: def __eq__(self, other) -> bool: ... def f(x: Custom | None): if x == None: reveal_type(x) # N: Revealed type is "__main__.Custom | None" else: reveal_type(x) # N: Revealed type is "__main__.Custom" if x != None: reveal_type(x) # N: Revealed type is "__main__.Custom" else: reveal_type(x) # N: Revealed type is "__main__.Custom | None" [builtins fixtures/primitives.pyi] [case testNarrowingWithTupleOfTypes] # flags: --strict-equality --warn-unreachable from typing import Tuple, Type class Base: ... class Impl1(Base): ... class Impl2(Base): ... impls: Tuple[Type[Base], ...] = (Impl1, Impl2) some: object if isinstance(some, impls): reveal_type(some) # N: Revealed type is "__main__.Base" else: reveal_type(some) # N: Revealed type is "builtins.object" raw: Tuple[type, ...] if isinstance(some, raw): reveal_type(some) # N: Revealed type is "builtins.object" else: reveal_type(some) # N: Revealed type is "builtins.object" [builtins fixtures/dict.pyi] [case testNarrowingWithTupleOfTypesPy310Plus] # flags: --strict-equality --warn-unreachable class Base: ... class Impl1(Base): ... class Impl2(Base): ... some: int | Base impls: tuple[type[Base], ...] = (Impl1, Impl2) if isinstance(some, impls): reveal_type(some) # N: Revealed type is "__main__.Base" else: reveal_type(some) # N: Revealed type is "builtins.int | __main__.Base" raw: tuple[type, ...] if isinstance(some, raw): reveal_type(some) # N: Revealed type is "builtins.int | __main__.Base" else: reveal_type(some) # N: Revealed type is "builtins.int | __main__.Base" [builtins fixtures/dict.pyi] [case testNarrowingWithAnyOps] # flags: --strict-equality --warn-unreachable from typing import Any class C: ... class D(C): ... tp: Any c: C if isinstance(c, tp) or isinstance(c, D): reveal_type(c) # N: Revealed type is "Any | __main__.D" else: reveal_type(c) # N: Revealed type is "__main__.C" reveal_type(c) # N: Revealed type is "__main__.C" c1: C if isinstance(c1, tp) and isinstance(c1, D): reveal_type(c1) # N: Revealed type is "__main__.D" else: reveal_type(c1) # N: Revealed type is "__main__.C" reveal_type(c1) # N: Revealed type is "__main__.C" c2: C if isinstance(c2, D) or isinstance(c2, tp): reveal_type(c2) # N: Revealed type is "__main__.D | Any" else: reveal_type(c2) # N: Revealed type is "__main__.C" reveal_type(c2) # N: Revealed type is "__main__.C" c3: C if isinstance(c3, D) and isinstance(c3, tp): reveal_type(c3) # N: Revealed type is "__main__.D" else: reveal_type(c3) # N: Revealed type is "__main__.C" reveal_type(c3) # N: Revealed type is "__main__.C" t: Any if isinstance(t, (list, tuple)) and isinstance(t, tuple): reveal_type(t) # N: Revealed type is "builtins.tuple[Any, ...]" else: reveal_type(t) # N: Revealed type is "Any" reveal_type(t) # N: Revealed type is "Any" [builtins fixtures/isinstancelist.pyi] [case testNarrowingAnyUnion] # flags: --strict-equality --warn-unreachable from __future__ import annotations from unknown import A, AA # E: Cannot find implementation or library stub for module named "unknown" \ # N: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports class B: ... def f(x: A | B) -> None: if isinstance(x, (AA, B)): reveal_type(x) # N: Revealed type is "Any | __main__.B" else: reveal_type(x) # N: Revealed type is "Any" [builtins fixtures/isinstancelist.pyi] [case testNarrowingLenItemAndLenCompare] # flags: --strict-equality --warn-unreachable from typing import Any x: Any if len(x) == x: reveal_type(x) # N: Revealed type is "Any" [builtins fixtures/len.pyi] [case testNarrowingLenTuple] # flags: --strict-equality --warn-unreachable from typing import Tuple, Union VarTuple = Union[Tuple[int, int], Tuple[int, int, int]] x: VarTuple a = b = c = 0 if len(x) == 3: a, b, c = x else: a, b = x if len(x) != 3: a, b = x else: a, b, c = x [builtins fixtures/len.pyi] [case testNarrowingLenHomogeneousTuple] # flags: --strict-equality --warn-unreachable from typing import Tuple x: Tuple[int, ...] if len(x) == 3: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int, builtins.int]" else: reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.int, ...]" if len(x) != 3: reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.int, ...]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int, builtins.int]" [builtins fixtures/len.pyi] [case testNarrowingLenTypeUnaffected] # flags: --strict-equality --warn-unreachable from typing import Union, List x: Union[str, List[int]] if len(x) == 3: reveal_type(x) # N: Revealed type is "builtins.str | builtins.list[builtins.int]" else: reveal_type(x) # N: Revealed type is "builtins.str | builtins.list[builtins.int]" [builtins fixtures/len.pyi] [case testNarrowingLenAnyListElseNotAffected] # flags: --strict-equality --warn-unreachable from typing import Any def f(self, value: Any) -> Any: if isinstance(value, list) and len(value) == 0: reveal_type(value) # N: Revealed type is "builtins.list[Any]" return value reveal_type(value) # N: Revealed type is "Any" return None [builtins fixtures/len.pyi] [case testNarrowingLenMultiple] # flags: --strict-equality --warn-unreachable from typing import Tuple, Union VarTuple = Union[Tuple[int, int], Tuple[int, int, int]] x: VarTuple y: VarTuple if len(x) == len(y) == 3: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int, builtins.int]" reveal_type(y) # N: Revealed type is "tuple[builtins.int, builtins.int, builtins.int]" [builtins fixtures/len.pyi] [case testNarrowingLenFinal] # flags: --strict-equality --warn-unreachable from typing import Final, Tuple, Union VarTuple = Union[Tuple[int, int], Tuple[int, int, int]] x: VarTuple fin: Final = 3 if len(x) == fin: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int, builtins.int]" [builtins fixtures/len.pyi] [case testNarrowingLenGreaterThan] # flags: --strict-equality --warn-unreachable from typing import Tuple, Union VarTuple = Union[Tuple[int], Tuple[int, int], Tuple[int, int, int]] x: VarTuple if len(x) > 1: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int] | tuple[builtins.int, builtins.int, builtins.int]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int]" if len(x) < 2: reveal_type(x) # N: Revealed type is "tuple[builtins.int]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int] | tuple[builtins.int, builtins.int, builtins.int]" if len(x) >= 2: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int] | tuple[builtins.int, builtins.int, builtins.int]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int]" if len(x) <= 2: reveal_type(x) # N: Revealed type is "tuple[builtins.int] | tuple[builtins.int, builtins.int]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int, builtins.int]" [builtins fixtures/len.pyi] [case testNarrowingLenBothSidesUnionTuples] # flags: --strict-equality --warn-unreachable from typing import Tuple, Union VarTuple = Union[ Tuple[int], Tuple[int, int], Tuple[int, int, int], Tuple[int, int, int, int], ] x: VarTuple if 2 <= len(x) <= 3: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int] | tuple[builtins.int, builtins.int, builtins.int]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int] | tuple[builtins.int, builtins.int, builtins.int, builtins.int]" [builtins fixtures/len.pyi] [case testNarrowingLenGreaterThanHomogeneousTupleShort] # flags: --enable-incomplete-feature=PreciseTupleTypes --strict-equality --warn-unreachable from typing import Tuple VarTuple = Tuple[int, ...] x: VarTuple if len(x) < 3: reveal_type(x) # N: Revealed type is "tuple[()] | tuple[builtins.int] | tuple[builtins.int, builtins.int]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int, builtins.int, Unpack[builtins.tuple[builtins.int, ...]]]" reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.int, ...]" [builtins fixtures/len.pyi] [case testNarrowingLenBiggerThanHomogeneousTupleLong] # flags: --enable-incomplete-feature=PreciseTupleTypes --strict-equality --warn-unreachable from typing import Tuple VarTuple = Tuple[int, ...] x: VarTuple if len(x) < 30: reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.int, ...]" else: reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.int, ...]" [builtins fixtures/len.pyi] [case testNarrowingLenBothSidesHomogeneousTuple] # flags: --enable-incomplete-feature=PreciseTupleTypes --strict-equality --warn-unreachable from typing import Tuple x: Tuple[int, ...] if 1 < len(x) < 4: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int] | tuple[builtins.int, builtins.int, builtins.int]" else: reveal_type(x) # N: Revealed type is "tuple[()] | tuple[builtins.int] | tuple[builtins.int, builtins.int, builtins.int, builtins.int, Unpack[builtins.tuple[builtins.int, ...]]]" reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.int, ...]" [builtins fixtures/len.pyi] [case testNarrowingLenUnionTupleUnreachable] # flags: --strict-equality --warn-unreachable from typing import Tuple, Union x: Union[Tuple[int, int], Tuple[int, int, int]] if len(x) >= 4: reveal_type(x) # E: Statement is unreachable else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int] | tuple[builtins.int, builtins.int, builtins.int]" if len(x) < 2: reveal_type(x) # E: Statement is unreachable else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int] | tuple[builtins.int, builtins.int, builtins.int]" [builtins fixtures/len.pyi] [case testNarrowingLenMixedTypes] # flags: --strict-equality --warn-unreachable from typing import Tuple, List, Union x: Union[Tuple[int, int], Tuple[int, int, int], List[int]] a = b = c = 0 if len(x) == 3: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int, builtins.int] | builtins.list[builtins.int]" a, b, c = x else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int] | builtins.list[builtins.int]" a, b = x if len(x) != 3: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int] | builtins.list[builtins.int]" a, b = x else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int, builtins.int] | builtins.list[builtins.int]" a, b, c = x [builtins fixtures/len.pyi] [case testNarrowingLenTypeVarTupleEquals] # flags: --strict-equality --warn-unreachable from typing import Tuple from typing_extensions import TypeVarTuple, Unpack Ts = TypeVarTuple("Ts") def foo(x: Tuple[int, Unpack[Ts], str]) -> None: if len(x) == 5: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[Ts`-1], builtins.str]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[Ts`-1], builtins.str]" if len(x) != 5: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[Ts`-1], builtins.str]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[Ts`-1], builtins.str]" [builtins fixtures/len.pyi] [case testNarrowingLenTypeVarTupleGreaterThan] # flags: --strict-equality --warn-unreachable from typing import Tuple from typing_extensions import TypeVarTuple, Unpack Ts = TypeVarTuple("Ts") def foo(x: Tuple[int, Unpack[Ts], str]) -> None: if len(x) > 5: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[Ts`-1], builtins.str]" reveal_type(x[5]) # N: Revealed type is "builtins.object" reveal_type(x[-6]) # N: Revealed type is "builtins.object" reveal_type(x[-1]) # N: Revealed type is "builtins.str" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[Ts`-1], builtins.str]" if len(x) < 5: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[Ts`-1], builtins.str]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[Ts`-1], builtins.str]" x[5] # E: Tuple index out of range \ # N: Variadic tuple can have length 5 x[-6] # E: Tuple index out of range \ # N: Variadic tuple can have length 5 x[2] # E: Tuple index out of range \ # N: Variadic tuple can have length 2 x[-3] # E: Tuple index out of range \ # N: Variadic tuple can have length 2 [builtins fixtures/len.pyi] [case testNarrowingLenTypeVarTupleUnreachable] # flags: --strict-equality --warn-unreachable from typing import Tuple from typing_extensions import TypeVarTuple, Unpack Ts = TypeVarTuple("Ts") def foo(x: Tuple[int, Unpack[Ts], str]) -> None: if len(x) == 1: reveal_type(x) # E: Statement is unreachable else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[Ts`-1], builtins.str]" if len(x) != 1: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[Ts`-1], builtins.str]" else: reveal_type(x) # E: Statement is unreachable def bar(x: Tuple[int, Unpack[Ts], str]) -> None: if len(x) >= 2: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[Ts`-1], builtins.str]" else: reveal_type(x) # E: Statement is unreachable if len(x) < 2: reveal_type(x) # E: Statement is unreachable else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[Ts`-1], builtins.str]" [builtins fixtures/len.pyi] [case testNarrowingLenVariadicTupleEquals] # flags: --strict-equality --warn-unreachable from typing import Tuple from typing_extensions import Unpack def foo(x: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None: if len(x) == 4: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.float, builtins.float, builtins.str]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" if len(x) != 4: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.float, builtins.float, builtins.str]" [builtins fixtures/len.pyi] [case testNarrowingLenVariadicTupleGreaterThan] # flags: --strict-equality --warn-unreachable from typing import Tuple from typing_extensions import Unpack def foo(x: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None: if len(x) > 3: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.float, builtins.float, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.str] | tuple[builtins.int, builtins.float, builtins.str]" reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" if len(x) < 3: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.str]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.float, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" [builtins fixtures/len.pyi] [case testNarrowingLenVariadicTupleUnreachable] # flags: --strict-equality --warn-unreachable from typing import Tuple from typing_extensions import Unpack def foo(x: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None: if len(x) == 1: reveal_type(x) # E: Statement is unreachable else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" if len(x) != 1: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" else: reveal_type(x) # E: Statement is unreachable def bar(x: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None: if len(x) >= 2: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" else: reveal_type(x) # E: Statement is unreachable if len(x) < 2: reveal_type(x) # E: Statement is unreachable else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]" [builtins fixtures/len.pyi] [case testNarrowingLenBareExpressionPrecise] # flags: --enable-incomplete-feature=PreciseTupleTypes --strict-equality --warn-unreachable from typing import Tuple x: Tuple[int, ...] assert x reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[builtins.int, ...]]]" [builtins fixtures/len.pyi] [case testNarrowingLenBareExpressionTypeVarTuple] # flags: --strict-equality --warn-unreachable from typing import Tuple from typing_extensions import TypeVarTuple, Unpack Ts = TypeVarTuple("Ts") def test(*xs: Unpack[Ts]) -> None: assert xs xs[0] # OK [builtins fixtures/len.pyi] [case testNarrowingLenBareExpressionWithNonePrecise] # flags: --enable-incomplete-feature=PreciseTupleTypes --strict-equality --warn-unreachable from typing import Tuple, Optional x: Optional[Tuple[int, ...]] if x: reveal_type(x) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[builtins.int, ...]]]" else: reveal_type(x) # N: Revealed type is "tuple[()] | None" [builtins fixtures/len.pyi] [case testNarrowingLenBareExpressionWithNoneImprecise] # flags: --strict-equality --warn-unreachable from typing import Tuple, Optional x: Optional[Tuple[int, ...]] if x: reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.int, ...]" else: reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.int, ...] | None" [builtins fixtures/len.pyi] [case testNarrowingLenMixWithAnyPrecise] # flags: --enable-incomplete-feature=PreciseTupleTypes --strict-equality --warn-unreachable from typing import Any x: Any if isinstance(x, (list, tuple)) and len(x) == 0: reveal_type(x) # N: Revealed type is "tuple[()] | builtins.list[Any]" else: reveal_type(x) # N: Revealed type is "Any" reveal_type(x) # N: Revealed type is "Any" x1: Any if isinstance(x1, (list, tuple)) and len(x1) > 1: reveal_type(x1) # N: Revealed type is "tuple[Any, Any, Unpack[builtins.tuple[Any, ...]]] | builtins.list[Any]" else: reveal_type(x1) # N: Revealed type is "Any" reveal_type(x1) # N: Revealed type is "Any" [builtins fixtures/len.pyi] [case testNarrowingLenMixWithAnyImprecise] # flags: --strict-equality --warn-unreachable from typing import Any x: Any if isinstance(x, (list, tuple)) and len(x) == 0: reveal_type(x) # N: Revealed type is "tuple[()] | builtins.list[Any]" else: reveal_type(x) # N: Revealed type is "Any" reveal_type(x) # N: Revealed type is "Any" x1: Any if isinstance(x1, (list, tuple)) and len(x1) > 1: reveal_type(x1) # N: Revealed type is "builtins.tuple[Any, ...] | builtins.list[Any]" else: reveal_type(x1) # N: Revealed type is "Any" reveal_type(x1) # N: Revealed type is "Any" [builtins fixtures/len.pyi] [case testNarrowingLenExplicitLiteralTypes] # flags: --strict-equality --warn-unreachable from typing import Literal, Tuple, Union VarTuple = Union[ Tuple[int], Tuple[int, int], Tuple[int, int, int], ] x: VarTuple supported: Literal[2] if len(x) == supported: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int] | tuple[builtins.int, builtins.int, builtins.int]" not_supported_yet: Literal[2, 3] if len(x) == not_supported_yet: reveal_type(x) # N: Revealed type is "tuple[builtins.int] | tuple[builtins.int, builtins.int] | tuple[builtins.int, builtins.int, builtins.int]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int] | tuple[builtins.int, builtins.int] | tuple[builtins.int, builtins.int, builtins.int]" [builtins fixtures/len.pyi] [case testNarrowingLenUnionOfVariadicTuples] # flags: --strict-equality --warn-unreachable from typing import Tuple, Union x: Union[Tuple[int, ...], Tuple[str, ...]] if len(x) == 2: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int] | tuple[builtins.str, builtins.str]" else: reveal_type(x) # N: Revealed type is "builtins.tuple[builtins.int, ...] | builtins.tuple[builtins.str, ...]" [builtins fixtures/len.pyi] [case testNarrowingLenUnionOfNamedTuples] # flags: --strict-equality --warn-unreachable from typing import NamedTuple, Union class Point2D(NamedTuple): x: int y: int class Point3D(NamedTuple): x: int y: int z: int x: Union[Point2D, Point3D] if len(x) == 2: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int, fallback=__main__.Point2D]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int, builtins.int, fallback=__main__.Point3D]" [builtins fixtures/len.pyi] [case testNarrowingLenTupleSubclass] # flags: --strict-equality --warn-unreachable from typing import Tuple class Ints(Tuple[int, ...]): size: int x: Ints if len(x) == 2: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int, fallback=__main__.Ints]" reveal_type(x.size) # N: Revealed type is "builtins.int" else: reveal_type(x) # N: Revealed type is "__main__.Ints" reveal_type(x.size) # N: Revealed type is "builtins.int" reveal_type(x) # N: Revealed type is "__main__.Ints" [builtins fixtures/len.pyi] [case testNarrowingLenTupleSubclassCustomNotAllowed] # flags: --strict-equality --warn-unreachable from typing import Tuple class Ints(Tuple[int, ...]): def __len__(self) -> int: return 0 x: Ints if len(x) > 2: reveal_type(x) # N: Revealed type is "__main__.Ints" else: reveal_type(x) # N: Revealed type is "__main__.Ints" [builtins fixtures/len.pyi] [case testNarrowingLenTupleSubclassPreciseNotAllowed] # flags: --enable-incomplete-feature=PreciseTupleTypes --strict-equality --warn-unreachable from typing import Tuple class Ints(Tuple[int, ...]): size: int x: Ints if len(x) > 2: reveal_type(x) # N: Revealed type is "__main__.Ints" else: reveal_type(x) # N: Revealed type is "__main__.Ints" [builtins fixtures/len.pyi] [case testNarrowingLenUnknownLen] # flags: --strict-equality --warn-unreachable from typing import Any, Tuple, Union x: Union[Tuple[int, int], Tuple[int, int, int]] n: int if len(x) == n: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int] | tuple[builtins.int, builtins.int, builtins.int]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int] | tuple[builtins.int, builtins.int, builtins.int]" a: Any if len(x) == a: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int] | tuple[builtins.int, builtins.int, builtins.int]" else: reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int] | tuple[builtins.int, builtins.int, builtins.int]" [builtins fixtures/len.pyi] [case testNarrowingLenUnionWithUnreachable] # flags: --strict-equality --warn-unreachable from typing import Union, Sequence def f(x: Union[int, Sequence[int]]) -> None: if ( isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], int) and isinstance(x[1], int) ): reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int]" [builtins fixtures/len.pyi] [case testNarrowingIsSubclassNoneType1] # flags: --strict-equality --warn-unreachable from typing import Type, Union def f(cls: Type[Union[None, int]]) -> None: if issubclass(cls, int): reveal_type(cls) # N: Revealed type is "type[builtins.int]" else: reveal_type(cls) # N: Revealed type is "type[None]" [builtins fixtures/isinstance.pyi] [case testNarrowingIsSubclassNoneType2] # flags: --strict-equality --warn-unreachable from typing import Type, Union def f(cls: Type[Union[None, int]]) -> None: if issubclass(cls, type(None)): reveal_type(cls) # N: Revealed type is "type[None]" else: reveal_type(cls) # N: Revealed type is "type[builtins.int]" [builtins fixtures/isinstance.pyi] [case testNarrowingIsSubclassNoneType3] # flags: --strict-equality --warn-unreachable from typing import Type, Union NoneType_ = type(None) def f(cls: Type[Union[None, int]]) -> None: if issubclass(cls, NoneType_): reveal_type(cls) # N: Revealed type is "type[None]" else: reveal_type(cls) # N: Revealed type is "type[builtins.int]" [builtins fixtures/isinstance.pyi] [case testNarrowingIsSubclassNoneType4] # flags: --strict-equality --warn-unreachable from types import NoneType from typing import Type, Union def f(cls: Type[Union[None, int]]) -> None: if issubclass(cls, NoneType): reveal_type(cls) # N: Revealed type is "type[None]" else: reveal_type(cls) # N: Revealed type is "type[builtins.int]" [builtins fixtures/isinstance.pyi] [case testNarrowingIsInstanceNoIntersectionWithFinalTypeAndNoneType] # flags: --warn-unreachable --strict-equality from types import NoneType from typing import final class X: ... class Y: ... @final class Z: ... x: X if isinstance(x, (Y, Z)): reveal_type(x) # N: Revealed type is "__main__." if isinstance(x, (Y, NoneType)): reveal_type(x) # N: Revealed type is "__main__." if isinstance(x, (Y, Z, NoneType)): reveal_type(x) # N: Revealed type is "__main__." if isinstance(x, (Z, NoneType)): # E: Subclass of "X" and "Z" cannot exist: "Z" is final \ # E: Subclass of "X" and "NoneType" cannot exist: "NoneType" is final reveal_type(x) # E: Statement is unreachable [builtins fixtures/isinstance.pyi] [case testTypeNarrowingReachableNegative] # flags: --strict-equality --warn-unreachable from typing import Literal x: Literal[-1] if x == -1: assert True [typing fixtures/typing-medium.pyi] [builtins fixtures/ops.pyi] [case testTypeNarrowingReachableNegativeUnion] # flags: --strict-equality --warn-unreachable from typing import Literal x: Literal[-1, 1] if x == -1: reveal_type(x) # N: Revealed type is "Literal[-1]" else: reveal_type(x) # N: Revealed type is "Literal[1]" [typing fixtures/typing-medium.pyi] [builtins fixtures/ops.pyi] [case testNarrowingWithIntEnum] # flags: --strict-equality --warn-unreachable from __future__ import annotations from typing import Any from enum import IntEnum class IE(IntEnum): X = 1 Y = 2 def f1(x: int) -> None: if x == IE.X: reveal_type(x) # N: Revealed type is "builtins.int" else: reveal_type(x) # N: Revealed type is "builtins.int" if x != IE.X: reveal_type(x) # N: Revealed type is "builtins.int" else: reveal_type(x) # N: Revealed type is "builtins.int" def f2(x: IE) -> None: if x == 1: reveal_type(x) # N: Revealed type is "__main__.IE" else: reveal_type(x) # N: Revealed type is "__main__.IE" def f3(x: object) -> None: if x == IE.X: reveal_type(x) # N: Revealed type is "builtins.object" else: reveal_type(x) # N: Revealed type is "builtins.object" def f4(x: int | Any) -> None: if x == IE.X: reveal_type(x) # N: Revealed type is "Literal[__main__.IE.X] | Any" else: reveal_type(x) # N: Revealed type is "builtins.int | Any" def f5(x: int) -> None: if x is IE.X: reveal_type(x) # N: Revealed type is "Literal[__main__.IE.X]" else: reveal_type(x) # N: Revealed type is "builtins.int" if x is not IE.X: reveal_type(x) # N: Revealed type is "builtins.int" else: reveal_type(x) # N: Revealed type is "Literal[__main__.IE.X]" def f6(x: IE) -> None: if x == IE.X: reveal_type(x) # N: Revealed type is "Literal[__main__.IE.X]" else: reveal_type(x) # N: Revealed type is "Literal[__main__.IE.Y]" [builtins fixtures/primitives.pyi] [case testNarrowingWithIntEnum2] # flags: --strict-equality --warn-unreachable from __future__ import annotations from typing import Any from enum import IntEnum, Enum class MyDecimal: ... class IE(IntEnum): X = 1 Y = 2 class IE2(IntEnum): X = 1 Y = 2 class E(Enum): X = 1 Y = 2 def f1(x: IE | MyDecimal) -> None: if x == IE.X: reveal_type(x) # N: Revealed type is "__main__.IE | __main__.MyDecimal" else: reveal_type(x) # N: Revealed type is "__main__.IE | __main__.MyDecimal" def f2(x: E | bytes) -> None: if x == E.X: reveal_type(x) # N: Revealed type is "Literal[__main__.E.X]" else: reveal_type(x) # N: Revealed type is "Literal[__main__.E.Y] | builtins.bytes" def f3(x: IE | IE2) -> None: if x == IE.X: reveal_type(x) # N: Revealed type is "__main__.IE | __main__.IE2" else: reveal_type(x) # N: Revealed type is "__main__.IE | __main__.IE2" def f4(x: IE | E) -> None: if x == IE.X: reveal_type(x) # N: Revealed type is "Literal[__main__.IE.X]" elif x == E.X: reveal_type(x) # N: Revealed type is "Literal[__main__.E.X]" else: reveal_type(x) # N: Revealed type is "Literal[__main__.IE.Y] | Literal[__main__.E.Y]" def f5(x: E | str | int) -> None: if x == E.X: reveal_type(x) # N: Revealed type is "Literal[__main__.E.X]" else: reveal_type(x) # N: Revealed type is "Literal[__main__.E.Y] | builtins.str | builtins.int" def f6(x: IE | Any) -> None: if x == IE.X: reveal_type(x) # N: Revealed type is "Literal[__main__.IE.X] | Any" else: reveal_type(x) # N: Revealed type is "Literal[__main__.IE.Y] | Any" def f7(x: IE | None) -> None: if x == IE.X: reveal_type(x) # N: Revealed type is "Literal[__main__.IE.X]" else: reveal_type(x) # N: Revealed type is "Literal[__main__.IE.Y] | None" def f8(x: IE | None) -> None: if x is None: reveal_type(x) # N: Revealed type is "None" elif x == IE.X: reveal_type(x) # N: Revealed type is "Literal[__main__.IE.X]" else: reveal_type(x) # N: Revealed type is "Literal[__main__.IE.Y]" [builtins fixtures/primitives.pyi] [case testNarrowingWithStrEnum] # flags: --strict-equality --warn-unreachable # mypy: strict-equality from enum import StrEnum class SE(StrEnum): A = 'a' B = 'b' def f1(x: str) -> None: if x == SE.A: reveal_type(x) # N: Revealed type is "builtins.str" else: reveal_type(x) # N: Revealed type is "builtins.str" def f2(x: SE) -> None: if x == 'a': reveal_type(x) # N: Revealed type is "__main__.SE" else: reveal_type(x) # N: Revealed type is "__main__.SE" def f3(x: object) -> None: if x == SE.A: reveal_type(x) # N: Revealed type is "builtins.object" else: reveal_type(x) # N: Revealed type is "builtins.object" def f4(x: SE) -> None: if x == SE.A: reveal_type(x) # N: Revealed type is "Literal[__main__.SE.A]" else: reveal_type(x) # N: Revealed type is "Literal[__main__.SE.B]" [builtins fixtures/primitives.pyi] [case testNarrowingWithEnumStrSubclass] # flags: --strict-equality --warn-unreachable from enum import Enum class ParameterLocation(str, Enum): QUERY = "query" HEADER = "header" PATH = "path" def foo(location: ParameterLocation): if location == "path": reveal_type(location) # N: Revealed type is "__main__.ParameterLocation" else: reveal_type(location) # N: Revealed type is "__main__.ParameterLocation" if location == ParameterLocation.PATH: reveal_type(location) # N: Revealed type is "Literal[__main__.ParameterLocation.PATH]" else: reveal_type(location) # N: Revealed type is "Literal[__main__.ParameterLocation.QUERY] | Literal[__main__.ParameterLocation.HEADER]" [builtins fixtures/primitives.pyi] [case testConsistentNarrowingEqAndIn] # flags: --strict-equality --warn-unreachable # https://github.com/python/mypy/issues/17864 def f(x: str | int) -> None: if x == "x": reveal_type(x) # N: Revealed type is "builtins.str" y = x if x in ["x"]: reveal_type(x) # N: Revealed type is "builtins.str" y = x z = x z = y [builtins fixtures/primitives.pyi] [case testConsistentNarrowingEqAndInWithCustomEq] # flags: --strict-equality --warn-unreachable # https://github.com/python/mypy/issues/17864 class C: def __init__(self, x: int) -> None: self.x = x def __eq__(self, other: object) -> bool: raise # Example implementation: # if isinstance(other, C) and other.x == self.x: # return True # return NotImplemented class D(C): pass def f1(x: C) -> None: if x in [D(5)]: reveal_type(x) # D # N: Revealed type is "__main__.C" f1(C(5)) def f2(x: C) -> None: if x == D(5): reveal_type(x) # D # N: Revealed type is "__main__.C" f2(C(5)) [builtins fixtures/primitives.pyi] [case testNarrowingTypeVarNone] # flags: --strict-equality --warn-unreachable # https://github.com/python/mypy/issues/18126 from typing import TypeVar T = TypeVar("T") def fn_if(arg: T) -> None: if arg is None: return None return None def fn_while(arg: T) -> None: while arg is None: return None return None [builtins fixtures/primitives.pyi] -- Parallel mode does not support --no-local-partial-types [case testRefinePartialTypeWithinLoop_no_parallel] # flags: --no-local-partial-types --strict-equality --warn-unreachable x = None for _ in range(2): if x is not None: reveal_type(x) # N: Revealed type is "builtins.int" x = 1 reveal_type(x) # N: Revealed type is "builtins.int | None" def f() -> bool: ... y = None while f(): reveal_type(y) # N: Revealed type is "None | builtins.int" y = 1 reveal_type(y) # N: Revealed type is "builtins.int | None" z = [] # E: Need type annotation for "z" (hint: "z: list[] = ...") def g() -> None: for i in range(2): while f(): if z: z[0] + "v" # E: Unsupported operand types for + ("int" and "str") z.append(1) class A: def g(self) -> None: z = [] # E: Need type annotation for "z" (hint: "z: list[] = ...") for i in range(2): while f(): if z: z[0] + "v" # E: Unsupported operand types for + ("int" and "str") z.append(1) [builtins fixtures/primitives.pyi] [case testPersistentUnreachableLinesNestedInInpersistentUnreachableLines] # flags: --warn-unreachable --python-version 3.11 --strict-equality x = None y = None while True: if x is not None: if y is not None: reveal_type(y) # E: Statement is unreachable x = 1 [builtins fixtures/bool.pyi] [case testAvoidFalseRedundantCastInLoops] # flags: --warn-redundant-casts --strict-equality --warn-unreachable from typing import Callable, cast, Union ProcessorReturnValue = Union[str, int] Processor = Callable[[str], ProcessorReturnValue] def main_cast(p: Processor) -> None: ed: ProcessorReturnValue ed = cast(str, ...) while True: ed = p(cast(str, ed)) def main_no_cast(p: Processor) -> None: ed: ProcessorReturnValue ed = cast(str, ...) while True: ed = p(ed) # E: Argument 1 has incompatible type "str | int"; expected "str" [builtins fixtures/bool.pyi] [case testAvoidFalseUnreachableInLoop1] # flags: --warn-unreachable --python-version 3.11 --strict-equality def f() -> int | None: ... def b() -> bool: ... x: int | None x = 1 while x is not None or b(): x = f() [builtins fixtures/bool.pyi] [case testAvoidFalseUnreachableInLoop2] # flags: --warn-unreachable --python-version 3.11 --strict-equality y = None while y is None: if y is None: y = [] y.append(1) [builtins fixtures/list.pyi] [case testAvoidFalseUnreachableInLoop3] # flags: --warn-unreachable --python-version 3.11 --strict-equality xs: list[int | None] y = None for x in xs: if x is not None: if y is None: y = {} # E: Need type annotation for "y" (hint: "y: dict[, ] = ...") [builtins fixtures/list.pyi] [case testAvoidFalseRedundantExprInLoop] # flags: --enable-error-code redundant-expr --python-version 3.11 --strict-equality --warn-unreachable def f() -> int | None: ... def b() -> bool: ... x: int | None x = 1 while x is not None and b(): x = f() [builtins fixtures/primitives.pyi] [case testAvoidFalseNonOverlappingEqualityCheckInLoop1] # flags: --allow-redefinition --strict-equality --warn-unreachable def f(x: int) -> None: while True: if x == str(): break x = str() if x == int(): # E: Non-overlapping equality check (left operand type: "str", right operand type: "int") break # E: Statement is unreachable [builtins fixtures/primitives.pyi] [case testAvoidFalseNonOverlappingEqualityCheckInLoop2] # flags: --allow-redefinition --strict-equality --warn-unreachable class A: ... class B: ... class C: ... def f(x: A) -> None: while True: if x == C(): # E: Non-overlapping equality check (left operand type: "A | B", right operand type: "C") break # E: Statement is unreachable x = B() [builtins fixtures/primitives.pyi] [case testAvoidFalseNonOverlappingEqualityCheckInLoop3] # flags: --strict-equality --warn-unreachable for y in [1.0]: if y is not None or y != "None": # E: Right operand of "or" is never evaluated ... [builtins fixtures/primitives.pyi] [case testNarrowPromotionsInsideUnions1] # flags: --strict-equality --warn-unreachable from typing import Union x: Union[str, float, None] y: Union[int, str] x = y reveal_type(x) # N: Revealed type is "builtins.str | builtins.int" z: Union[complex, str] z = x reveal_type(z) # N: Revealed type is "builtins.int | builtins.str" [builtins fixtures/primitives.pyi] [case testNarrowPromotionsInsideUnions2] # flags: --strict-equality --warn-unreachable from typing import Optional def b() -> bool: ... def i() -> int: ... x: Optional[float] while b(): x = None while b(): reveal_type(x) # N: Revealed type is "None | builtins.int" if x is None or b(): x = i() reveal_type(x) # N: Revealed type is "builtins.int" [builtins fixtures/bool.pyi] [case testAvoidFalseUnreachableInFinally] # flags: --allow-redefinition --strict-equality --warn-unreachable def f() -> None: try: x = 1 if int(): x = "" return if int(): x = None return finally: reveal_type(x) # N: Revealed type is "builtins.int | builtins.str | None" if isinstance(x, str): reveal_type(x) # N: Revealed type is "builtins.str" reveal_type(x) # N: Revealed type is "builtins.int | builtins.str | None" [builtins fixtures/isinstancelist.pyi] [case testNarrowingTypeVarMultiple] # flags: --strict-equality --warn-unreachable from typing import TypeVar class A: ... class B: ... T = TypeVar("T") def foo(x: T) -> T: if isinstance(x, A): pass elif isinstance(x, B): pass else: raise reveal_type(x) # N: Revealed type is "T`-1" return x [builtins fixtures/isinstance.pyi] [case testDoNotNarrowToNever] # flags: --strict-equality --warn-unreachable def any(): return 1 def f() -> None: x = "a" x = any() assert isinstance(x, int) reveal_type(x) # N: Revealed type is "builtins.int" [builtins fixtures/isinstance.pyi] [case testNarrowTypeVarBoundType] # flags: --strict-equality --warn-unreachable from typing import Type, TypeVar class A: ... class B(A): other: int T = TypeVar("T", bound=A) def test(cls: Type[T]) -> T: if issubclass(cls, B): reveal_type(cls) # N: Revealed type is "type[T`-1]" reveal_type(cls().other) # N: Revealed type is "builtins.int" return cls() return cls() [builtins fixtures/isinstance.pyi] [case testNarrowTypeVarBoundUnion] # flags: --strict-equality --warn-unreachable from typing import TypeVar class A: x: int class B: x: str T = TypeVar("T") def test(x: T) -> T: if not isinstance(x, (A, B)): return x reveal_type(x) # N: Revealed type is "T`-1" reveal_type(x.x) # N: Revealed type is "builtins.int | builtins.str" if isinstance(x, A): reveal_type(x) # N: Revealed type is "T`-1" reveal_type(x.x) # N: Revealed type is "builtins.int" return x reveal_type(x) # N: Revealed type is "T`-1" reveal_type(x.x) # N: Revealed type is "builtins.str" return x [builtins fixtures/isinstance.pyi] [case testIsinstanceNarrowingWithSelfTypes] # flags: --strict-equality --warn-unreachable from typing import Generic, TypeVar, overload T = TypeVar("T") class A(Generic[T]): def __init__(self: A[int]) -> None: pass def check_a(obj: "A[T] | str") -> None: reveal_type(obj) # N: Revealed type is "__main__.A[T`-1] | builtins.str" if isinstance(obj, A): reveal_type(obj) # N: Revealed type is "__main__.A[T`-1]" else: reveal_type(obj) # N: Revealed type is "builtins.str" class B(Generic[T]): @overload def __init__(self, x: T) -> None: ... @overload def __init__(self: B[int]) -> None: ... def __init__(self, x: "T | None" = None) -> None: pass def check_b(obj: "B[T] | str") -> None: reveal_type(obj) # N: Revealed type is "__main__.B[T`-1] | builtins.str" if isinstance(obj, B): reveal_type(obj) # N: Revealed type is "__main__.B[T`-1]" else: reveal_type(obj) # N: Revealed type is "builtins.str" class C(Generic[T]): @overload def __init__(self: C[int]) -> None: ... @overload def __init__(self, x: T) -> None: ... def __init__(self, x: "T | None" = None) -> None: pass def check_c(obj: "C[T] | str") -> None: reveal_type(obj) # N: Revealed type is "__main__.C[T`-1] | builtins.str" if isinstance(obj, C): reveal_type(obj) # N: Revealed type is "__main__.C[T`-1]" else: reveal_type(obj) # N: Revealed type is "builtins.str" class D(tuple[T], Generic[T]): ... def check_d(arg: D[T]) -> None: if not isinstance(arg, D): return # E: Statement is unreachable reveal_type(arg) # N: Revealed type is "tuple[T`-1, fallback=__main__.D[Any]]" [builtins fixtures/tuple.pyi] [case testNarrowingUnionMixins] # flags: --strict-equality --warn-unreachable class Base: ... class FooMixin: def foo(self) -> None: ... class BarMixin: def bar(self) -> None: ... def baz(item: Base) -> None: if not isinstance(item, (FooMixin, BarMixin)): raise reveal_type(item) # N: Revealed type is "__main__. | __main__." if isinstance(item, FooMixin): reveal_type(item) # N: Revealed type is "__main__." item.foo() else: reveal_type(item) # N: Revealed type is "__main__." item.bar() [builtins fixtures/isinstance.pyi] [case testCustomSetterNarrowingReWidened] # flags: --strict-equality --warn-unreachable class B: ... class C(B): ... class C1(B): ... class D(C): ... class Test: @property def foo(self) -> C: ... @foo.setter def foo(self, val: B) -> None: ... t: Test t.foo = D() reveal_type(t.foo) # N: Revealed type is "__main__.D" t.foo = C1() reveal_type(t.foo) # N: Revealed type is "__main__.C" [builtins fixtures/property.pyi] [case testNarrowingNotImplemented] # flags: --warn-unreachable from __future__ import annotations import types def foo(x: types.NotImplementedType | str): if x is not NotImplemented: reveal_type(x) # N: Revealed type is "builtins.str" from typing_extensions import Self class X: def __divmod__(self, other: Self | int) -> tuple[Self, Self]: ... def __floordiv__(self, other: Self | int) -> Self: qr = self.__divmod__(other) if qr is NotImplemented: return NotImplemented return qr[0] [builtins fixtures/notimplemented.pyi] [case testNarrowingBooleans] # flags: --warn-return-any --strict-equality --warn-unreachable from typing import Any def foo(x: dict[str, Any]) -> bool: if x.get("event") is False: return False if x.get("event") is True: return True raise [builtins fixtures/dict.pyi] [case testNarrowingTypeObjects] # flags: --strict-equality --warn-unreachable from __future__ import annotations from typing import Callable, Any, TypeVar, Generic, Protocol _T_co = TypeVar('_T_co', covariant=True) class Boxxy(Protocol[_T_co]): def get_box(self) -> _T_co: ... class TupleLike(Generic[_T_co]): def __init__(self, iterable: Boxxy[_T_co], /) -> None: raise class Box1(Generic[_T_co]): def __init__(self, content: _T_co, /) -> None: ... def get_box(self) -> _T_co: raise class Box2(Generic[_T_co]): def __init__(self, content: _T_co, /) -> None: ... def get_box(self) -> _T_co: raise def get_type(setting_name: str) -> Callable[[Box1], Any] | type[Any]: raise def main(key: str): existing_value_type = get_type(key) if existing_value_type is TupleLike: reveal_type(TupleLike) # N: Revealed type is "def [_T_co] (__main__.Boxxy[_T_co`1]) -> __main__.TupleLike[_T_co`1]" reveal_type(TupleLike(Box2("str"))) # N: Revealed type is "__main__.TupleLike[builtins.str]" reveal_type(existing_value_type) # N: Revealed type is "(def [_T_co] (__main__.Boxxy[_T_co`1]) -> __main__.TupleLike[_T_co`1]) | type[Any]" reveal_type(existing_value_type(Box2("str"))) # N: Revealed type is "__main__.TupleLike[builtins.str] | Any" [builtins fixtures/tuple.pyi] [case testNarrowingCollections] # flags: --strict-equality --warn-unreachable from typing import cast class X: def __init__(self) -> None: self.x: dict[str, str] = {} self.y: list[str] = [] def xxx(self) -> None: if self.x == {}: reveal_type(self.x) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]" self.x["asdf"] if self.x == dict(): reveal_type(self.x) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]" self.x["asdf"] if self.x == cast(dict[int, int], {}): # E: Non-overlapping equality check (left operand type: "dict[str, str]", right operand type: "dict[int, int]") reveal_type(self.x) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]" self.x["asdf"] def yyy(self) -> None: if self.y == []: reveal_type(self.y) # N: Revealed type is "builtins.list[builtins.str]" self.y[0].does_not_exist # E: "str" has no attribute "does_not_exist" if self.y == list(): reveal_type(self.y) # N: Revealed type is "builtins.list[builtins.str]" self.y[0].does_not_exist # E: "str" has no attribute "does_not_exist" if self.y == cast(list[int], []): # E: Non-overlapping equality check (left operand type: "list[str]", right operand type: "list[int]") reveal_type(self.y) # N: Revealed type is "builtins.list[builtins.str]" self.y[0].does_not_exist # E: "str" has no attribute "does_not_exist" [builtins fixtures/dict.pyi] [case testTypeNarrowingStringInLiteralContainer] # flags: --strict-equality --warn-unreachable from typing import Literal def narrow_tuple(x: str, t: tuple[Literal['a', 'b']]): if x in t: reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" else: reveal_type(x) # N: Revealed type is "builtins.str" if x not in t: reveal_type(x) # N: Revealed type is "builtins.str" else: reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" def narrow_homo_tuple(x: str, t: tuple[Literal['a', 'b'], ...]): if x in t: reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" else: reveal_type(x) # N: Revealed type is "builtins.str" def narrow_list(x: str, t: list[Literal['a', 'b']]): if x in t: reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" else: reveal_type(x) # N: Revealed type is "builtins.str" def narrow_set(x: str, t: set[Literal['a', 'b']]): if x in t: reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" else: reveal_type(x) # N: Revealed type is "builtins.str" [builtins fixtures/primitives.pyi] [case testNarrowingLiteralInLiteralContainer] # flags: --strict-equality --warn-unreachable from typing import Literal def narrow_tuple(x: Literal['c'], overlap: list[Literal['b', 'c']], no_overlap: list[Literal['a', 'b']]): if x in overlap: reveal_type(x) # N: Revealed type is "Literal['c']" else: reveal_type(x) # N: Revealed type is "Literal['c']" if x in no_overlap: reveal_type(x) # E: Statement is unreachable else: reveal_type(x) # N: Revealed type is "Literal['c']" [builtins fixtures/tuple.pyi] [case testTypeNarrowingUnionInContainer] # flags: --strict-equality --warn-unreachable from typing import Union, Literal def f1(x: Union[str, float], t1: list[Literal['a', 'b']], t2: list[str]): if x in t1: reveal_type(x) # N: Revealed type is "Literal['a'] | Literal['b']" else: reveal_type(x) # N: Revealed type is "builtins.str | builtins.float" if x in t2: reveal_type(x) # N: Revealed type is "builtins.str" else: reveal_type(x) # N: Revealed type is "builtins.str | builtins.float" [builtins fixtures/primitives.pyi] [case testNarrowAnyWithEqualityOrContainment] # flags: --strict-equality --warn-unreachable # https://github.com/python/mypy/issues/17841 from typing import Any def f1(x: Any) -> None: if x is not None and x not in ["x"]: return reveal_type(x) # N: Revealed type is "Any" def f2(x: Any) -> None: if x is not None and x != "x": return reveal_type(x) # N: Revealed type is "Any" [builtins fixtures/tuple.pyi] [case testNarrowChainedContainment] # flags: --strict-equality --warn-unreachable def bad_compare_has_key(has_key: bool, key: str, s: tuple[str, ...]) -> None: if has_key == key in s: # E: Non-overlapping equality check (left operand type: "bool", right operand type: "str") reveal_type(has_key) # E: Statement is unreachable reveal_type(key) def bad_but_should_pass(has_key: bool, key: bool, s: tuple[bool, ...]) -> None: if has_key == key in s: reveal_type(has_key) # N: Revealed type is "builtins.bool" reveal_type(key) # N: Revealed type is "builtins.bool" [builtins fixtures/primitives.pyi] [case testNarrowChainedComparisonMeetAndForwardPropagation] # flags: --strict-equality --warn-unreachable from __future__ import annotations from typing import Any def f1(a: str | None, b: str | None) -> None: if None is not a == b: reveal_type(a) # N: Revealed type is "builtins.str" reveal_type(b) # N: Revealed type is "builtins.str" if (None is not a) and (a == b): reveal_type(a) # N: Revealed type is "builtins.str" reveal_type(b) # N: Revealed type is "builtins.str" def f2(a: Any | None, b: str | None) -> None: if None is not a == b: reveal_type(a) # N: Revealed type is "Any" reveal_type(b) # N: Revealed type is "builtins.str | None" if (None is not a) and (a == b): reveal_type(a) # N: Revealed type is "Any" reveal_type(b) # N: Revealed type is "builtins.str | None" def f3(a: str | None, b: Any | None) -> None: if None is not a == b: reveal_type(a) # N: Revealed type is "builtins.str" reveal_type(b) # N: Revealed type is "Any | builtins.str" if (None is not a) and (a == b): reveal_type(a) # N: Revealed type is "builtins.str" reveal_type(b) # N: Revealed type is "Any | builtins.str" def f4(a: str | None, b: str | None, c: str | None) -> None: if None is not a == b == c: reveal_type(a) # N: Revealed type is "builtins.str" reveal_type(b) # N: Revealed type is "builtins.str" reveal_type(c) # N: Revealed type is "builtins.str" if (None is not a) and (a == b) and (b == c): reveal_type(a) # N: Revealed type is "builtins.str" reveal_type(b) # N: Revealed type is "builtins.str" reveal_type(c) # N: Revealed type is "builtins.str" def f5(pair: tuple[None, int] | tuple[str, str], other: str | None) -> None: if None is not pair[0] == other: reveal_type(pair[0]) # N: Revealed type is "builtins.str" reveal_type(pair) # N: Revealed type is "tuple[builtins.str, builtins.str]" reveal_type(other) # N: Revealed type is "builtins.str" if (None is not pair[0]) and (pair[0] == other): reveal_type(pair[0]) # N: Revealed type is "builtins.str" reveal_type(pair) # N: Revealed type is "tuple[builtins.str, builtins.str]" reveal_type(other) # N: Revealed type is "builtins.str" [builtins fixtures/primitives.pyi] [case testNarrowTypeObject] # flags: --strict-equality --warn-unreachable from typing import Any # https://github.com/python/mypy/issues/13704 def f1(cls: type): if cls is str: reveal_type(cls) # N: Revealed type is "def (o: builtins.object =) -> builtins.str" reveal_type(cls(5)) # N: Revealed type is "builtins.str" if issubclass(cls, int): reveal_type(cls) # N: Revealed type is "type[builtins.int]" elif cls is str: reveal_type(cls) # N: Revealed type is "def (o: builtins.object =) -> builtins.str" reveal_type(cls(5)) # N: Revealed type is "builtins.str" def f2(cls: type[object]): if cls is str: reveal_type(cls) # N: Revealed type is "def (o: builtins.object =) -> builtins.str" reveal_type(cls(5)) # N: Revealed type is "builtins.str" if issubclass(cls, int): reveal_type(cls) # N: Revealed type is "type[builtins.int]" elif cls is str: reveal_type(cls) # N: Revealed type is "def (o: builtins.object =) -> builtins.str" reveal_type(cls(5)) # N: Revealed type is "builtins.str" def f3(cls: type[Any]): if cls is str: reveal_type(cls) # N: Revealed type is "type[Any]" reveal_type(cls(5)) # N: Revealed type is "Any" if issubclass(cls, int): reveal_type(cls) # N: Revealed type is "type[builtins.int]" elif cls is str: reveal_type(cls) # N: Revealed type is "type[Any]" reveal_type(cls(5)) # N: Revealed type is "Any" [builtins fixtures/isinstance.pyi] [case testNarrowTypeObjectUnion] # flags: --strict-equality --warn-unreachable from __future__ import annotations def f4(cls: type[str | int]): reveal_type(cls) # N: Revealed type is "type[builtins.str] | type[builtins.int]" if cls is int: reveal_type(cls) # N: Revealed type is "type[builtins.int]" if cls == int: reveal_type(cls) # N: Revealed type is "type[builtins.int]" [builtins fixtures/primitives.pyi] [case testTypeEqualsCheck] # flags: --strict-equality --warn-unreachable from typing import Any y: Any if type(y) == int: reveal_type(y) # N: Revealed type is "builtins.int" [case testMultipleTypeEqualsCheck] # flags: --strict-equality --warn-unreachable from typing import Any x: Any y: Any if type(x) == type(y) == int: reveal_type(y) # N: Revealed type is "builtins.int" reveal_type(x) # N: Revealed type is "builtins.int" z: Any if int == type(z) == int: reveal_type(z) # N: Revealed type is "builtins.int" [case testTypeEqualsCheckWidening] # flags: --strict-equality --warn-unreachable from typing import Any def f(x: str, y: Any, z: object): if type(x) is type(y): reveal_type(x) # N: Revealed type is "builtins.str" reveal_type(y) # N: Revealed type is "builtins.str" if type(x) == type(y): reveal_type(x) # N: Revealed type is "builtins.str" reveal_type(y) # N: Revealed type is "builtins.str" if type(x) is type(z): reveal_type(x) # N: Revealed type is "builtins.str" reveal_type(z) # N: Revealed type is "builtins.str" if type(x) == type(z): reveal_type(x) # N: Revealed type is "builtins.str" reveal_type(z) # N: Revealed type is "builtins.str" [builtins fixtures/primitives.pyi] [case testTypeEqualsCheckWideningSelf] # flags: --strict-equality --warn-unreachable from typing import Any from typing_extensions import Self class A: def f(self: Self, y: Any, z: object): if type(self) is type(y): reveal_type(self) # N: Revealed type is "Self`0" reveal_type(y) # N: Revealed type is "Self`0" if type(self) is type(z): reveal_type(self) # N: Revealed type is "Self`0" reveal_type(z) # N: Revealed type is "Self`0" if type(self) == type(y): reveal_type(self) # N: Revealed type is "Self`0" reveal_type(y) # N: Revealed type is "Self`0" if type(self) == type(z): reveal_type(self) # N: Revealed type is "Self`0" reveal_type(z) # N: Revealed type is "Self`0" [builtins fixtures/primitives.pyi] [case testTypeEqualsCheckUsingIs] # flags: --strict-equality --warn-unreachable from typing import Any y: Any if type(y) is int: reveal_type(y) # N: Revealed type is "builtins.int" [case testTypeEqualsCheckUsingIsNonOverlapping] # flags: --strict-equality --warn-unreachable from typing import Union y: str if type(y) is int: # E: Non-overlapping identity check (left operand type: "type[str]", right operand type: "type[int]") \ # E: Subclass of "str" and "int" cannot exist: would have incompatible method signatures y # E: Statement is unreachable else: reveal_type(y) # N: Revealed type is "builtins.str" [builtins fixtures/isinstance.pyi] [case testTypeEqualsCheckUsingIsNonOverlappingChild] # flags: --strict-equality --warn-unreachable from typing import Union class A: ... class B: ... class C(A): ... def main(x: Union[B, C]): # C instance cannot be exactly its parent A, we need reversed subtyping relationship # here (type(parent) is Child). if type(x) is A: reveal_type(x) # E: Statement is unreachable else: reveal_type(x) # N: Revealed type is "__main__.B | __main__.C" [builtins fixtures/isinstance.pyi] [case testTypeEqualsCheckTypeVar] # flags: --strict-equality --warn-unreachable from typing import TypeVar class A: ... class B: ... T = TypeVar("T") def forgets_about_subclasses(self, obj: T) -> T: if isinstance(obj, A): return A() # E: Incompatible return value type (got "A", expected "T") elif isinstance(obj, B): return B() # E: Incompatible return value type (got "B", expected "T") raise def correct1(self, obj: T) -> T: # TODO: mypy should not error in this case if type(obj) == A: return A() # E: Incompatible return value type (got "A", expected "T") elif type(obj) == B: return B() # E: Incompatible return value type (got "B", expected "T") raise T_value = TypeVar("T_value", A, B) def forgets_about_multiple_inheritance(self, obj: T_value) -> T_value: # Note that it is a little confusing that mypy only errors in the first branch here, but this # is a branch that would be taken by a subclass of both A and B. # See also https://github.com/python/mypy/issues/10302#issuecomment-3832182574 if isinstance(obj, A): return A() # E: Incompatible return value type (got "A", expected "B") elif isinstance(obj, B): return B() raise def correct2(self, obj: T_value) -> T_value: if type(obj) == A: return A() elif type(obj) == B: return B() raise [builtins fixtures/primitives.pyi] [case testTypeEqualsCheckUsingDifferentSpecializedTypes] # flags: --strict-equality --warn-unreachable from collections import defaultdict x: defaultdict y: dict z: object if type(x) is type(y) is type(z): reveal_type(x) # N: Revealed type is "collections.defaultdict[Any, Any]" reveal_type(y) # N: Revealed type is "collections.defaultdict[Any, Any]" reveal_type(z) # N: Revealed type is "collections.defaultdict[Any, Any]" [case testTypeEqualsTypeObjectUnion] # flags: --strict-equality --warn-unreachable from __future__ import annotations def f(x: object, y: type[int] | type[str]): if type(x) == y: reveal_type(x) # N: Revealed type is "builtins.int | builtins.str" reveal_type(y) # N: Revealed type is "type[builtins.int] | type[builtins.str]" [builtins fixtures/primitives.pyi] [case testUnionTypeEquality] # flags: --strict-equality --warn-unreachable from typing import Any, reveal_type def f(x: Any): if type(x) == (int, str): # E: Non-overlapping equality check (left operand type: "type[Any]", right operand type: "tuple[type[int], type[str]]") reveal_type(x) # E: Statement is unreachable [builtins fixtures/tuple.pyi] [case testTypeIntersectionWithConcreteTypes] # flags: --strict-equality --warn-unreachable class X: x = 1 class Y: y = 1 class Z(X, Y): ... z = Z() x: X = z y: Y = z if type(x) is type(y): # E: Non-overlapping identity check (left operand type: "type[X]", right operand type: "type[Y]") reveal_type(x) # N: Revealed type is "__main__." reveal_type(y) # N: Revealed type is "__main__." x.y + y.x if isinstance(x, type(y)) and isinstance(y, type(x)): reveal_type(x) # N: Revealed type is "__main__." reveal_type(y) # N: Revealed type is "__main__." x.y + y.x [builtins fixtures/isinstance.pyi] [case testTypeEqualsNarrowingUnionWithElse] # flags: --strict-equality --warn-unreachable from typing import Union x: Union[int, str] if type(x) is int: reveal_type(x) # N: Revealed type is "builtins.int" else: reveal_type(x) # N: Revealed type is "builtins.int | builtins.str" [case testTypeEqualsMultipleTypesShouldntNarrow] # flags: --strict-equality --warn-unreachable from typing import Union x: Union[int, str] if type(x) == int == str: # E: Non-overlapping equality check (left operand type: "type[int]", right operand type: "type[str]") reveal_type(x) # E: Statement is unreachable else: reveal_type(x) # N: Revealed type is "builtins.int | builtins.str" [builtins fixtures/primitives.pyi] [case testTypeNotEqualsCheck] # flags: --strict-equality --warn-unreachable from typing import Union x: Union[int, str] if type(x) != int: reveal_type(x) # N: Revealed type is "builtins.int | builtins.str" else: reveal_type(x) # N: Revealed type is "builtins.int" # mypy thinks int isn't defined unless we include this [builtins fixtures/primitives.pyi] [case testTypeNotEqualsCheckUsingIsNot] # flags: --strict-equality --warn-unreachable from typing import Union x: Union[int, str] if type(x) is not int: reveal_type(x) # N: Revealed type is "builtins.int | builtins.str" else: reveal_type(x) # N: Revealed type is "builtins.int" [case testTypeNarrowingAgainstType] # flags: --strict-equality --warn-unreachable class A: def foo(self, x: object) -> None: reveal_type(self) # N: Revealed type is "__main__.A" reveal_type(x) # N: Revealed type is "builtins.object" if type(self) is type(x): reveal_type(self) # N: Revealed type is "__main__.A" reveal_type(x) # N: Revealed type is "__main__.A" else: reveal_type(self) # N: Revealed type is "__main__.A" reveal_type(x) # N: Revealed type is "builtins.object" if type(self) == type(x): reveal_type(self) # N: Revealed type is "__main__.A" reveal_type(x) # N: Revealed type is "__main__.A" else: reveal_type(self) # N: Revealed type is "__main__.A" reveal_type(x) # N: Revealed type is "builtins.object" class B: y: int def __eq__(self, other: object) -> bool: return type(other) is type(self) and other.y == self.y [builtins fixtures/primitives.pyi] [case testNarrowInElseCaseIfFinal] # flags: --strict-equality --warn-unreachable from typing import final, Union @final class C: pass class D: pass x: Union[C, D] if type(x) is C: reveal_type(x) # N: Revealed type is "__main__.C" else: reveal_type(x) # N: Revealed type is "__main__.D" [case testNarrowInIfCaseIfFinalUsingIsNot] # flags: --strict-equality --warn-unreachable from typing import final, Union @final class C: pass class D: pass x: Union[C, D] if type(x) is not C: reveal_type(x) # N: Revealed type is "__main__.D" else: reveal_type(x) # N: Revealed type is "__main__.C" [case testDunderClassNarrowing] # flags: --strict-equality --warn-unreachable from typing import Any def foo(y: object): if y.__class__ == int: reveal_type(y) # N: Revealed type is "builtins.int" else: reveal_type(y) # N: Revealed type is "builtins.object" if y.__class__ is int: reveal_type(y) # N: Revealed type is "builtins.int" else: reveal_type(y) # N: Revealed type is "builtins.object" def bar(y: Any): if y.__class__ == int: reveal_type(y) # N: Revealed type is "builtins.int" else: reveal_type(y) # N: Revealed type is "Any" if y.__class__ is int: reveal_type(y) # N: Revealed type is "builtins.int" else: reveal_type(y) # N: Revealed type is "Any" [builtins fixtures/dict-full.pyi] [case testNarrowTypeVarType] # flags: --strict-equality --warn-unreachable from typing import TypeVar T = TypeVar("T") class A: ... def foo(X: type[T]) -> T: if X == A: return X() raise # It could be nice to make these two test cases consistent, but it would be tricky # The above case is much more common in real world code def bar(X: type[T]) -> T: if X == A: return A() # E: Incompatible return value type (got "A", expected "T") raise [builtins fixtures/type.pyi] [case testNarrowingConstrainedTypeVarType] # flags: --strict-equality --warn-unreachable from typing import TypeVar, Any, Type TargetType = TypeVar("TargetType", int, float, str) # TODO: this behaviour is incorrect, it will be fixed by improving reachability def convert_type(target_type: Type[TargetType]) -> TargetType: if target_type == str: return str() if target_type == int: return int() if target_type == float: return float() # E: Incompatible return value type (got "float", expected "int") raise [builtins fixtures/primitives.pyi] [case testNarrowingEqualityWithPromotions] # flags: --strict-equality --warn-unreachable from __future__ import annotations from typing import Literal def f1(number: float, i: int): if number == i: reveal_type(number) # N: Revealed type is "builtins.float" reveal_type(i) # N: Revealed type is "builtins.int" def f2(number: float, five: Literal[5]): if number == five: reveal_type(number) # N: Revealed type is "builtins.float" reveal_type(five) # N: Revealed type is "Literal[5]" def f3(number: float | int, five: Literal[5]): if number == five: reveal_type(number) # N: Revealed type is "builtins.float | Literal[5]" reveal_type(five) # N: Revealed type is "Literal[5]" def f8(number: float | Literal[5], five: Literal[5]): if number == five: reveal_type(number) # N: Revealed type is "builtins.float | Literal[5]" reveal_type(five) # N: Revealed type is "Literal[5]" else: reveal_type(number) # N: Revealed type is "builtins.float" reveal_type(five) # N: Revealed type is "Literal[5]" def f4(number: float | None, i: int): if number == i: reveal_type(number) # N: Revealed type is "builtins.float" reveal_type(i) # N: Revealed type is "builtins.int" def f5(number: float | int, i: int): if number == i: reveal_type(number) # N: Revealed type is "builtins.float | builtins.int" reveal_type(i) # N: Revealed type is "builtins.int" def f6(number: float | complex, i: int): if number == i: reveal_type(number) # N: Revealed type is "builtins.float | builtins.complex" reveal_type(i) # N: Revealed type is "builtins.int" class Custom: def __eq__(self, other: object) -> bool: return True def f7(number: float, x: Custom | int): if number == x: reveal_type(number) # N: Revealed type is "builtins.float" reveal_type(x) # N: Revealed type is "__main__.Custom | builtins.int" [builtins fixtures/primitives.pyi] [case testNarrowingAnyNegativeIntersection-xfail] # flags: --strict-equality --warn-unreachable # https://github.com/python/mypy/issues/20597 from __future__ import annotations from typing import Any class array: ... def get_result() -> Any: ... def foo(x: str | array) -> str: result = get_result() if isinstance(result, array): return "asdf" if result is x: reveal_type(result) # N: Revealed type is "Any" return result raise [builtins fixtures/tuple.pyi] [case testNarrowingOptionalBytes] from __future__ import annotations def f(x: bytes | None): if x == b"asdf": reveal_type(x) # N: Revealed type is "builtins.bytes" else: reveal_type(x) # N: Revealed type is "builtins.bytes | None" [builtins fixtures/primitives.pyi] [case testNarrowingBytesLikeWithPromotion] # flags: --strict-equality --warn-unreachable --strict-bytes from __future__ import annotations def check_test(x: bytes) -> None: ... check_test(bytearray(b"asdf")) # E: Argument 1 to "check_test" has incompatible type "bytearray"; expected "bytes" def main( v_bytes: bytes, v_bytearray: bytearray, v_memoryview: memoryview, v_all: bytes | bytearray | memoryview, ) -> None: if v_bytes == v_bytearray: reveal_type(v_bytes) # N: Revealed type is "builtins.bytes" reveal_type(v_bytearray) # N: Revealed type is "builtins.bytearray" if v_bytes == v_memoryview: reveal_type(v_bytes) # N: Revealed type is "builtins.bytes" reveal_type(v_memoryview) # N: Revealed type is "builtins.memoryview" if v_bytearray == v_memoryview: reveal_type(v_bytearray) # N: Revealed type is "builtins.bytearray" reveal_type(v_memoryview) # N: Revealed type is "builtins.memoryview" if v_all == v_bytes: reveal_type(v_all) # N: Revealed type is "builtins.bytes" reveal_type(v_bytes) # N: Revealed type is "builtins.bytes" if v_all == v_bytearray: reveal_type(v_all) # N: Revealed type is "builtins.bytes | builtins.bytearray | builtins.memoryview" reveal_type(v_bytearray) # N: Revealed type is "builtins.bytearray" if v_all == v_memoryview: reveal_type(v_all) # N: Revealed type is "builtins.bytes | builtins.bytearray | builtins.memoryview" reveal_type(v_memoryview) # N: Revealed type is "builtins.memoryview" [builtins fixtures/primitives.pyi] [case testNarrowingBytesLikeNoPromotion] # flags: --strict-equality --warn-unreachable --no-strict-bytes from __future__ import annotations def check_test(x: bytes) -> None: ... check_test(bytearray(b"asdf")) def main( v_bytes: bytes, v_bytearray: bytearray, v_memoryview: memoryview, v_all: bytes | bytearray | memoryview, ) -> None: if v_bytes == v_bytearray: reveal_type(v_bytes) # N: Revealed type is "builtins.bytes" reveal_type(v_bytearray) # N: Revealed type is "builtins.bytearray" if v_bytes == v_memoryview: reveal_type(v_bytes) # N: Revealed type is "builtins.bytes" reveal_type(v_memoryview) # N: Revealed type is "builtins.memoryview" if v_bytearray == v_memoryview: reveal_type(v_bytearray) # N: Revealed type is "builtins.bytearray" reveal_type(v_memoryview) # N: Revealed type is "builtins.memoryview" if v_all == v_bytes: reveal_type(v_all) # N: Revealed type is "builtins.bytes | builtins.bytearray | builtins.memoryview" reveal_type(v_bytes) # N: Revealed type is "builtins.bytes" if v_all == v_bytearray: reveal_type(v_all) # N: Revealed type is "builtins.bytes | builtins.bytearray | builtins.memoryview" reveal_type(v_bytearray) # N: Revealed type is "builtins.bytearray" if v_all == v_memoryview: reveal_type(v_all) # N: Revealed type is "builtins.bytes | builtins.bytearray | builtins.memoryview" reveal_type(v_memoryview) # N: Revealed type is "builtins.memoryview" [builtins fixtures/primitives.pyi] [case testNarrowNewTypeVsSubclass] # mypy: strict-equality, warn-unreachable from typing import NewType M1 = NewType("M1", int) M2 = NewType("M2", int) def check_m(base: int, m1: M1, m2: M2): if m1 == m2: # E: Non-overlapping equality check (left operand type: "M1", right operand type: "M2") reveal_type(m1) # N: Revealed type is "__main__.M1" reveal_type(m2) # N: Revealed type is "__main__.M2" if m1 == base: # We do not narrow base reveal_type(m1) # N: Revealed type is "__main__.M1" reveal_type(base) # N: Revealed type is "builtins.int" if m2 == base: reveal_type(m2) # N: Revealed type is "__main__.M2" reveal_type(base) # N: Revealed type is "builtins.int" # We do narrow for subclasses! (assuming no custom equality) class A: ... class A1(A): ... class A2(A): ... def check_a(base: A, a1: A1, a2: A2): if a1 == a2: # E: Non-overlapping equality check (left operand type: "A1", right operand type: "A2") reveal_type(a1) # E: Statement is unreachable reveal_type(a2) if a1 == base: # We do narrow base reveal_type(a1) # N: Revealed type is "__main__.A1" reveal_type(base) # N: Revealed type is "__main__.A1" if a2 == base: # E: Non-overlapping equality check (left operand type: "A2", right operand type: "A1") reveal_type(a2) # E: Statement is unreachable reveal_type(base) [builtins fixtures/primitives.pyi] [case testNarrowNewTypeFromObject] # mypy: strict-equality, warn-unreachable from __future__ import annotations from typing import NewType UserId = NewType("UserId", int) def f1(whatever: object, uid: UserId): # The general principle is that we should not be able to produce a value of NewType # without there being explicit wrapping somewhere if whatever == uid: reveal_type(whatever) # N: Revealed type is "builtins.int" reveal_type(uid) # N: Revealed type is "__main__.UserId" class Other: ... def f2(whatever: object, uid: UserId | Other): if whatever == uid: reveal_type(whatever) # N: Revealed type is "builtins.int | __main__.Other" reveal_type(uid) # N: Revealed type is "__main__.UserId | __main__.Other" [builtins fixtures/primitives.pyi] [case testNarrowNewTypeNested] # mypy: strict-equality, warn-unreachable from typing import NewType, Final Path = NewType("Path", str) NormPath = NewType("NormPath", Path) def op(normpath: NormPath, path: Path): if normpath == path: # No narrowing reveal_type(normpath) # N: Revealed type is "__main__.NormPath" reveal_type(path) # N: Revealed type is "__main__.Path" [builtins fixtures/primitives.pyi] [case testNarrowNewTypeSharedValue] # mypy: strict-equality, warn-unreachable from typing import NewType, Final UserId = NewType("UserId", int) TeamId = NewType("TeamId", int) INVALID = 123 def get_owner(uid: UserId, tid: TeamId): # No narrowing for INVALID if uid == INVALID: reveal_type(uid) # N: Revealed type is "__main__.UserId" reveal_type(tid) # N: Revealed type is "__main__.TeamId" reveal_type(INVALID) # N: Revealed type is "builtins.int" if tid == INVALID: reveal_type(uid) # N: Revealed type is "__main__.UserId" reveal_type(tid) # N: Revealed type is "__main__.TeamId" reveal_type(INVALID) # N: Revealed type is "builtins.int" return None [builtins fixtures/primitives.pyi] [case testNarrowCallableTypeVarByEquality] from typing import Callable, TypeVar T = TypeVar("T") def remove(path: str) -> None: ... def unlink(path: str) -> None: ... def f1(func: Callable[..., T], arg: str) -> T: if func == remove: reveal_type(func) # N: Revealed type is "def (path: builtins.str) -> T`-1" reveal_type(func(arg)) # N: Revealed type is "T`-1" return func(arg) return func(arg) def f2(func: Callable[..., T], arg: str) -> T: if func in [unlink, remove]: reveal_type(func) # N: Revealed type is "def (path: builtins.str) -> T`-1" reveal_type(func(arg)) # N: Revealed type is "T`-1" return func(arg) return func(arg) [builtins fixtures/primitives.pyi] [case testNarrowGenericCallableEquality] # flags: --strict-equality --warn-unreachable from typing import Callable, TypeVar S = TypeVar("S") T = TypeVar("T") def identity(x: T) -> T: return x def msg(cmp_property: Callable[[T], S]) -> None: if cmp_property == identity: # TODO: the swapping of these reveal's is not ideal reveal_type(cmp_property) # N: Revealed type is "def [T] (x: T`-1) -> T`-1" reveal_type(identity) # N: Revealed type is "def (T`-1) -> S`-2" return [builtins fixtures/primitives.pyi] [case testPropagatedParentNarrowingMeet] # flags: --strict-equality --warn-unreachable from __future__ import annotations class A: tag: int class B: tag: int name = "b" class C: tag: str def stringify(value: A | B | C) -> str: if isinstance(value.tag, int) and isinstance(value, B): reveal_type(value) # N: Revealed type is "__main__.B" return value.name return "" [builtins fixtures/tuple.pyi]