Skip to content

Commit fd8d1cd

Browse files
committed
Narrow type variable bounds in binder
1 parent 68233f6 commit fd8d1cd

File tree

7 files changed

+72
-27
lines changed

7 files changed

+72
-27
lines changed

mypy/checkmember.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,19 +1480,20 @@ def bind_self_fast(method: F, original_type: Type | None = None) -> F:
14801480
items = [bind_self_fast(c, original_type) for c in method.items]
14811481
return cast(F, Overloaded(items))
14821482
assert isinstance(method, CallableType)
1483-
if not method.arg_types:
1483+
func: CallableType = method
1484+
if not func.arg_types:
14841485
# Invalid method, return something.
1485-
return cast(F, method)
1486-
if method.arg_kinds[0] in (ARG_STAR, ARG_STAR2):
1486+
return method
1487+
if func.arg_kinds[0] in (ARG_STAR, ARG_STAR2):
14871488
# See typeops.py for details.
1488-
return cast(F, method)
1489+
return method
14891490
original_type = get_proper_type(original_type)
14901491
if isinstance(original_type, CallableType) and original_type.is_type_obj():
14911492
original_type = TypeType.make_normalized(original_type.ret_type)
1492-
res = method.copy_modified(
1493-
arg_types=method.arg_types[1:],
1494-
arg_kinds=method.arg_kinds[1:],
1495-
arg_names=method.arg_names[1:],
1493+
res = func.copy_modified(
1494+
arg_types=func.arg_types[1:],
1495+
arg_kinds=func.arg_kinds[1:],
1496+
arg_names=func.arg_names[1:],
14961497
bound_args=[original_type],
14971498
)
14981499
return cast(F, res)

mypy/expandtype.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def freshen_function_type_vars(callee: F) -> F:
122122
"""Substitute fresh type variables for generic function type variables."""
123123
if isinstance(callee, CallableType):
124124
if not callee.is_generic():
125-
return cast(F, callee)
125+
return callee
126126
tvs = []
127127
tvmap: dict[TypeVarId, Type] = {}
128128
for v in callee.variables:

mypy/meet.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
find_unpack_in_list,
5151
get_proper_type,
5252
get_proper_types,
53+
has_type_vars,
5354
is_named_instance,
5455
split_with_prefix_and_suffix,
5556
)
@@ -149,6 +150,12 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type:
149150
return make_simplified_union(
150151
[narrow_declared_type(declared, x) for x in narrowed.relevant_items()]
151152
)
153+
elif (
154+
isinstance(declared, TypeVarType)
155+
and not has_type_vars(original_narrowed)
156+
and is_subtype(original_narrowed, declared.upper_bound)
157+
):
158+
return declared.copy_modified(upper_bound=original_narrowed)
152159
elif not is_overlapping_types(declared, narrowed, prohibit_none_typevar_overlap=True):
153160
if state.strict_optional:
154161
return UninhabitedType()

mypy/typeops.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -415,10 +415,10 @@ class B(A): pass
415415
]
416416
return cast(F, Overloaded(items))
417417
assert isinstance(method, CallableType)
418-
func = method
418+
func: CallableType = method
419419
if not func.arg_types:
420420
# Invalid method, return something.
421-
return cast(F, func)
421+
return method
422422
if func.arg_kinds[0] in (ARG_STAR, ARG_STAR2):
423423
# The signature is of the form 'def foo(*args, ...)'.
424424
# In this case we shouldn't drop the first arg,
@@ -427,7 +427,7 @@ class B(A): pass
427427

428428
# In the case of **kwargs we should probably emit an error, but
429429
# for now we simply skip it, to avoid crashes down the line.
430-
return cast(F, func)
430+
return method
431431
self_param_type = get_proper_type(func.arg_types[0])
432432

433433
variables: Sequence[TypeVarLikeType]

test-data/unit/check-classes.test

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6891,24 +6891,21 @@ reveal_type(i.x) # N: Revealed type is "builtins.int"
68916891
[builtins fixtures/isinstancelist.pyi]
68926892

68936893
[case testIsInstanceTypeTypeVar]
6894-
from typing import Type, TypeVar, Generic
6894+
from typing import Type, TypeVar, Generic, ClassVar
68956895

68966896
class Base: ...
6897-
class Sub(Base): ...
6897+
class Sub(Base):
6898+
other: ClassVar[int]
68986899

68996900
T = TypeVar('T', bound=Base)
69006901

69016902
class C(Generic[T]):
69026903
def meth(self, cls: Type[T]) -> None:
69036904
if not issubclass(cls, Sub):
69046905
return
6905-
reveal_type(cls) # N: Revealed type is "type[__main__.Sub]"
6906-
def other(self, cls: Type[T]) -> None:
6907-
if not issubclass(cls, Sub):
6908-
return
6909-
reveal_type(cls) # N: Revealed type is "type[__main__.Sub]"
6910-
6911-
[builtins fixtures/isinstancelist.pyi]
6906+
reveal_type(cls) # N: Revealed type is "type[T`1]"
6907+
reveal_type(cls.other) # N: Revealed type is "builtins.int"
6908+
[builtins fixtures/isinstance.pyi]
69126909

69136910
[case testIsInstanceTypeSubclass]
69146911
from typing import Type, Optional
@@ -7602,7 +7599,7 @@ class C1:
76027599
class C2(Generic[TypeT]):
76037600
def method(self, other: TypeT) -> int:
76047601
if issubclass(other, Base):
7605-
reveal_type(other) # N: Revealed type is "type[__main__.Base]"
7602+
reveal_type(other) # N: Revealed type is "TypeT`1"
76067603
return other.field
76077604
return 0
76087605

test-data/unit/check-isinstance.test

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1821,19 +1821,23 @@ if issubclass(fm, Baz):
18211821
from typing import TypeVar
18221822

18231823
class A: pass
1824-
class B(A): pass
1824+
class B(A):
1825+
attr: int
18251826

18261827
T = TypeVar('T', bound=A)
18271828

18281829
def f(x: T) -> None:
18291830
if isinstance(x, B):
1830-
reveal_type(x) # N: Revealed type is "__main__.B"
1831+
reveal_type(x) # N: Revealed type is "T`-1"
1832+
reveal_type(x.attr) # N: Revealed type is "builtins.int"
18311833
else:
18321834
reveal_type(x) # N: Revealed type is "T`-1"
1835+
x.attr # E: "T" has no attribute "attr"
18331836
reveal_type(x) # N: Revealed type is "T`-1"
1837+
x.attr # E: "T" has no attribute "attr"
18341838
[builtins fixtures/isinstance.pyi]
18351839

1836-
[case testIsinstanceAndNegativeNarrowTypeVariableWithUnionBound]
1840+
[case testIsinstanceAndNegativeNarrowTypeVariableWithUnionBound1]
18371841
from typing import Union, TypeVar
18381842

18391843
class A:
@@ -1845,9 +1849,11 @@ T = TypeVar("T", bound=Union[A, B])
18451849

18461850
def f(x: T) -> T:
18471851
if isinstance(x, A):
1848-
reveal_type(x) # N: Revealed type is "__main__.A"
1852+
reveal_type(x) # N: Revealed type is "T`-1"
18491853
x.a
1850-
x.b # E: "A" has no attribute "b"
1854+
x.b # E: "T" has no attribute "b"
1855+
if bool():
1856+
return x
18511857
else:
18521858
reveal_type(x) # N: Revealed type is "T`-1"
18531859
x.a # E: "T" has no attribute "a"
@@ -1857,6 +1863,24 @@ def f(x: T) -> T:
18571863
return x
18581864
[builtins fixtures/isinstance.pyi]
18591865

1866+
[case testIsinstanceAndNegativeNarrowTypeVariableWithUnionBound2]
1867+
from typing import Union, TypeVar
1868+
1869+
class A:
1870+
a: int
1871+
class B:
1872+
b: int
1873+
1874+
T = TypeVar("T", bound=Union[A, B])
1875+
1876+
def f(x: T) -> T:
1877+
if isinstance(x, A):
1878+
return x
1879+
x.a # E: "T" has no attribute "a"
1880+
x.b # OK
1881+
return x
1882+
[builtins fixtures/isinstance.pyi]
1883+
18601884
[case testIsinstanceAndTypeType]
18611885
from typing import Type
18621886
def f(x: Type[int]) -> None:

test-data/unit/check-narrowing.test

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2424,3 +2424,19 @@ def f() -> None:
24242424
assert isinstance(x, int)
24252425
reveal_type(x) # N: Revealed type is "builtins.int"
24262426
[builtins fixtures/isinstance.pyi]
2427+
2428+
[case testNarrowTypeVarBoundType]
2429+
from typing import Type, TypeVar
2430+
2431+
class A: ...
2432+
class B(A):
2433+
other: int
2434+
2435+
T = TypeVar("T", bound=A)
2436+
def test(cls: Type[T]) -> T:
2437+
if issubclass(cls, B):
2438+
reveal_type(cls) # N: Revealed type is "type[T`-1]"
2439+
reveal_type(cls().other) # N: Revealed type is "builtins.int"
2440+
return cls()
2441+
return cls()
2442+
[builtins fixtures/isinstance.pyi]

0 commit comments

Comments
 (0)