Skip to content

Commit

Permalink
Enable negative narrowing of Union TypeVar upper bounds (#17850)
Browse files Browse the repository at this point in the history
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
```
  • Loading branch information
brianschubert authored Sep 30, 2024
1 parent 6726d77 commit 7dc23fe
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 0 deletions.
2 changes: 2 additions & 0 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1941,6 +1941,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:
Expand Down
24 changes: 24 additions & 0 deletions test-data/unit/check-isinstance.test
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 7dc23fe

Please sign in to comment.