Skip to content

Commit ed0dcad

Browse files
authored
Fix interaction of isinstance() with Type[...] (#6419)
Fixes #4616 Fixes #6416 Fixes #6386 Currently `Type[C]` is considered a subtype of metaclass of `C`, and I think this is right. The idea is to fix `ProperSubtypeVisitor.visit_type_type()` to match `SubtypeVisitor.visit_type_type()` (plus couple small updates to make the former used by `isinstance()` and `issubclass()`).
1 parent b37965e commit ed0dcad

File tree

5 files changed

+119
-10
lines changed

5 files changed

+119
-10
lines changed

mypy/checker.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3356,15 +3356,18 @@ def find_isinstance_check(self, node: Expression
33563356
if isinstance(t, TypeType):
33573357
union_list.append(t.item)
33583358
else:
3359-
# this is an error that should be reported earlier
3360-
# if we reach here, we refuse to do any type inference
3359+
# This is an error that should be reported earlier
3360+
# if we reach here, we refuse to do any type inference.
33613361
return {}, {}
33623362
vartype = UnionType(union_list)
33633363
elif isinstance(vartype, TypeType):
33643364
vartype = vartype.item
3365+
elif (isinstance(vartype, Instance) and
3366+
vartype.type.fullname() == 'builtins.type'):
3367+
vartype = self.named_type('builtins.object')
33653368
else:
3366-
# any other object whose type we don't know precisely
3367-
# for example, Any or Instance of type type
3369+
# Any other object whose type we don't know precisely
3370+
# for example, Any or a custom metaclass.
33683371
return {}, {} # unknown type
33693372
yes_map, no_map = conditional_type_map(expr, vartype, type)
33703373
yes_map, no_map = map(convert_to_typetype, (yes_map, no_map))
@@ -3923,7 +3926,11 @@ def convert_to_typetype(type_map: TypeMap) -> TypeMap:
39233926
if type_map is None:
39243927
return None
39253928
for expr, typ in type_map.items():
3926-
if not isinstance(typ, (UnionType, Instance)):
3929+
t = typ
3930+
if isinstance(t, TypeVarType):
3931+
t = t.upper_bound
3932+
# TODO: should we only allow unions of instances as per PEP 484?
3933+
if not isinstance(t, (UnionType, Instance)):
39273934
# unknown type; error was likely reported earlier
39283935
return {}
39293936
converted_type_map[expr] = TypeType.make_normalized(typ)

mypy/meet.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type:
5050
for x in narrowed.relevant_items()])
5151
elif isinstance(narrowed, AnyType):
5252
return narrowed
53-
elif isinstance(declared, (Instance, TupleType)):
54-
return meet_types(declared, narrowed)
5553
elif isinstance(declared, TypeType) and isinstance(narrowed, TypeType):
5654
return TypeType.make_normalized(narrow_declared_type(declared.item, narrowed.item))
55+
elif isinstance(declared, (Instance, TupleType, TypeType)):
56+
return meet_types(declared, narrowed)
5757
return narrowed
5858

5959

@@ -485,6 +485,12 @@ def visit_callable_type(self, t: CallableType) -> Type:
485485
# Return a plain None or <uninhabited> instead of a weird function.
486486
return self.default(self.s)
487487
return result
488+
elif isinstance(self.s, TypeType) and t.is_type_obj() and not t.is_generic():
489+
# In this case we are able to potentially produce a better meet.
490+
res = meet_types(self.s.item, t.ret_type)
491+
if not isinstance(res, (NoneTyp, UninhabitedType)):
492+
return TypeType.make_normalized(res)
493+
return self.default(self.s)
488494
elif isinstance(self.s, Instance) and self.s.type.is_protocol:
489495
call = unpack_callback_protocol(self.s)
490496
if call:
@@ -568,6 +574,8 @@ def visit_type_type(self, t: TypeType) -> Type:
568574
return typ
569575
elif isinstance(self.s, Instance) and self.s.type.fullname() == 'builtins.type':
570576
return t
577+
elif isinstance(self.s, CallableType):
578+
return self.meet(t, self.s)
571579
else:
572580
return self.default(self.s)
573581

mypy/subtypes.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1205,7 +1205,6 @@ def visit_partial_type(self, left: PartialType) -> bool:
12051205
return False
12061206

12071207
def visit_type_type(self, left: TypeType) -> bool:
1208-
# TODO: Handle metaclasses?
12091208
right = self.right
12101209
if isinstance(right, TypeType):
12111210
# This is unsound, we don't check the __init__ signature.
@@ -1222,6 +1221,12 @@ def visit_type_type(self, left: TypeType) -> bool:
12221221
return True
12231222
if right.type.fullname() == 'builtins.object':
12241223
return True
1224+
item = left.item
1225+
if isinstance(item, TypeVarType):
1226+
item = item.upper_bound
1227+
if isinstance(item, Instance):
1228+
metaclass = item.type.metaclass_type
1229+
return metaclass is not None and self._is_proper_subtype(metaclass, right)
12251230
return False
12261231

12271232

test-data/unit/check-classes.test

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5715,3 +5715,92 @@ class Test:
57155715
def __init__(self) -> None:
57165716
some_module = self.a
57175717
[out]
5718+
5719+
[case testIsInstanceTypeVsMetaclass]
5720+
from typing import Type
5721+
class Meta(type):
5722+
pass
5723+
class Thing(metaclass=Meta):
5724+
pass
5725+
5726+
def foo(x: Type[Thing]) -> Type[Thing]:
5727+
assert isinstance(x, Meta)
5728+
return x
5729+
[builtins fixtures/isinstancelist.pyi]
5730+
5731+
[case testIsInstanceTypeVsUnionOfType]
5732+
from typing import Type, Union
5733+
5734+
class AA: pass
5735+
class AB: pass
5736+
5737+
class M: pass
5738+
5739+
class A(M, AA): pass
5740+
class B(M, AB): pass
5741+
5742+
AOrB = Union[A, B]
5743+
5744+
class T(object):
5745+
def __init__(self, typ: Type[AOrB] = A) -> None:
5746+
assert isinstance(typ, type(M))
5747+
self.typ: Type[AOrB] = typ
5748+
[builtins fixtures/isinstancelist.pyi]
5749+
5750+
[case testIsInstanceTypeIsSubclass]
5751+
from typing import Union, Type
5752+
5753+
class C: ...
5754+
5755+
x: Union[C, Type[C]]
5756+
5757+
if isinstance(x, type) and issubclass(x, C):
5758+
reveal_type(x) # E: Revealed type is 'Type[__main__.C]'
5759+
[builtins fixtures/isinstancelist.pyi]
5760+
5761+
[case testIsInstanceTypeByAssert]
5762+
class A:
5763+
x = 42
5764+
5765+
i: type = A
5766+
assert issubclass(i, A)
5767+
reveal_type(i.x) # E: Revealed type is 'builtins.int'
5768+
[builtins fixtures/isinstancelist.pyi]
5769+
5770+
[case testIsInstanceTypeTypeVar]
5771+
from typing import Type, TypeVar, Generic
5772+
5773+
class Base: ...
5774+
class Sub(Base): ...
5775+
5776+
T = TypeVar('T', bound=Base)
5777+
5778+
class C(Generic[T]):
5779+
def meth(self, cls: Type[T]) -> None:
5780+
if not issubclass(cls, Sub):
5781+
return
5782+
reveal_type(cls) # E: Revealed type is 'Type[__main__.Sub]'
5783+
def other(self, cls: Type[T]) -> None:
5784+
if not issubclass(cls, Sub):
5785+
return
5786+
reveal_type(cls) # E: Revealed type is 'Type[__main__.Sub]'
5787+
5788+
[builtins fixtures/isinstancelist.pyi]
5789+
5790+
[case testIsInstanceTypeSubclass]
5791+
# flags: --strict-optional
5792+
from typing import Type, Optional
5793+
class Base: ...
5794+
class One(Base): ...
5795+
class Other(Base): ...
5796+
5797+
def test() -> None:
5798+
x: Optional[Type[Base]]
5799+
if int():
5800+
x = One
5801+
elif int():
5802+
x = Other
5803+
else:
5804+
return
5805+
reveal_type(x) # E: Revealed type is 'Union[Type[__main__.One], Type[__main__.Other]]'
5806+
[builtins fixtures/isinstancelist.pyi]

test-data/unit/check-unions.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,8 +410,8 @@ def u(x: T, y: S) -> Union[S, T]: pass
410410
a: Any
411411
t_a: Type[A]
412412

413-
reveal_type(u(M(*a), t_a)) # E: Revealed type is 'Union[Type[__main__.A], __main__.M*]'
414-
reveal_type(u(t_a, M(*a))) # E: Revealed type is 'Union[__main__.M*, Type[__main__.A]]'
413+
reveal_type(u(M(*a), t_a)) # E: Revealed type is '__main__.M*'
414+
reveal_type(u(t_a, M(*a))) # E: Revealed type is '__main__.M*'
415415

416416
reveal_type(u(M2(*a), t_a)) # E: Revealed type is 'Union[Type[__main__.A], __main__.M2*]'
417417
reveal_type(u(t_a, M2(*a))) # E: Revealed type is 'Union[__main__.M2*, Type[__main__.A]]'

0 commit comments

Comments
 (0)