Skip to content

Commit 75d8073

Browse files
committed
Don't do M <: Type[X]
1 parent 907f07b commit 75d8073

File tree

2 files changed

+68
-32
lines changed

2 files changed

+68
-32
lines changed

mypy/subtypes.py

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,6 @@ def is_subtype(left: Type, right: Type,
6969
elif is_subtype_of_item:
7070
return True
7171
# otherwise, fall through
72-
# Treat builtins.type the same as Type[Any]
73-
elif is_named_instance(left, 'builtins.type'):
74-
return is_subtype(TypeType(AnyType()), right)
75-
elif is_named_instance(right, 'builtins.type'):
76-
return is_subtype(left, TypeType(AnyType()))
7772
return left.accept(SubtypeVisitor(right, type_parameter_checker,
7873
ignore_pos_arg_names=ignore_pos_arg_names))
7974

@@ -155,19 +150,18 @@ def visit_instance(self, left: Instance) -> bool:
155150
for lefta, righta, tvar in
156151
zip(t.args, right.args, right.type.defn.type_vars))
157152
if isinstance(right, TypeType):
158-
item = right.item
159-
if isinstance(item, TupleType):
160-
item = item.fallback
161-
if isinstance(item, Instance):
162-
return is_subtype(left, item.type.metaclass_type)
163-
elif isinstance(item, AnyType):
164-
# Special case: all metaclasses are subtypes of Type[Any]
165-
mro = left.type.mro or []
166-
return any(base.fullname() == 'builtins.type' for base in mro)
167-
else:
168-
return False
169-
else:
170-
return False
153+
if is_named_instance(left, 'builtins.type'):
154+
return is_subtype(TypeType(AnyType()), right)
155+
if left.type.is_metaclass():
156+
if isinstance(right.item, AnyType):
157+
return True
158+
if isinstance(right.item, Instance):
159+
# Special-case enum since we don't have better way of expressing it
160+
if (is_named_instance(left, 'enum.EnumMeta')
161+
and is_named_instance(right.item, 'enum.Enum')):
162+
return True
163+
return is_named_instance(right.item, 'builtins.object')
164+
return False
171165

172166
def visit_type_var(self, left: TypeVarType) -> bool:
173167
right = self.right
@@ -263,8 +257,7 @@ def visit_overloaded(self, left: Overloaded) -> bool:
263257
elif isinstance(right, TypeType):
264258
# All the items must have the same type object status, so
265259
# it's sufficient to query only (any) one of them.
266-
# This is unsound, we don't check the __init__ signature.
267-
return left.is_type_obj() and is_subtype(left.items()[0].ret_type, right.item)
260+
return left.is_type_obj() and is_subtype(left.items()[0], right)
268261
else:
269262
return False
270263

@@ -284,11 +277,14 @@ def visit_type_type(self, left: TypeType) -> bool:
284277
# This is unsound, we don't check the __init__ signature.
285278
return is_subtype(left.item, right.ret_type)
286279
if isinstance(right, Instance):
287-
if right.type.fullname() == 'builtins.object':
288-
# treat builtins.object the same as Any.
280+
if right.type.fullname() in ['builtins.object', 'builtins.type']:
289281
return True
290282
item = left.item
291-
return isinstance(item, Instance) and is_subtype(item.type.metaclass_type, right)
283+
if isinstance(item, TypeVarType):
284+
item = item.upper_bound
285+
if isinstance(item, Instance):
286+
metaclass = item.type.metaclass_type
287+
return metaclass is not None and is_subtype(metaclass, right)
292288
return False
293289

294290

test-data/unit/check-classes.test

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3065,13 +3065,14 @@ class M(type):
30653065

30663066
class A(metaclass=M): pass
30673067

3068+
reveal_type(A[M]) # E: Revealed type is 'builtins.int'
30683069
reveal_type(A[M]) # E: Revealed type is 'builtins.int'
30693070

3070-
[case testMetaclassSelftype]
3071+
[case testMetaclassSelfType]
30713072
from typing import TypeVar, Type
30723073

30733074
class M(type): pass
3074-
T = TypeVar('T', bound='A')
3075+
T = TypeVar('T')
30753076

30763077
class M1(M):
30773078
def foo(cls: Type[T]) -> T: ...
@@ -3137,12 +3138,51 @@ class M(type):
31373138
class A(metaclass=M): pass
31383139
reveal_type(type(A).x) # E: Revealed type is 'builtins.int'
31393140

3140-
[case testMetaclassSubtype]
3141-
from typing import Type
3141+
[case testMetaclassStrictSupertypeOfTypeWithClassmethods]
3142+
from typing import Type, TypeVar
3143+
TA = TypeVar('TA', bound='A')
3144+
TTA = TypeVar('TTA', bound='Type[A]')
3145+
TM = TypeVar('TM', bound='M')
31423146

3143-
class M(type): pass
3144-
class A(metaclass=M): pass
3147+
class M(type):
3148+
def g1(cls: 'Type[A]') -> None: pass # E: The erased type of self 'Type[__main__.A]' is not a supertype of its class '__main__.M'
3149+
def g2(cls: Type[TA]) -> None: pass # E: The erased type of self 'Type[__main__.A]' is not a supertype of its class '__main__.M'
3150+
def g3(cls: TTA) -> None: pass # E: The erased type of self 'Type[__main__.A]' is not a supertype of its class '__main__.M'
3151+
def g4(cls: TM) -> None: pass
3152+
m: M
3153+
3154+
class A(metaclass=M):
3155+
def foo(self): pass
31453156

3146-
a: Type[A] = A
3147-
m: M = a
3148-
m2: M = A
3157+
# 3 examples of unsoundness - instantiation, classmethod, staticmethod and ClassVar:
3158+
3159+
ta: Type[A] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[A])
3160+
a: A = ta()
3161+
x: M = ta
3162+
3163+
def r(ta: Type[TA], tta: TTA) -> None:
3164+
x: M = ta
3165+
y: M = tta
3166+
3167+
class Class(metaclass=M):
3168+
@classmethod
3169+
def f1(cls: M): pass
3170+
cl: Type[Class] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[Class])
3171+
cl.f1()
3172+
x1: M = cl
3173+
3174+
class Static(metaclass=M):
3175+
@staticmethod
3176+
def f(): pass
3177+
s: Type[Static] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[Static])
3178+
s.f()
3179+
x2: M = s
3180+
3181+
from typing import ClassVar
3182+
class Cvar(metaclass=M):
3183+
x = 1 # type: ClassVar[int]
3184+
cv: Type[Cvar] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[Cvar])
3185+
cv.x
3186+
x3: M = cv
3187+
3188+
[builtins fixtures/classmethod.pyi]

0 commit comments

Comments
 (0)