Skip to content

Commit 15280bf

Browse files
TH3CHARLieilevkivskyi
authored andcommitted
Fix issubclass() to narrow down types of type variables (#7930)
Resolves #7920 The fix is just to add one more special-case to the existing logic. Using this opportunity, the special-casing logic around `issubclass()` is moved to a helper method.
1 parent 47bafd6 commit 15280bf

File tree

2 files changed

+67
-24
lines changed

2 files changed

+67
-24
lines changed

mypy/checker.py

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3733,30 +3733,7 @@ def find_isinstance_check_helper(self, node: Expression) -> Tuple[TypeMap, TypeM
37333733
return {}, {}
37343734
expr = node.args[0]
37353735
if literal(expr) == LITERAL_TYPE:
3736-
vartype = get_proper_type(type_map[expr])
3737-
type = get_isinstance_type(node.args[1], type_map)
3738-
if isinstance(vartype, UnionType):
3739-
union_list = []
3740-
for t in get_proper_types(vartype.items):
3741-
if isinstance(t, TypeType):
3742-
union_list.append(t.item)
3743-
else:
3744-
# This is an error that should be reported earlier
3745-
# if we reach here, we refuse to do any type inference.
3746-
return {}, {}
3747-
vartype = UnionType(union_list)
3748-
elif isinstance(vartype, TypeType):
3749-
vartype = vartype.item
3750-
elif (isinstance(vartype, Instance) and
3751-
vartype.type.fullname == 'builtins.type'):
3752-
vartype = self.named_type('builtins.object')
3753-
else:
3754-
# Any other object whose type we don't know precisely
3755-
# for example, Any or a custom metaclass.
3756-
return {}, {} # unknown type
3757-
yes_map, no_map = conditional_type_map(expr, vartype, type)
3758-
yes_map, no_map = map(convert_to_typetype, (yes_map, no_map))
3759-
return yes_map, no_map
3736+
return self.infer_issubclass_maps(node, expr, type_map)
37603737
elif refers_to_fullname(node.callee, 'builtins.callable'):
37613738
if len(node.args) != 1: # the error will be reported elsewhere
37623739
return {}, {}
@@ -4367,6 +4344,39 @@ def push_type_map(self, type_map: 'TypeMap') -> None:
43674344
for expr, type in type_map.items():
43684345
self.binder.put(expr, type)
43694346

4347+
def infer_issubclass_maps(self, node: CallExpr,
4348+
expr: Expression,
4349+
type_map: Dict[Expression, Type]
4350+
) -> Tuple[TypeMap, TypeMap]:
4351+
"""Infer type restrictions for an expression in issubclass call."""
4352+
vartype = type_map[expr]
4353+
type = get_isinstance_type(node.args[1], type_map)
4354+
if isinstance(vartype, TypeVarType):
4355+
vartype = vartype.upper_bound
4356+
vartype = get_proper_type(vartype)
4357+
if isinstance(vartype, UnionType):
4358+
union_list = []
4359+
for t in get_proper_types(vartype.items):
4360+
if isinstance(t, TypeType):
4361+
union_list.append(t.item)
4362+
else:
4363+
# This is an error that should be reported earlier
4364+
# if we reach here, we refuse to do any type inference.
4365+
return {}, {}
4366+
vartype = UnionType(union_list)
4367+
elif isinstance(vartype, TypeType):
4368+
vartype = vartype.item
4369+
elif (isinstance(vartype, Instance) and
4370+
vartype.type.fullname == 'builtins.type'):
4371+
vartype = self.named_type('builtins.object')
4372+
else:
4373+
# Any other object whose type we don't know precisely
4374+
# for example, Any or a custom metaclass.
4375+
return {}, {} # unknown type
4376+
yes_map, no_map = conditional_type_map(expr, vartype, type)
4377+
yes_map, no_map = map(convert_to_typetype, (yes_map, no_map))
4378+
return yes_map, no_map
4379+
43704380

43714381
def conditional_type_map(expr: Expression,
43724382
current_type: Optional[Type],

test-data/unit/check-classes.test

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6512,3 +6512,36 @@ def access_after_declaration(self) -> None:
65126512

65136513
reveal_type(x) # N: Revealed type is 'builtins.int'
65146514
x = x + 1
6515+
6516+
[case testIsSubClassNarrowDownTypesOfTypeVariables]
6517+
from typing import Type, TypeVar, Generic
6518+
6519+
class Base:
6520+
field: int = 42
6521+
6522+
TypeT = TypeVar("TypeT", bound=type)
6523+
6524+
TypeT1 = TypeVar("TypeT1", bound=Type[Base])
6525+
6526+
class C1:
6527+
def method(self, other: type) -> int:
6528+
if issubclass(other, Base):
6529+
reveal_type(other) # N: Revealed type is 'Type[__main__.Base]'
6530+
return other.field
6531+
return 0
6532+
6533+
class C2(Generic[TypeT]):
6534+
def method(self, other: TypeT) -> int:
6535+
if issubclass(other, Base):
6536+
reveal_type(other) # N: Revealed type is 'Type[__main__.Base]'
6537+
return other.field
6538+
return 0
6539+
6540+
class C3(Generic[TypeT1]):
6541+
def method(self, other: TypeT1) -> int:
6542+
if issubclass(other, Base):
6543+
reveal_type(other) # N: Revealed type is 'TypeT1`1'
6544+
return other.field
6545+
return 0
6546+
6547+
[builtins fixtures/isinstancelist.pyi]

0 commit comments

Comments
 (0)