From 907f07b7ed199dca899265bd7e1cfaa38bd9378d Mon Sep 17 00:00:00 2001 From: elazar Date: Tue, 9 May 2017 09:41:48 +0300 Subject: [PATCH 1/4] Reverse subtype check, add checks for typevar --- mypy/subtypes.py | 2 +- test-data/unit/check-classes.test | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 8ca6421a0a91..a52f19890cfc 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -288,7 +288,7 @@ def visit_type_type(self, left: TypeType) -> bool: # treat builtins.object the same as Any. return True item = left.item - return isinstance(item, Instance) and is_subtype(item, right.type.metaclass_type) + return isinstance(item, Instance) and is_subtype(item.type.metaclass_type, right) return False diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 1dd3353ec903..f6981f0e19d6 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -3137,3 +3137,12 @@ class M(type): class A(metaclass=M): pass reveal_type(type(A).x) # E: Revealed type is 'builtins.int' +[case testMetaclassSubtype] +from typing import Type + +class M(type): pass +class A(metaclass=M): pass + +a: Type[A] = A +m: M = a +m2: M = A From 75d80738b1b2abc8b64225594b28602cbfb03b31 Mon Sep 17 00:00:00 2001 From: elazar Date: Tue, 9 May 2017 23:21:34 +0300 Subject: [PATCH 2/4] Don't do M <: Type[X] --- mypy/subtypes.py | 42 ++++++++++------------ test-data/unit/check-classes.test | 58 ++++++++++++++++++++++++++----- 2 files changed, 68 insertions(+), 32 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index a52f19890cfc..c4c29c33830d 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -69,11 +69,6 @@ def is_subtype(left: Type, right: Type, elif is_subtype_of_item: return True # otherwise, fall through - # Treat builtins.type the same as Type[Any] - elif is_named_instance(left, 'builtins.type'): - return is_subtype(TypeType(AnyType()), right) - elif is_named_instance(right, 'builtins.type'): - return is_subtype(left, TypeType(AnyType())) return left.accept(SubtypeVisitor(right, type_parameter_checker, ignore_pos_arg_names=ignore_pos_arg_names)) @@ -155,19 +150,18 @@ def visit_instance(self, left: Instance) -> bool: for lefta, righta, tvar in zip(t.args, right.args, right.type.defn.type_vars)) if isinstance(right, TypeType): - item = right.item - if isinstance(item, TupleType): - item = item.fallback - if isinstance(item, Instance): - return is_subtype(left, item.type.metaclass_type) - elif isinstance(item, AnyType): - # Special case: all metaclasses are subtypes of Type[Any] - mro = left.type.mro or [] - return any(base.fullname() == 'builtins.type' for base in mro) - else: - return False - else: - return False + if is_named_instance(left, 'builtins.type'): + return is_subtype(TypeType(AnyType()), right) + if left.type.is_metaclass(): + if isinstance(right.item, AnyType): + return True + if isinstance(right.item, Instance): + # Special-case enum since we don't have better way of expressing it + if (is_named_instance(left, 'enum.EnumMeta') + and is_named_instance(right.item, 'enum.Enum')): + return True + return is_named_instance(right.item, 'builtins.object') + return False def visit_type_var(self, left: TypeVarType) -> bool: right = self.right @@ -263,8 +257,7 @@ def visit_overloaded(self, left: Overloaded) -> bool: elif isinstance(right, TypeType): # All the items must have the same type object status, so # it's sufficient to query only (any) one of them. - # This is unsound, we don't check the __init__ signature. - return left.is_type_obj() and is_subtype(left.items()[0].ret_type, right.item) + return left.is_type_obj() and is_subtype(left.items()[0], right) else: return False @@ -284,11 +277,14 @@ def visit_type_type(self, left: TypeType) -> bool: # This is unsound, we don't check the __init__ signature. return is_subtype(left.item, right.ret_type) if isinstance(right, Instance): - if right.type.fullname() == 'builtins.object': - # treat builtins.object the same as Any. + if right.type.fullname() in ['builtins.object', 'builtins.type']: return True item = left.item - return isinstance(item, Instance) and is_subtype(item.type.metaclass_type, right) + if isinstance(item, TypeVarType): + item = item.upper_bound + if isinstance(item, Instance): + metaclass = item.type.metaclass_type + return metaclass is not None and is_subtype(metaclass, right) return False diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index f6981f0e19d6..4f44ccddf2fb 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -3065,13 +3065,14 @@ class M(type): class A(metaclass=M): pass +reveal_type(A[M]) # E: Revealed type is 'builtins.int' reveal_type(A[M]) # E: Revealed type is 'builtins.int' -[case testMetaclassSelftype] +[case testMetaclassSelfType] from typing import TypeVar, Type class M(type): pass -T = TypeVar('T', bound='A') +T = TypeVar('T') class M1(M): def foo(cls: Type[T]) -> T: ... @@ -3137,12 +3138,51 @@ class M(type): class A(metaclass=M): pass reveal_type(type(A).x) # E: Revealed type is 'builtins.int' -[case testMetaclassSubtype] -from typing import Type +[case testMetaclassStrictSupertypeOfTypeWithClassmethods] +from typing import Type, TypeVar +TA = TypeVar('TA', bound='A') +TTA = TypeVar('TTA', bound='Type[A]') +TM = TypeVar('TM', bound='M') -class M(type): pass -class A(metaclass=M): pass +class M(type): + 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' + 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' + def g3(cls: TTA) -> None: pass # E: The erased type of self 'Type[__main__.A]' is not a supertype of its class '__main__.M' + def g4(cls: TM) -> None: pass +m: M + +class A(metaclass=M): + def foo(self): pass -a: Type[A] = A -m: M = a -m2: M = A +# 3 examples of unsoundness - instantiation, classmethod, staticmethod and ClassVar: + +ta: Type[A] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[A]) +a: A = ta() +x: M = ta + +def r(ta: Type[TA], tta: TTA) -> None: + x: M = ta + y: M = tta + +class Class(metaclass=M): + @classmethod + def f1(cls: M): pass +cl: Type[Class] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[Class]) +cl.f1() +x1: M = cl + +class Static(metaclass=M): + @staticmethod + def f(): pass +s: Type[Static] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[Static]) +s.f() +x2: M = s + +from typing import ClassVar +class Cvar(metaclass=M): + x = 1 # type: ClassVar[int] +cv: Type[Cvar] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[Cvar]) +cv.x +x3: M = cv + +[builtins fixtures/classmethod.pyi] From 2209639f5c785eabc0e2ad077baeedb51cf3f84c Mon Sep 17 00:00:00 2001 From: elazar Date: Sat, 13 May 2017 20:56:05 +0300 Subject: [PATCH 3/4] fix some errors, extend test, don't remove comment --- mypy/subtypes.py | 12 ++++++++---- test-data/unit/check-classes.test | 28 ++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index c4c29c33830d..b03843fba9a4 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -150,17 +150,20 @@ def visit_instance(self, left: Instance) -> bool: for lefta, righta, tvar in zip(t.args, right.args, right.type.defn.type_vars)) if isinstance(right, TypeType): + item = right.item + if isinstance(item, TupleType): + item = item.fallback if is_named_instance(left, 'builtins.type'): return is_subtype(TypeType(AnyType()), right) if left.type.is_metaclass(): - if isinstance(right.item, AnyType): + if isinstance(item, AnyType): return True - if isinstance(right.item, Instance): + if isinstance(item, Instance): # Special-case enum since we don't have better way of expressing it if (is_named_instance(left, 'enum.EnumMeta') - and is_named_instance(right.item, 'enum.Enum')): + and is_named_instance(item, 'enum.Enum')): return True - return is_named_instance(right.item, 'builtins.object') + return is_named_instance(item, 'builtins.object') return False def visit_type_var(self, left: TypeVarType) -> bool: @@ -257,6 +260,7 @@ def visit_overloaded(self, left: Overloaded) -> bool: elif isinstance(right, TypeType): # All the items must have the same type object status, so # it's sufficient to query only (any) one of them. + # This is unsound, we don't check all the __init__ signatures. return left.is_type_obj() and is_subtype(left.items()[0], right) else: return False diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 4f44ccddf2fb..6e0639a9a07c 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -3065,7 +3065,6 @@ class M(type): class A(metaclass=M): pass -reveal_type(A[M]) # E: Revealed type is 'builtins.int' reveal_type(A[M]) # E: Revealed type is 'builtins.int' [case testMetaclassSelfType] @@ -3154,11 +3153,25 @@ m: M class A(metaclass=M): def foo(self): pass -# 3 examples of unsoundness - instantiation, classmethod, staticmethod and ClassVar: +A.g1() +A.g2() +A.g3() +A.g4() + +# 4 examples of unsoundness - instantiation, classmethod, staticmethod and ClassVar: ta: Type[A] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[A]) +ta.g1() +ta.g2() +ta.g4() +ta.g2() + a: A = ta() x: M = ta +x.g1() +x.g2() +x.g3() +x.g4() def r(ta: Type[TA], tta: TTA) -> None: x: M = ta @@ -3166,16 +3179,19 @@ def r(ta: Type[TA], tta: TTA) -> None: class Class(metaclass=M): @classmethod - def f1(cls: M): pass + def f1(cls: Type[Class]) -> None: pass + @classmethod + def f2(cls: M) -> None: pass cl: Type[Class] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[Class]) -cl.f1() +reveal_type(cl.f1) # E: Revealed type is 'def ()' +reveal_type(cl.f2) # E: Revealed type is 'def ()' x1: M = cl class Static(metaclass=M): @staticmethod - def f(): pass + def f() -> None: pass s: Type[Static] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[Static]) -s.f() +reveal_type(s.f) # E: Revealed type is 'def ()' x2: M = s from typing import ClassVar From 34b5817acd2516a94a21a785e4c59f537de0ec09 Mon Sep 17 00:00:00 2001 From: elazar Date: Tue, 30 May 2017 19:34:16 +0300 Subject: [PATCH 4/4] add tests, and prepare tests for checking-on-access --- test-data/unit/check-classes.test | 42 ++++++++++++++++++------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index c783b052dcb1..bf048c88edb4 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -3144,34 +3144,42 @@ TTA = TypeVar('TTA', bound='Type[A]') TM = TypeVar('TM', bound='M') class M(type): - 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' - 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' - def g3(cls: TTA) -> None: pass # E: The erased type of self 'Type[__main__.A]' is not a supertype of its class '__main__.M' - def g4(cls: TM) -> None: pass + 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' + 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' + def g3(cls: TTA) -> TTA: pass # E: The erased type of self 'Type[__main__.A]' is not a supertype of its class '__main__.M' + def g4(cls: TM) -> TM: pass m: M class A(metaclass=M): def foo(self): pass -A.g1() -A.g2() -A.g3() -A.g4() +reveal_type(A.g1) # E: Revealed type is 'def () -> __main__.A' +reveal_type(A.g2) # E: Revealed type is 'def () -> __main__.A*' +reveal_type(A.g3) # E: Revealed type is 'def () -> def () -> __main__.A' +reveal_type(A.g4) # E: Revealed type is 'def () -> def () -> __main__.A' + +class B(metaclass=M): + def foo(self): pass + +B.g1 # Should be error: Argument 0 to "g1" of "M" has incompatible type "B"; expected Type[A] +B.g2 # Should be error: Argument 0 to "g2" of "M" has incompatible type "B"; expected Type[TA] +B.g3 # Should be error: Argument 0 to "g3" of "M" has incompatible type "B"; expected "TTA" +reveal_type(B.g4) # E: Revealed type is 'def () -> def () -> __main__.B' # 4 examples of unsoundness - instantiation, classmethod, staticmethod and ClassVar: ta: Type[A] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[A]) -ta.g1() -ta.g2() -ta.g4() -ta.g2() - a: A = ta() +reveal_type(ta.g1) # E: Revealed type is 'def () -> __main__.A' +reveal_type(ta.g2) # E: Revealed type is 'def () -> __main__.A*' +reveal_type(ta.g3) # E: Revealed type is 'def () -> Type[__main__.A]' +reveal_type(ta.g4) # E: Revealed type is 'def () -> Type[__main__.A]' + x: M = ta -x.g1() -x.g2() -x.g3() -x.g4() +x.g1 # should be error: Argument 0 to "g1" of "M" has incompatible type "M"; expected Type[A] +x.g2 # should be error: Argument 0 to "g2" of "M" has incompatible type "M"; expected Type[TA] +x.g3 # should be error: Argument 0 to "g3" of "M" has incompatible type "M"; expected "TTA" +reveal_type(x.g4) # E: Revealed type is 'def () -> __main__.M*' def r(ta: Type[TA], tta: TTA) -> None: x: M = ta