From 796f5274b2967abb94db1bf52e5ab9326759d3e8 Mon Sep 17 00:00:00 2001 From: TH3CHARLie Date: Tue, 12 Nov 2019 09:44:16 +0800 Subject: [PATCH 1/7] add testcase --- test-data/unit/check-classes.test | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 78121bc70dad..c8fbd59cf26e 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6512,3 +6512,28 @@ def access_after_declaration(self) -> None: reveal_type(x) # N: Revealed type is 'builtins.int' x = x + 1 + +[case testIsSubClassNarrowDownTypesOfTypeVariables] +from typing import TypeVar, Generic + +TypeT = TypeVar("TypeT", bound=type) + +class IntBase: + field: int = 42 + +class IntFoo(Generic[TypeT]): + def method(self, other: TypeT) -> int: + if issubclass(other, IntBase): + return other.field + return 0 + +class StrBase: + field: str = "hello" + +class StrFoo(Generic[TypeT]): + def method(self, other: TypeT) -> str: + if issubclass(other, StrBase): + return other.field + return "hi" + +[builtins fixtures/isinstancelist.pyi] From 238bfa8d2c2f804013e6849135e2a69c0b836def Mon Sep 17 00:00:00 2001 From: TH3CHARLie Date: Tue, 12 Nov 2019 09:59:12 +0800 Subject: [PATCH 2/7] modify find_isinstance_check in checker.py to fix bug --- mypy/checker.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 557ceb8a71c0..7259cef15da5 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3745,6 +3745,8 @@ def find_isinstance_check(self, node: Expression elif (isinstance(vartype, Instance) and vartype.type.fullname() == 'builtins.type'): vartype = self.named_type('builtins.object') + elif (isinstance(vartype, TypeVarType)): + vartype = vartype.upper_bound else: # Any other object whose type we don't know precisely # for example, Any or a custom metaclass. From 5b35e9d8b20087652ce23f6aab54403c831711d1 Mon Sep 17 00:00:00 2001 From: TH3CHARLie Date: Wed, 13 Nov 2019 09:51:33 +0800 Subject: [PATCH 3/7] adjust branch statement in checker.py, modify testcase --- mypy/checker.py | 5 +++-- test-data/unit/check-classes.test | 21 ++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 7259cef15da5..7c6e1583db6a 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3730,6 +3730,9 @@ def find_isinstance_check(self, node: Expression if literal(expr) == LITERAL_TYPE: vartype = get_proper_type(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): @@ -3745,8 +3748,6 @@ def find_isinstance_check(self, node: Expression elif (isinstance(vartype, Instance) and vartype.type.fullname() == 'builtins.type'): vartype = self.named_type('builtins.object') - elif (isinstance(vartype, TypeVarType)): - vartype = vartype.upper_bound else: # Any other object whose type we don't know precisely # for example, Any or a custom metaclass. diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index c8fbd59cf26e..467f2d436cda 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6518,22 +6518,21 @@ from typing import TypeVar, Generic TypeT = TypeVar("TypeT", bound=type) -class IntBase: +class Base: field: int = 42 -class IntFoo(Generic[TypeT]): - def method(self, other: TypeT) -> int: - if issubclass(other, IntBase): +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 StrBase: - field: str = "hello" - -class StrFoo(Generic[TypeT]): - def method(self, other: TypeT) -> str: - if issubclass(other, StrBase): +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 "hi" + return 0 [builtins fixtures/isinstancelist.pyi] From df62cbc10529256d4aea1912236fab2509196cd0 Mon Sep 17 00:00:00 2001 From: TH3CHARLie Date: Wed, 13 Nov 2019 10:37:02 +0800 Subject: [PATCH 4/7] refactor issubclass check into helper function --- mypy/checker.py | 64 +++++++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 7c6e1583db6a..c401818dfb60 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3726,35 +3726,7 @@ def find_isinstance_check(self, node: Expression elif refers_to_fullname(node.callee, 'builtins.issubclass'): if len(node.args) != 2: # the error will be reported elsewhere 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, 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 + return self.infer_issubclass_maps(node, type_map) elif refers_to_fullname(node.callee, 'builtins.callable'): if len(node.args) != 1: # the error will be reported elsewhere return {}, {} @@ -4197,6 +4169,40 @@ 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, + type_map: Dict[Expression, Type] + ) -> Tuple[TypeMap, TypeMap]: + expr = node.args[0] + if literal(expr) == LITERAL_TYPE: + 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 + return {}, {} + def conditional_type_map(expr: Expression, current_type: Optional[Type], From 316cca73b582dd4543b6688f1d4bb45381ff8fbb Mon Sep 17 00:00:00 2001 From: TH3CHARLie Date: Wed, 13 Nov 2019 22:20:57 +0800 Subject: [PATCH 5/7] modify test by adding new class with different upper bound, add docstring for helper --- mypy/checker.py | 69 ++++++++++++++++--------------- test-data/unit/check-classes.test | 14 +++++++ 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index c401818dfb60..730d17bed183 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3726,7 +3726,9 @@ def find_isinstance_check(self, node: Expression elif refers_to_fullname(node.callee, 'builtins.issubclass'): if len(node.args) != 2: # the error will be reported elsewhere return {}, {} - return self.infer_issubclass_maps(node, type_map) + expr = node.args[0] + if literal(expr) == LITERAL_TYPE: + return self.infer_issubclass_maps_literal_type(node, expr, type_map) elif refers_to_fullname(node.callee, 'builtins.callable'): if len(node.args) != 1: # the error will be reported elsewhere return {}, {} @@ -4169,39 +4171,38 @@ 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, - type_map: Dict[Expression, Type] - ) -> Tuple[TypeMap, TypeMap]: - expr = node.args[0] - if literal(expr) == LITERAL_TYPE: - 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 - return {}, {} + def infer_issubclass_maps_literal_type(self, node: CallExpr, + expr: Expression, + type_map: Dict[Expression, Type] + ) -> Tuple[TypeMap, TypeMap]: + """Infer type maps for issubclass calls on literal type.""" + 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, diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 467f2d436cda..43b354ab0cc2 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6516,11 +6516,18 @@ def access_after_declaration(self) -> None: [case testIsSubClassNarrowDownTypesOfTypeVariables] from typing import TypeVar, Generic +class Foo: + int_val: int = 42 + str_val: str = "hello" + TypeT = TypeVar("TypeT", bound=type) +TypeT1 = TypeVar("TypeT1", bound=Foo) + class Base: field: int = 42 + class C1: def method(self, other: type) -> int: if issubclass(other, Base): @@ -6535,4 +6542,11 @@ class C2(Generic[TypeT]): return other.field return 0 +class C3(Generic[TypeT1]): + 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 + [builtins fixtures/isinstancelist.pyi] From e987e420e6f3c17c06be289f2fedb1ae9cf27384 Mon Sep 17 00:00:00 2001 From: TH3CHARLie Date: Wed, 13 Nov 2019 23:50:10 +0800 Subject: [PATCH 6/7] modify testcase, modify helper function name --- mypy/checker.py | 12 ++++++------ test-data/unit/check-classes.test | 17 ++++++----------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 730d17bed183..e1963b34e1e6 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3728,7 +3728,7 @@ def find_isinstance_check(self, node: Expression return {}, {} expr = node.args[0] if literal(expr) == LITERAL_TYPE: - return self.infer_issubclass_maps_literal_type(node, expr, type_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 {}, {} @@ -4171,11 +4171,11 @@ 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_literal_type(self, node: CallExpr, - expr: Expression, - type_map: Dict[Expression, Type] - ) -> Tuple[TypeMap, TypeMap]: - """Infer type maps for issubclass calls on literal 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)): diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 43b354ab0cc2..9e134d95cb97 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6514,19 +6514,14 @@ def access_after_declaration(self) -> None: x = x + 1 [case testIsSubClassNarrowDownTypesOfTypeVariables] -from typing import TypeVar, Generic - -class Foo: - int_val: int = 42 - str_val: str = "hello" - -TypeT = TypeVar("TypeT", bound=type) - -TypeT1 = TypeVar("TypeT1", bound=Foo) +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: @@ -6543,9 +6538,9 @@ class C2(Generic[TypeT]): return 0 class C3(Generic[TypeT1]): - def method(self, other: TypeT) -> int: + def method(self, other: TypeT1) -> int: if issubclass(other, Base): - reveal_type(other) # N: Revealed type is 'Type[__main__.Base]' + reveal_type(other) # N: Revealed type is 'TypeT1`1' return other.field return 0 From 8ab0d96b8d2d53557c65e1b60d76a8396c9dd5e7 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 14 Nov 2019 14:48:08 +0000 Subject: [PATCH 7/7] Remove redundant parens --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index e1963b34e1e6..4bb5d031be83 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4178,7 +4178,7 @@ def infer_issubclass_maps(self, node: CallExpr, """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)): + if isinstance(vartype, TypeVarType): vartype = vartype.upper_bound vartype = get_proper_type(vartype) if isinstance(vartype, UnionType):