From cadfbb7bdc956a003bb4a87a288fc0dd409fd7cc Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Sun, 29 Sep 2024 14:26:37 -0400 Subject: [PATCH 1/2] Allow narrowing of TypeVar upper bound during conditional types checks --- mypy/subtypes.py | 2 ++ test-data/unit/check-narrowing.test | 47 +++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 608d098791a9..b5c6c7b8d508 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1930,6 +1930,8 @@ def restrict_subtype_away(t: Type, s: Type) -> Type: if (isinstance(get_proper_type(item), AnyType) or not covers_at_runtime(item, s)) ] return UnionType.make_union(new_items) + elif isinstance(p_t, TypeVarType): + return p_t.copy_modified(upper_bound=restrict_subtype_away(p_t.upper_bound, s)) elif covers_at_runtime(t, s): return UninhabitedType() else: diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 0cb4bf8e4a19..5819262f1dd7 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -1305,6 +1305,53 @@ def f(t: Type[T], a: A, b: B) -> None: else: reveal_type(b) # N: Revealed type is "__main__.B" +[case testNarrowingTypeVarConstraints] +from typing import TypeVar + +class A: + a: int +class B: + b: int + +T = TypeVar("T", A, B) + +def f(x: T): + x.a # E: "B" has no attribute "a" + x.b # E: "A" has no attribute "b" + if isinstance(x, A): + reveal_type(x) # N: Revealed type is "__main__.A" \ + # N: Revealed type is "__main__." + x.a + x.b # E: "A" has no attribute "b" + else: + reveal_type(x) # N: Revealed type is "__main__.B" + x.a # E: "B" has no attribute "a" + x.b +[builtins fixtures/isinstance.pyi] + +[case testNarrowingTypeVarUnionBound] +from typing import Union, TypeVar + +class A: + a: int +class B: + b: int + +T = TypeVar("T", bound=Union[A, B]) + +def f(x: T): + x.a # E: Item "B" of the upper bound "Union[A, B]" of type variable "T" has no attribute "a" + x.b # E: Item "A" of the upper bound "Union[A, B]" of type variable "T" has no attribute "b" + if isinstance(x, A): + reveal_type(x) # N: Revealed type is "__main__.A" + x.a + x.b # E: "A" has no attribute "b" + else: + reveal_type(x) # N: Revealed type is "T`-1" + x.a # E: "T" has no attribute "a" + x.b +[builtins fixtures/isinstance.pyi] + [case testNarrowingNestedUnionOfTypedDicts] from typing import Union from typing_extensions import Literal, TypedDict From f364f89482c75094235b939c063dc25a31e93784 Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Sun, 29 Sep 2024 15:36:49 -0400 Subject: [PATCH 2/2] Refactor new tests Better fit for 'check-isinstance.test' New test 'testNarrowingTypeVarConstraints' is already covered by 'testIsInstanceAdHocIntersectionGenericsWithValues' --- test-data/unit/check-isinstance.test | 24 ++++++++++++++ test-data/unit/check-narrowing.test | 47 ---------------------------- 2 files changed, 24 insertions(+), 47 deletions(-) diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index b7ee38b69d00..8fa1bc1ca1ac 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1833,6 +1833,30 @@ def f(x: T) -> None: reveal_type(x) # N: Revealed type is "T`-1" [builtins fixtures/isinstance.pyi] +[case testIsinstanceAndNegativeNarrowTypeVariableWithUnionBound] +from typing import Union, TypeVar + +class A: + a: int +class B: + b: int + +T = TypeVar("T", bound=Union[A, B]) + +def f(x: T) -> T: + if isinstance(x, A): + reveal_type(x) # N: Revealed type is "__main__.A" + x.a + x.b # E: "A" has no attribute "b" + else: + reveal_type(x) # N: Revealed type is "T`-1" + x.a # E: "T" has no attribute "a" + x.b + x.a # E: Item "B" of the upper bound "Union[A, B]" of type variable "T" has no attribute "a" + x.b # E: Item "A" of the upper bound "Union[A, B]" of type variable "T" has no attribute "b" + return x +[builtins fixtures/isinstance.pyi] + [case testIsinstanceAndTypeType] from typing import Type def f(x: Type[int]) -> None: diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 5819262f1dd7..0cb4bf8e4a19 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -1305,53 +1305,6 @@ def f(t: Type[T], a: A, b: B) -> None: else: reveal_type(b) # N: Revealed type is "__main__.B" -[case testNarrowingTypeVarConstraints] -from typing import TypeVar - -class A: - a: int -class B: - b: int - -T = TypeVar("T", A, B) - -def f(x: T): - x.a # E: "B" has no attribute "a" - x.b # E: "A" has no attribute "b" - if isinstance(x, A): - reveal_type(x) # N: Revealed type is "__main__.A" \ - # N: Revealed type is "__main__." - x.a - x.b # E: "A" has no attribute "b" - else: - reveal_type(x) # N: Revealed type is "__main__.B" - x.a # E: "B" has no attribute "a" - x.b -[builtins fixtures/isinstance.pyi] - -[case testNarrowingTypeVarUnionBound] -from typing import Union, TypeVar - -class A: - a: int -class B: - b: int - -T = TypeVar("T", bound=Union[A, B]) - -def f(x: T): - x.a # E: Item "B" of the upper bound "Union[A, B]" of type variable "T" has no attribute "a" - x.b # E: Item "A" of the upper bound "Union[A, B]" of type variable "T" has no attribute "b" - if isinstance(x, A): - reveal_type(x) # N: Revealed type is "__main__.A" - x.a - x.b # E: "A" has no attribute "b" - else: - reveal_type(x) # N: Revealed type is "T`-1" - x.a # E: "T" has no attribute "a" - x.b -[builtins fixtures/isinstance.pyi] - [case testNarrowingNestedUnionOfTypedDicts] from typing import Union from typing_extensions import Literal, TypedDict