From 3249acc5b0cff344b0aac094ca3ec2867df27cb2 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 17 Feb 2019 12:39:25 +0000 Subject: [PATCH 1/5] Fix interaction of isinstance() with Type[...] --- mypy/checker.py | 5 ++- mypy/meet.py | 2 +- mypy/subtypes.py | 6 ++++ test-data/unit/check-classes.test | 51 +++++++++++++++++++++++++++++++ test-data/unit/check-unions.test | 4 +-- 5 files changed, 64 insertions(+), 4 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 758121f82dfa..0cca1c17a4ec 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3358,9 +3358,12 @@ def find_isinstance_check(self, node: Expression 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 Instance of type type + # 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)) diff --git a/mypy/meet.py b/mypy/meet.py index 10d5b051293a..6497f74f974d 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -49,7 +49,7 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type: for x in narrowed.relevant_items()]) elif isinstance(narrowed, AnyType): return narrowed - elif isinstance(declared, (Instance, TupleType)): + elif isinstance(declared, (Instance, TupleType, TypeType)): return meet_types(declared, narrowed) elif isinstance(declared, TypeType) and isinstance(narrowed, TypeType): return TypeType.make_normalized(narrow_declared_type(declared.item, narrowed.item)) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 83ebd468b9b3..f855a2b56df7 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1210,6 +1210,12 @@ def visit_type_type(self, left: TypeType) -> bool: return True if right.type.fullname() == 'builtins.object': return True + item = left.item + if isinstance(item, TypeVarType): + item = item.upper_bound + if isinstance(item, Instance): + metaclass = item.type.metaclass_type + return metaclass is not None and self._is_proper_subtype(metaclass, right) return False diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 6ee486935882..682cbf4ab7e4 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -5715,3 +5715,54 @@ class Test: def __init__(self) -> None: some_module = self.a [out] + +[case testIsInstanceTypeVsMetaclass] +from typing import Type +class Meta(type): + pass +class Thing(metaclass=Meta): + pass + +def foo(x: Type[Thing]) -> Type[Thing]: + assert isinstance(x, Meta) + return x +[builtins fixtures/isinstancelist.pyi] + +[case testIsInstanceTypeVsUnionOfType] +from typing import Type, Union + +class AA: pass +class AB: pass + +class M: pass + +class A(M, AA): pass +class B(M, AB): pass + +AOrB = Union[A, B] + +class T(object): + def __init__(self, typ: Type[AOrB] = A) -> None: + assert isinstance(typ, type(M)) + self.typ: Type[AOrB] = typ +[builtins fixtures/isinstancelist.pyi] + +[case testIsInstanceTypeIsSubclass] +from typing import Union, Type + +class C: ... + +x: Union[C, Type[C]] + +if isinstance(x, type) and issubclass(x, C): + reveal_type(x) # E: Revealed type is 'Type[__main__.C]' +[builtins fixtures/isinstancelist.pyi] + +[case testIsInstanceTypeByAssert] +class A: + x = 42 + +i: type = A +assert issubclass(i, A) +reveal_type(i.x) # E: Revealed type is 'builtins.int' +[builtins fixtures/isinstancelist.pyi] diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index da90bc6dcd2e..50c7b5260d33 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -410,8 +410,8 @@ def u(x: T, y: S) -> Union[S, T]: pass a: Any t_a: Type[A] -reveal_type(u(M(*a), t_a)) # E: Revealed type is 'Union[Type[__main__.A], __main__.M*]' -reveal_type(u(t_a, M(*a))) # E: Revealed type is 'Union[__main__.M*, Type[__main__.A]]' +reveal_type(u(M(*a), t_a)) # E: Revealed type is '__main__.M*' +reveal_type(u(t_a, M(*a))) # E: Revealed type is '__main__.M*' reveal_type(u(M2(*a), t_a)) # E: Revealed type is 'Union[Type[__main__.A], __main__.M2*]' reveal_type(u(t_a, M2(*a))) # E: Revealed type is 'Union[__main__.M2*, Type[__main__.A]]' From b51d5c4f7835f8df5da75be1c85e28430684c43f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 17 Feb 2019 12:48:10 +0000 Subject: [PATCH 2/5] Fix comments --- mypy/checker.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 0cca1c17a4ec..c59fe0a97a81 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3352,8 +3352,8 @@ def find_isinstance_check(self, node: Expression 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 + # 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): @@ -3362,8 +3362,8 @@ def find_isinstance_check(self, node: Expression 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 + # 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)) From 1755f680946669cb757b5c9948209a25a181dc09 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 21 Feb 2019 17:50:15 +0000 Subject: [PATCH 3/5] Fix some issues --- mypy/checker.py | 6 ++++- mypy/meet.py | 7 ++++-- test-data/unit/check-classes.test | 38 +++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index c59fe0a97a81..94ad1d4ea285 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3922,7 +3922,11 @@ def convert_to_typetype(type_map: TypeMap) -> TypeMap: if type_map is None: return None for expr, typ in type_map.items(): - if not isinstance(typ, (UnionType, Instance)): + t = typ + if isinstance(t, TypeVarType): + t = t.upper_bound + # TODO: should we only allow unions of instances as per PEP 484? + if not isinstance(t, (UnionType, Instance)): # unknown type; error was likely reported earlier return {} converted_type_map[expr] = TypeType.make_normalized(typ) diff --git a/mypy/meet.py b/mypy/meet.py index 6497f74f974d..90891c39f0cc 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -49,10 +49,10 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type: for x in narrowed.relevant_items()]) elif isinstance(narrowed, AnyType): return narrowed - elif isinstance(declared, (Instance, TupleType, TypeType)): - return meet_types(declared, narrowed) elif isinstance(declared, TypeType) and isinstance(narrowed, TypeType): return TypeType.make_normalized(narrow_declared_type(declared.item, narrowed.item)) + elif isinstance(declared, (Instance, TupleType, TypeType)): + return meet_types(declared, narrowed) return narrowed @@ -484,6 +484,9 @@ def visit_callable_type(self, t: CallableType) -> Type: # Return a plain None or instead of a weird function. return self.default(self.s) return result + elif isinstance(self.s, TypeType) and t.is_type_obj(): + # TODO: what if t is generic? + return TypeType.make_normalized(meet_types(self.s.item, t.ret_type)) elif isinstance(self.s, Instance) and self.s.type.is_protocol: call = unpack_callback_protocol(self.s) if call: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 682cbf4ab7e4..68cb74737392 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -5766,3 +5766,41 @@ i: type = A assert issubclass(i, A) reveal_type(i.x) # E: Revealed type is 'builtins.int' [builtins fixtures/isinstancelist.pyi] + +[case testIsInstanceTypeTypeVar] +from typing import Type, TypeVar, Generic + +class Base: ... +class Sub(Base): ... + +T = TypeVar('T', bound=Base) + +class C(Generic[T]): + def meth(self, cls: Type[T]) -> None: + if not issubclass(cls, Sub): + return + reveal_type(cls) # E: Revealed type is 'Type[__main__.Sub]' + def other(self, cls: Type[T]) -> None: + if not issubclass(cls, Sub): + return + reveal_type(cls) # E: Revealed type is 'Type[__main__.Sub]' + +[builtins fixtures/isinstancelist.pyi] + +[case testIsInstanceTypeSubclass] +# flags: --strict-optional +from typing import Type, Optional +class Base: ... +class One(Base): ... +class Other(Base): ... + +def test() -> None: + x: Optional[Type[Base]] + if int(): + x = One + elif int(): + x = Other + else: + return + reveal_type(x) # E: Revealed type is 'Union[Type[__main__.One], Type[__main__.Other]]' +[builtins fixtures/isinstancelist.pyi] From 2243722611288446280884e4b17de1457c68f87e Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 22 Feb 2019 19:01:43 +0000 Subject: [PATCH 4/5] Fix meet and make it symmetric --- mypy/meet.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/mypy/meet.py b/mypy/meet.py index 90891c39f0cc..efab8744e82c 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -484,9 +484,12 @@ def visit_callable_type(self, t: CallableType) -> Type: # Return a plain None or instead of a weird function. return self.default(self.s) return result - elif isinstance(self.s, TypeType) and t.is_type_obj(): - # TODO: what if t is generic? - return TypeType.make_normalized(meet_types(self.s.item, t.ret_type)) + elif isinstance(self.s, TypeType) and t.is_type_obj() and not t.is_generic(): + # In this case we are able to potentially produce a better meet. + res = meet_types(self.s.item, t.ret_type) + if not isinstance(res, (NoneTyp, UninhabitedType)): + return TypeType.make_normalized(res) + return self.default(self.s) elif isinstance(self.s, Instance) and self.s.type.is_protocol: call = unpack_callback_protocol(self.s) if call: @@ -570,6 +573,8 @@ def visit_type_type(self, t: TypeType) -> Type: return typ elif isinstance(self.s, Instance) and self.s.type.fullname() == 'builtins.type': return t + elif isinstance(self.s, CallableType): + return self.meet(t, self.s) else: return self.default(self.s) From a79fc87d050605d3baaf84e01ced05e63cd69c81 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 23 Feb 2019 00:54:46 +0000 Subject: [PATCH 5/5] Remove obsolete comment --- mypy/subtypes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index f855a2b56df7..d3886533eaeb 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1193,7 +1193,6 @@ def visit_partial_type(self, left: PartialType) -> bool: return False def visit_type_type(self, left: TypeType) -> bool: - # TODO: Handle metaclasses? right = self.right if isinstance(right, TypeType): # This is unsound, we don't check the __init__ signature.