Skip to content

Commit 15280bf

Browse files
TH3CHARLieilevkivskyi
authored andcommitted
Fix issubclass() to narrow down types of type variables (python#7930)
Resolves python#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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy