Skip to content

Commit f60d874

Browse files
elazargilevkivskyi
authored andcommitted
Make Type[X] compatible with metaclass of X (#3346)
* Reverse subtype check, add checks for typevar * Don't do M <: Type[X] * fix some errors, extend test, don't remove comment * add tests, and prepare tests for checking-on-access
1 parent ff8b814 commit f60d874

File tree

2 files changed

+96
-22
lines changed

2 files changed

+96
-22
lines changed

mypy/subtypes.py

+20-20
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

@@ -158,16 +153,18 @@ def visit_instance(self, left: Instance) -> bool:
158153
item = right.item
159154
if isinstance(item, TupleType):
160155
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
156+
if is_named_instance(left, 'builtins.type'):
157+
return is_subtype(TypeType(AnyType()), right)
158+
if left.type.is_metaclass():
159+
if isinstance(item, AnyType):
160+
return True
161+
if isinstance(item, Instance):
162+
# Special-case enum since we don't have better way of expressing it
163+
if (is_named_instance(left, 'enum.EnumMeta')
164+
and is_named_instance(item, 'enum.Enum')):
165+
return True
166+
return is_named_instance(item, 'builtins.object')
167+
return False
171168

172169
def visit_type_var(self, left: TypeVarType) -> bool:
173170
right = self.right
@@ -263,8 +260,8 @@ def visit_overloaded(self, left: Overloaded) -> bool:
263260
elif isinstance(right, TypeType):
264261
# All the items must have the same type object status, so
265262
# 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)
263+
# This is unsound, we don't check all the __init__ signatures.
264+
return left.is_type_obj() and is_subtype(left.items()[0], right)
268265
else:
269266
return False
270267

@@ -284,11 +281,14 @@ def visit_type_type(self, left: TypeType) -> bool:
284281
# This is unsound, we don't check the __init__ signature.
285282
return is_subtype(left.item, right.ret_type)
286283
if isinstance(right, Instance):
287-
if right.type.fullname() == 'builtins.object':
288-
# treat builtins.object the same as Any.
284+
if right.type.fullname() in ['builtins.object', 'builtins.type']:
289285
return True
290286
item = left.item
291-
return isinstance(item, Instance) and is_subtype(item, right.type.metaclass_type)
287+
if isinstance(item, TypeVarType):
288+
item = item.upper_bound
289+
if isinstance(item, Instance):
290+
metaclass = item.type.metaclass_type
291+
return metaclass is not None and is_subtype(metaclass, right)
292292
return False
293293

294294

test-data/unit/check-classes.test

+76-2
Original file line numberDiff line numberDiff line change
@@ -3145,11 +3145,11 @@ class A(metaclass=M): pass
31453145

31463146
reveal_type(A[M]) # E: Revealed type is 'builtins.int'
31473147

3148-
[case testMetaclassSelftype]
3148+
[case testMetaclassSelfType]
31493149
from typing import TypeVar, Type
31503150

31513151
class M(type): pass
3152-
T = TypeVar('T', bound='A')
3152+
T = TypeVar('T')
31533153

31543154
class M1(M):
31553155
def foo(cls: Type[T]) -> T: ...
@@ -3215,6 +3215,80 @@ class M(type):
32153215
class A(metaclass=M): pass
32163216
reveal_type(type(A).x) # E: Revealed type is 'builtins.int'
32173217

3218+
[case testMetaclassStrictSupertypeOfTypeWithClassmethods]
3219+
from typing import Type, TypeVar
3220+
TA = TypeVar('TA', bound='A')
3221+
TTA = TypeVar('TTA', bound='Type[A]')
3222+
TM = TypeVar('TM', bound='M')
3223+
3224+
class M(type):
3225+
def g1(cls: 'Type[A]') -> A: pass # E: The erased type of self 'Type[__main__.A]' is not a supertype of its class '__main__.M'
3226+
def g2(cls: Type[TA]) -> TA: pass # E: The erased type of self 'Type[__main__.A]' is not a supertype of its class '__main__.M'
3227+
def g3(cls: TTA) -> TTA: pass # E: The erased type of self 'Type[__main__.A]' is not a supertype of its class '__main__.M'
3228+
def g4(cls: TM) -> TM: pass
3229+
m: M
3230+
3231+
class A(metaclass=M):
3232+
def foo(self): pass
3233+
3234+
reveal_type(A.g1) # E: Revealed type is 'def () -> __main__.A'
3235+
reveal_type(A.g2) # E: Revealed type is 'def () -> __main__.A*'
3236+
reveal_type(A.g3) # E: Revealed type is 'def () -> def () -> __main__.A'
3237+
reveal_type(A.g4) # E: Revealed type is 'def () -> def () -> __main__.A'
3238+
3239+
class B(metaclass=M):
3240+
def foo(self): pass
3241+
3242+
B.g1 # Should be error: Argument 0 to "g1" of "M" has incompatible type "B"; expected Type[A]
3243+
B.g2 # Should be error: Argument 0 to "g2" of "M" has incompatible type "B"; expected Type[TA]
3244+
B.g3 # Should be error: Argument 0 to "g3" of "M" has incompatible type "B"; expected "TTA"
3245+
reveal_type(B.g4) # E: Revealed type is 'def () -> def () -> __main__.B'
3246+
3247+
# 4 examples of unsoundness - instantiation, classmethod, staticmethod and ClassVar:
3248+
3249+
ta: Type[A] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[A])
3250+
a: A = ta()
3251+
reveal_type(ta.g1) # E: Revealed type is 'def () -> __main__.A'
3252+
reveal_type(ta.g2) # E: Revealed type is 'def () -> __main__.A*'
3253+
reveal_type(ta.g3) # E: Revealed type is 'def () -> Type[__main__.A]'
3254+
reveal_type(ta.g4) # E: Revealed type is 'def () -> Type[__main__.A]'
3255+
3256+
x: M = ta
3257+
x.g1 # should be error: Argument 0 to "g1" of "M" has incompatible type "M"; expected Type[A]
3258+
x.g2 # should be error: Argument 0 to "g2" of "M" has incompatible type "M"; expected Type[TA]
3259+
x.g3 # should be error: Argument 0 to "g3" of "M" has incompatible type "M"; expected "TTA"
3260+
reveal_type(x.g4) # E: Revealed type is 'def () -> __main__.M*'
3261+
3262+
def r(ta: Type[TA], tta: TTA) -> None:
3263+
x: M = ta
3264+
y: M = tta
3265+
3266+
class Class(metaclass=M):
3267+
@classmethod
3268+
def f1(cls: Type[Class]) -> None: pass
3269+
@classmethod
3270+
def f2(cls: M) -> None: pass
3271+
cl: Type[Class] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[Class])
3272+
reveal_type(cl.f1) # E: Revealed type is 'def ()'
3273+
reveal_type(cl.f2) # E: Revealed type is 'def ()'
3274+
x1: M = cl
3275+
3276+
class Static(metaclass=M):
3277+
@staticmethod
3278+
def f() -> None: pass
3279+
s: Type[Static] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[Static])
3280+
reveal_type(s.f) # E: Revealed type is 'def ()'
3281+
x2: M = s
3282+
3283+
from typing import ClassVar
3284+
class Cvar(metaclass=M):
3285+
x = 1 # type: ClassVar[int]
3286+
cv: Type[Cvar] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[Cvar])
3287+
cv.x
3288+
x3: M = cv
3289+
3290+
[builtins fixtures/classmethod.pyi]
3291+
32183292
-- Synthetic types crashes
32193293
-- -----------------------
32203294

0 commit comments

Comments
 (0)