Skip to content

Commit 7dc23fe

Browse files
Enable negative narrowing of Union TypeVar upper bounds (#17850)
Fixes #15235 ### Before ```python from typing import TypeVar class A: pass class B: b: int T = TypeVar("T", bound=A | B) def foo(x: T) -> T: if isinstance(x, A): reveal_type(x) # N: Revealed type is "__main__.A" else: reveal_type(x) # N: Revealed type is "T`-1" x.b # E: Item "A" of the upper bound "A | B" of type variable "T" has no attribute "b" return x ``` ### After ```python from typing import TypeVar class A: pass class B: b: int T = TypeVar("T", bound=A | B) def foo(x: T) -> T: if isinstance(x, A): reveal_type(x) # N: Revealed type is "__main__.A" else: reveal_type(x) # N: Revealed type is "T`-1" x.b # ok! Upper bound of T narrowed to B return x ```
1 parent 6726d77 commit 7dc23fe

File tree

2 files changed

+26
-0
lines changed

2 files changed

+26
-0
lines changed

Diff for: mypy/subtypes.py

+2
Original file line numberDiff line numberDiff line change
@@ -1941,6 +1941,8 @@ def restrict_subtype_away(t: Type, s: Type) -> Type:
19411941
if (isinstance(get_proper_type(item), AnyType) or not covers_at_runtime(item, s))
19421942
]
19431943
return UnionType.make_union(new_items)
1944+
elif isinstance(p_t, TypeVarType):
1945+
return p_t.copy_modified(upper_bound=restrict_subtype_away(p_t.upper_bound, s))
19441946
elif covers_at_runtime(t, s):
19451947
return UninhabitedType()
19461948
else:

Diff for: test-data/unit/check-isinstance.test

+24
Original file line numberDiff line numberDiff line change
@@ -1833,6 +1833,30 @@ def f(x: T) -> None:
18331833
reveal_type(x) # N: Revealed type is "T`-1"
18341834
[builtins fixtures/isinstance.pyi]
18351835

1836+
[case testIsinstanceAndNegativeNarrowTypeVariableWithUnionBound]
1837+
from typing import Union, TypeVar
1838+
1839+
class A:
1840+
a: int
1841+
class B:
1842+
b: int
1843+
1844+
T = TypeVar("T", bound=Union[A, B])
1845+
1846+
def f(x: T) -> T:
1847+
if isinstance(x, A):
1848+
reveal_type(x) # N: Revealed type is "__main__.A"
1849+
x.a
1850+
x.b # E: "A" has no attribute "b"
1851+
else:
1852+
reveal_type(x) # N: Revealed type is "T`-1"
1853+
x.a # E: "T" has no attribute "a"
1854+
x.b
1855+
x.a # E: Item "B" of the upper bound "Union[A, B]" of type variable "T" has no attribute "a"
1856+
x.b # E: Item "A" of the upper bound "Union[A, B]" of type variable "T" has no attribute "b"
1857+
return x
1858+
[builtins fixtures/isinstance.pyi]
1859+
18361860
[case testIsinstanceAndTypeType]
18371861
from typing import Type
18381862
def f(x: Type[int]) -> None:

0 commit comments

Comments
 (0)