diff --git a/mypy/checker.py b/mypy/checker.py index f3dc5d75f06d..97547b331a85 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3353,15 +3353,18 @@ 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): 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 + # 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)) @@ -3920,7 +3923,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 10d5b051293a..efab8744e82c 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)): - 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,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() 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: @@ -567,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) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 83ebd468b9b3..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. @@ -1210,6 +1209,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..68cb74737392 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -5715,3 +5715,92 @@ 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] + +[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] 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]]'