| [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/tuple.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/isinstance.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: Invalid tuple index type (actual type "str", expected type "Union[int, slice]") |
| 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/slice.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 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: FlipFlopEnum) -> None: |
| # Naively, we might assume the 'assert' here would narrow the type to |
| # Literal[State.A]. 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.A |
| reveal_type(switch.state) # N: Revealed type is '__main__.State' |
| |
| switch.mutate() |
| |
| assert switch.state == State.B |
| reveal_type(switch.state) # N: Revealed type is '__main__.State' |
| |
| def test2(switch: FlipFlopEnum) -> None: |
| # So strictly speaking, we ought to do the same thing with 'is' comparisons |
| # for the same reasons as above. But in practice, not too many people seem to |
| # know that doing 'some_enum is MyEnum.Value' is idiomatic. So in practice, |
| # this is probably good enough for now. |
| |
| 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 |
| |
| def test3(switch: FlipFlopStr) -> None: |
| # This is the same thing as 'test1', except we try using str literals. |
| |
| 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' |
| [builtins fixtures/primitives.pyi] |
| |
| [case testNarrowingEqualityRequiresExplicitStrLiteral] |
| # flags: --strict-optional |
| 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] |
| # flags: --strict-optional |
| 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] |
| |
| # See comments in testNarrowingEqualityRequiresExplicitStrLiteral and |
| # testNarrowingEqualityFlipFlop for more on why we can't narrow here. |
| x1: Foo |
| if x1 == Foo.A: |
| reveal_type(x1) # N: Revealed type is '__main__.Foo' |
| else: |
| reveal_type(x1) # N: Revealed type is '__main__.Foo' |
| |
| x2: Foo |
| if x2 == A_final: |
| reveal_type(x2) # N: Revealed type is '__main__.Foo' |
| else: |
| reveal_type(x2) # N: Revealed type is '__main__.Foo' |
| |
| # 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]' |
| [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-optional --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: "Union[Literal[1], Literal[2], None]", 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-optional --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-optional --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 'Union[Literal[False], Literal[True]]' |
| |
| [builtins fixtures/primitives.pyi] |