| [case testNarrowingParentWithStrsBasic] |
| # flags: --python-version 3.7 |
| from dataclasses import dataclass |
| from typing import NamedTuple, Tuple, Union |
| from typing_extensions import Literal, TypedDict |
| |
| 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] |
| |
| [case testNarrowingParentWithEnumsBasic] |
| # flags: --python-version 3.7 |
| from enum import Enum |
| from dataclasses import dataclass |
| from typing import NamedTuple, Tuple, Union |
| from typing_extensions import Literal, TypedDict |
| |
| 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/narrowing.pyi] |
| |
| [case testNarrowingParentWithIsInstanceBasic] |
| # flags: --python-version 3.7 |
| from dataclasses import dataclass |
| from typing import NamedTuple, Tuple, Union |
| from typing_extensions import TypedDict |
| |
| 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/narrowing.pyi] |
| |
| [case testNarrowingParentMultipleKeys] |
| # flags: --warn-unreachable |
| from enum import Enum |
| from typing import Union |
| from typing_extensions import Literal |
| |
| 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 "Union[__main__.Object1, __main__.Object2]" |
| |
| if x.key is Key.C: |
| reveal_type(x) # N: Revealed type is "Union[__main__.Object1, __main__.Object2]" |
| else: |
| reveal_type(x) # N: Revealed type is "Union[__main__.Object1, __main__.Object2]" |
| |
| if x.key is Key.D: |
| reveal_type(x) # E: Statement is unreachable |
| else: |
| reveal_type(x) # N: Revealed type is "Union[__main__.Object1, __main__.Object2]" |
| [builtins fixtures/tuple.pyi] |
| |
| [case testNarrowingTypedDictParentMultipleKeys] |
| # flags: --warn-unreachable |
| from typing import Union |
| from typing_extensions import Literal, TypedDict |
| |
| 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': Union[Literal['A'], Literal['C']]})" |
| else: |
| reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key': Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key': Union[Literal['B'], Literal['C']]})]" |
| |
| if x['key'] == 'C': |
| reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key': Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key': Union[Literal['B'], Literal['C']]})]" |
| else: |
| reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key': Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key': Union[Literal['B'], Literal['C']]})]" |
| |
| if x['key'] == 'D': |
| reveal_type(x) # E: Statement is unreachable |
| else: |
| reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key': Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key': Union[Literal['B'], Literal['C']]})]" |
| [builtins fixtures/primitives.pyi] |
| |
| [case testNarrowingPartialTypedDictParentMultipleKeys] |
| # flags: --warn-unreachable |
| from typing import Union |
| from typing_extensions import Literal, TypedDict |
| |
| 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'?: Union[Literal['A'], Literal['C']]})" |
| else: |
| reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key'?: Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key'?: Union[Literal['B'], Literal['C']]})]" |
| |
| if x['key'] == 'C': |
| reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key'?: Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key'?: Union[Literal['B'], Literal['C']]})]" |
| else: |
| reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key'?: Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key'?: Union[Literal['B'], Literal['C']]})]" |
| |
| if x['key'] == 'D': |
| reveal_type(x) # E: Statement is unreachable |
| else: |
| reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key'?: Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key'?: Union[Literal['B'], Literal['C']]})]" |
| [builtins fixtures/primitives.pyi] |
| |
| [case testNarrowingNestedTypedDicts] |
| from typing import Union |
| from typing_extensions import TypedDict, Literal |
| |
| 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': Union[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 "Union[TypedDict('__main__.X', {'inner': Union[TypedDict('__main__.A', {'key': Literal['A']}), TypedDict('__main__.B', {'key': Literal['B']})]}), TypedDict('__main__.Y', {'inner': Union[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': Union[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] |
| |
| [case testNarrowingParentWithMultipleParents] |
| from enum import Enum |
| from typing import Union |
| from typing_extensions import Literal |
| |
| 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 "Union[__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 "Union[__main__.Object1, __main__.Object2, __main__.Object3]" |
| [builtins fixtures/isinstance.pyi] |
| |
| [case testNarrowingParentsWithGenerics] |
| 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] |
| from enum import Enum |
| from typing import Union, NamedTuple |
| from typing_extensions import Literal, 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 "Union[KeyedObject, KeyedTypedDict]" has no attribute "key" |
| reveal_type(impossible_mixture) # N: Revealed type is "Union[__main__.KeyedObject, TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]})]" |
| else: |
| reveal_type(impossible_mixture) # N: Revealed type is "Union[__main__.KeyedObject, TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]})]" |
| |
| if impossible_mixture["key"] is Key.A: # E: Value of type "Union[KeyedObject, KeyedTypedDict]" is not indexable |
| reveal_type(impossible_mixture) # N: Revealed type is "Union[__main__.KeyedObject, TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]})]" |
| else: |
| reveal_type(impossible_mixture) # N: Revealed type is "Union[__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 "Union[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 "Union[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 "Union[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 "Union[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] |
| from enum import Enum |
| from typing import Union |
| from typing_extensions import Literal |
| |
| 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 "Union[__main__.Object1, __main__.Object2]" |
| else: |
| reveal_type(x) # N: Revealed type is "__main__.Object3" |
| [builtins fixtures/property.pyi] |
| |
| [case testNarrowingParentWithAny] |
| from enum import Enum |
| from typing import Union, Any |
| from typing_extensions import Literal |
| |
| 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 "Union[__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 "Union[Any, Literal[__main__.Key.B]]" |
| reveal_type(x) # N: Revealed type is "Union[__main__.Object1, __main__.Object2, Any]" |
| [builtins fixtures/tuple.pyi] |
| |
| [case testNarrowingParentsHierarchy] |
| from typing import Union |
| from typing_extensions import Literal |
| 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 "Union[__main__.Parent1, __main__.Parent3]" |
| reveal_type(x.child) # N: Revealed type is "__main__.Child1" |
| else: |
| reveal_type(x) # N: Revealed type is "Union[__main__.Parent1, __main__.Parent2, __main__.Parent3]" |
| reveal_type(x.child) # N: Revealed type is "Union[__main__.Child2, __main__.Child3]" |
| |
| if x.child.same_for_1_and_2 is Key.A: |
| reveal_type(x) # N: Revealed type is "Union[__main__.Parent1, __main__.Parent2, __main__.Parent3]" |
| reveal_type(x.child) # N: Revealed type is "Union[__main__.Child1, __main__.Child2]" |
| else: |
| reveal_type(x) # N: Revealed type is "Union[__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 "Union[__main__.Parent1, __main__.Parent2]" |
| reveal_type(y.child) # N: Revealed type is "Union[__main__.Child2, __main__.Child3]" |
| |
| if y.child.same_for_1_and_2 is Key.A: |
| reveal_type(y) # N: Revealed type is "Union[__main__.Parent1, __main__.Parent2]" |
| reveal_type(y.child) # N: Revealed type is "Union[__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] |
| 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: --warn-unreachable |
| from typing import Union |
| from typing_extensions import TypedDict, Literal |
| 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: |
| reveal_type(y) # E: Statement is unreachable |
| reveal_type(y["model"]) |
| else: |
| reveal_type(y) # N: Revealed type is "Union[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 "Union[TypedDict('__main__.Model1', {'key': Literal[__main__.Key.A]}), TypedDict('__main__.Model2', {'key': Literal[__main__.Key.B]})]" |
| [builtins fixtures/tuple.pyi] |
| |
| [case testNarrowingParentsHierarchyTypedDictWithStr] |
| # flags: --warn-unreachable |
| from typing import Union |
| from typing_extensions import TypedDict, Literal |
| |
| 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': |
| reveal_type(y) # E: Statement is unreachable |
| reveal_type(y["model"]) |
| else: |
| reveal_type(y) # N: Revealed type is "Union[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 "Union[TypedDict('__main__.Model1', {'key': Literal['A']}), TypedDict('__main__.Model2', {'key': Literal['B']})]" |
| [builtins fixtures/primitives.pyi] |
| |
| [case testNarrowingExprPropagation] |
| from typing import Union |
| from typing_extensions import Literal |
| |
| 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: --warn-unreachable --strict-equality |
| from typing_extensions import Literal, Final |
| 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] |
| from typing_extensions import Literal, Final |
| |
| 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 "Union[Literal['B'], None]" |
| reveal_type(x_union) # N: Revealed type is "Union[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 "Union[Literal['B'], None]" |
| reveal_type(x_union) # N: Revealed type is "Union[Literal['A'], Literal['B'], None]" |
| [builtins fixtures/primitives.pyi] |
| |
| [case testNarrowingEqualityRequiresExplicitEnumLiteral] |
| from typing import Union |
| from typing_extensions import Literal, Final |
| 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 testNarrowingEqualityDisabledForCustomEquality] |
| from typing import Union |
| from typing_extensions import Literal |
| from enum import Enum |
| |
| class Custom: |
| def __eq__(self, other: object) -> bool: return True |
| |
| class Default: pass |
| |
| x1: Union[Custom, Literal[1], Literal[2]] |
| if x1 == 1: |
| reveal_type(x1) # N: Revealed type is "Union[__main__.Custom, Literal[1], Literal[2]]" |
| else: |
| reveal_type(x1) # N: Revealed type is "Union[__main__.Custom, Literal[1], Literal[2]]" |
| |
| x2: Union[Default, Literal[1], Literal[2]] |
| if x2 == 1: |
| reveal_type(x2) # N: Revealed type is "Literal[1]" |
| else: |
| reveal_type(x2) # N: Revealed type is "Union[__main__.Default, Literal[2]]" |
| |
| 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 "__main__.CustomEnum" |
| |
| # 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 testNarrowingEqualityDisabledForCustomEqualityChain] |
| # flags: --strict-equality --warn-unreachable |
| from typing import Union |
| from typing_extensions import Literal |
| |
| class Custom: |
| def __eq__(self, other: object) -> bool: return True |
| |
| class Default: pass |
| |
| x: Literal[1, 2, None] |
| y: Custom |
| z: Default |
| |
| # We could maybe try doing something clever, but for simplicity we |
| # treat the whole chain as contaminated and mostly disable narrowing. |
| # |
| # The only exception is that we do at least strip away the 'None'. We |
| # (perhaps optimistically) assume no custom class would be pathological |
| # enough to declare itself to be equal to None and so permit this narrowing, |
| # since it's often convenient in practice. |
| if 1 == x == y: |
| reveal_type(x) # N: Revealed type is "Union[Literal[1], Literal[2]]" |
| reveal_type(y) # N: Revealed type is "__main__.Custom" |
| else: |
| reveal_type(x) # N: Revealed type is "Union[Literal[1], Literal[2], None]" |
| reveal_type(y) # N: Revealed type is "__main__.Custom" |
| |
| # No contamination here |
| if 1 == x == z: # E: Non-overlapping equality check (left operand type: "Optional[Literal[1, 2]]", right operand type: "Default") |
| reveal_type(x) # E: Statement is unreachable |
| reveal_type(z) |
| else: |
| reveal_type(x) # N: Revealed type is "Union[Literal[1], Literal[2], None]" |
| reveal_type(z) # N: Revealed type is "__main__.Default" |
| [builtins fixtures/primitives.pyi] |
| |
| [case testNarrowingUnreachableCases] |
| # flags: --strict-equality --warn-unreachable |
| from typing import Union |
| from typing_extensions import Literal |
| |
| 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 "Union[Literal[1], Literal[2]]" |
| reveal_type(c) # N: Revealed type is "Union[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 "Union[Literal[1], Literal[2]]" |
| reveal_type(c) # N: Revealed type is "Union[Literal[2], Literal[3]]" |
| [builtins fixtures/primitives.pyi] |
| |
| [case testNarrowingUnreachableCases2] |
| # flags: --strict-equality --warn-unreachable |
| from typing import Union |
| from typing_extensions import Literal |
| |
| 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 "Union[Literal[1], Literal[2], Literal[3], Literal[4]]" |
| reveal_type(b) # N: Revealed type is "Union[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] |
| from typing import Union |
| from typing_extensions import Literal |
| |
| 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 "Union[Literal[False], builtins.str]" |
| |
| 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 testNarrowingLiteralIdentityCheck] |
| from typing import Union |
| from typing_extensions import Literal |
| |
| 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 "Union[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] |
| from typing import Optional |
| from typing_extensions import Literal |
| |
| 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 "Union[Literal[True], None]" |
| else: |
| reveal_type(opt_bool_val) # N: Revealed type is "Literal[False]" |
| [builtins fixtures/primitives.pyi] |
| |
| [case testNarrowingBooleanTruthiness] |
| from typing import Optional |
| from typing_extensions import Literal |
| |
| 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 "Union[Literal[False], None]" |
| reveal_type(opt_bool_val) # N: Revealed type is "Union[builtins.bool, None]" |
| [builtins fixtures/primitives.pyi] |
| |
| [case testNarrowingBooleanBoolOp] |
| from typing import Optional |
| from typing_extensions import Literal |
| |
| 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] |
| from typing import Union |
| from typing_extensions import TypedDict, Literal |
| 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] |
| |
| [case testNarrowingUsingMetaclass] |
| 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] |
| 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 "<nothing>" |
| else: |
| reveal_type(b) # N: Revealed type is "__main__.B" |
| |
| [case testNarrowingNestedUnionOfTypedDicts] |
| from typing import Union |
| from typing_extensions import Literal, TypedDict |
| |
| 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] |
| |
| |
| [case testNarrowingRuntimeCover] |
| 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 |
| |
| 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 "Union[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 "Union[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] |
| 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] |
| from typing import Optional |
| |
| class A: ... |
| |
| val: Optional[A] |
| |
| if val == None: |
| reveal_type(val) # N: Revealed type is "Union[__main__.A, None]" |
| else: |
| reveal_type(val) # N: Revealed type is "Union[__main__.A, None]" |
| if val != None: |
| reveal_type(val) # N: Revealed type is "Union[__main__.A, None]" |
| else: |
| reveal_type(val) # N: Revealed type is "Union[__main__.A, None]" |
| |
| if val in (None,): |
| reveal_type(val) # N: Revealed type is "Union[__main__.A, None]" |
| else: |
| reveal_type(val) # N: Revealed type is "Union[__main__.A, None]" |
| if val not in (None,): |
| reveal_type(val) # N: Revealed type is "Union[__main__.A, None]" |
| else: |
| reveal_type(val) # N: Revealed type is "Union[__main__.A, None]" |
| [builtins fixtures/primitives.pyi] |
| |
| [case testNarrowingWithTupleOfTypes] |
| 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: --python-version 3.10 |
| 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 "Union[builtins.int, __main__.Base]" |
| |
| raw: tuple[type, ...] |
| if isinstance(some, raw): |
| reveal_type(some) # N: Revealed type is "Union[builtins.int, __main__.Base]" |
| else: |
| reveal_type(some) # N: Revealed type is "Union[builtins.int, __main__.Base]" |
| [builtins fixtures/dict.pyi] |