diff --git a/mypy/checker.py b/mypy/checker.py index 2f0d37d626dc..7c7913fbeeb9 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3733,30 +3733,7 @@ def find_isinstance_check_helper(self, node: Expression) -> Tuple[TypeMap, TypeM return {}, {} expr = node.args[0] if literal(expr) == LITERAL_TYPE: - vartype = get_proper_type(type_map[expr]) - type = get_isinstance_type(node.args[1], type_map) - if isinstance(vartype, UnionType): - union_list = [] - for t in get_proper_types(vartype.items): - if isinstance(t, TypeType): - union_list.append(t.item) - else: - # This is an error that should be reported earlier - # if we reach here, we refuse to do any type inference. - return {}, {} - vartype = UnionType(union_list) - elif isinstance(vartype, TypeType): - vartype = vartype.item - elif (isinstance(vartype, Instance) and - vartype.type.fullname == 'builtins.type'): - vartype = self.named_type('builtins.object') - else: - # Any other object whose type we don't know precisely - # for example, Any or a custom metaclass. - return {}, {} # unknown type - yes_map, no_map = conditional_type_map(expr, vartype, type) - yes_map, no_map = map(convert_to_typetype, (yes_map, no_map)) - return yes_map, no_map + return self.infer_issubclass_maps(node, expr, type_map) elif refers_to_fullname(node.callee, 'builtins.callable'): if len(node.args) != 1: # the error will be reported elsewhere return {}, {} @@ -4367,6 +4344,39 @@ def push_type_map(self, type_map: 'TypeMap') -> None: for expr, type in type_map.items(): self.binder.put(expr, type) + def infer_issubclass_maps(self, node: CallExpr, + expr: Expression, + type_map: Dict[Expression, Type] + ) -> Tuple[TypeMap, TypeMap]: + """Infer type restrictions for an expression in issubclass call.""" + vartype = type_map[expr] + type = get_isinstance_type(node.args[1], type_map) + if isinstance(vartype, TypeVarType): + vartype = vartype.upper_bound + vartype = get_proper_type(vartype) + if isinstance(vartype, UnionType): + union_list = [] + for t in get_proper_types(vartype.items): + if isinstance(t, TypeType): + union_list.append(t.item) + else: + # This is an error that should be reported earlier + # if we reach here, we refuse to do any type inference. + return {}, {} + vartype = UnionType(union_list) + elif isinstance(vartype, TypeType): + vartype = vartype.item + elif (isinstance(vartype, Instance) and + vartype.type.fullname == 'builtins.type'): + vartype = self.named_type('builtins.object') + else: + # Any other object whose type we don't know precisely + # for example, Any or a custom metaclass. + return {}, {} # unknown type + yes_map, no_map = conditional_type_map(expr, vartype, type) + yes_map, no_map = map(convert_to_typetype, (yes_map, no_map)) + return yes_map, no_map + def conditional_type_map(expr: Expression, current_type: Optional[Type], diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 78121bc70dad..9e134d95cb97 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6512,3 +6512,36 @@ def access_after_declaration(self) -> None: reveal_type(x) # N: Revealed type is 'builtins.int' x = x + 1 + +[case testIsSubClassNarrowDownTypesOfTypeVariables] +from typing import Type, TypeVar, Generic + +class Base: + field: int = 42 + +TypeT = TypeVar("TypeT", bound=type) + +TypeT1 = TypeVar("TypeT1", bound=Type[Base]) + +class C1: + def method(self, other: type) -> int: + if issubclass(other, Base): + reveal_type(other) # N: Revealed type is 'Type[__main__.Base]' + return other.field + return 0 + +class C2(Generic[TypeT]): + def method(self, other: TypeT) -> int: + if issubclass(other, Base): + reveal_type(other) # N: Revealed type is 'Type[__main__.Base]' + return other.field + return 0 + +class C3(Generic[TypeT1]): + def method(self, other: TypeT1) -> int: + if issubclass(other, Base): + reveal_type(other) # N: Revealed type is 'TypeT1`1' + return other.field + return 0 + +[builtins fixtures/isinstancelist.pyi]