From b20cfa4b98fb516e99dec1e31b5ec73f762c1bc5 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 2 Nov 2016 23:50:58 +0200 Subject: [PATCH 01/18] checker --- mypy/checker.py | 23 ++++++++++++++--------- mypy/checkexpr.py | 3 +-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 318a01689827..09d83bbaf4ae 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1433,6 +1433,8 @@ def split_around_star(self, items: List[T], star_index: int, return (left, star, right) def type_is_iterable(self, type: Type) -> bool: + if isinstance(type, CallableType) and type.is_type_obj(): + type = type.fallback return (is_subtype(type, self.named_generic_type('typing.Iterable', [AnyType()])) and isinstance(type, Instance)) @@ -2027,11 +2029,14 @@ def analyze_iterable_item_type(self, expr: Expression) -> Type: return joined else: # Non-tuple iterable. - self.check_subtype(iterable, - self.named_generic_type('typing.Iterable', - [AnyType()]), - expr, messages.ITERABLE_EXPECTED) - + if isinstance(iterable, CallableType) and iterable.is_type_obj(): + self.check_subtype(iterable.fallback, + self.named_generic_type('typing.Iterable', [AnyType()]), + expr, messages.ITERABLE_EXPECTED) + else: + self.check_subtype(iterable, + self.named_generic_type('typing.Iterable', [AnyType()]), + expr, messages.ITERABLE_EXPECTED) echk = self.expr_checker method = echk.analyze_external_member_access('__iter__', iterable, expr) @@ -2184,6 +2189,10 @@ def visit_call_expr(self, e: CallExpr) -> Type: def visit_yield_from_expr(self, e: YieldFromExpr) -> Type: return self.expr_checker.visit_yield_from_expr(e) + def has_coroutine_decorator(self, t: Type) -> bool: + """Whether t came from a function decorated with `@coroutine`.""" + return isinstance(t, Instance) and t.type.fullname() == 'typing.AwaitableGenerator' + def visit_member_expr(self, e: MemberExpr) -> Type: return self.expr_checker.visit_member_expr(e) @@ -2362,10 +2371,6 @@ def lookup_typeinfo(self, fullname: str) -> TypeInfo: sym = self.lookup_qualified(fullname) return cast(TypeInfo, sym.node) - def type_type(self) -> Instance: - """Return instance type 'type'.""" - return self.named_type('builtins.type') - def object_type(self) -> Instance: """Return instance type 'object'.""" return self.named_type('builtins.object') diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index fa77425db204..90df8698e1b9 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2099,8 +2099,7 @@ def visit_yield_from_expr(self, e: YieldFromExpr) -> Type: # by __iter__. if isinstance(subexpr_type, AnyType): iter_type = AnyType() - elif (isinstance(subexpr_type, Instance) and - is_subtype(subexpr_type, self.chk.named_type('typing.Iterable'))): + elif self.chk.type_is_iterable(subexpr_type): if is_async_def(subexpr_type) and not has_coroutine_decorator(return_type): self.chk.msg.yield_from_invalid_operand_type(subexpr_type, e) iter_method_type = self.analyze_external_member_access( From 7eee7641e2f70e89b7b57d66c95c47275b3cb965 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 2 Nov 2016 23:53:06 +0200 Subject: [PATCH 02/18] checkexpr and checkmember --- mypy/checkmember.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 456ad6b67b17..e3ad6ac35328 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -184,6 +184,8 @@ def analyze_member_access(name: str, if result: return result fallback = builtin_type('builtins.type') + if item is not None: + fallback = item.type.metaclass_type or fallback return analyze_member_access(name, fallback, node, is_lvalue, is_super, is_operator, builtin_type, not_ready_callback, msg, original_type=original_type, chk=chk) @@ -450,7 +452,7 @@ def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) -> # Must be an invalid class definition. return AnyType() else: - fallback = builtin_type('builtins.type') + fallback = info.metaclass_type or builtin_type('builtins.type') if init_method.info.fullname() == 'builtins.object': # No non-default __init__ -> look at __new__ instead. new_method = info.get_method('__new__') From 6ab73681e3a99457035461d9cf591c48d296dae3 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 2 Nov 2016 23:56:43 +0200 Subject: [PATCH 03/18] nodes --- mypy/nodes.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/mypy/nodes.py b/mypy/nodes.py index c9485fe66d56..06df3dba2381 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1946,6 +1946,29 @@ def __init__(self, names: 'SymbolTable', defn: ClassDef, module_name: str) -> No for vd in defn.type_vars: self.type_vars.append(vd.name) + _metaclass_type = None # type: Optional[mypy.types.Instance] + + @property + def metaclass_type(self) -> 'Optional[mypy.types.Instance]': + if self._metaclass_type: + return self._metaclass_type + if self._fullname == 'builtins.type': + return mypy.types.Instance(self, []) + if self.mro is None: + # XXX why does this happen? + return None + if len(self.mro) > 1: + return self.mro[1].metaclass_type + # FIX: assert False + return None + + @metaclass_type.setter + def metaclass_type(self, value: 'mypy.types.Instance'): + self._metaclass_type = value + + def is_metaclass(self) -> bool: + return self.has_base('builtins.type') + def name(self) -> str: """Short name.""" return self.defn.name From 0d0e9ff83cef8aca39dc9fbe48d22c44410a4646 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 2 Nov 2016 23:57:51 +0200 Subject: [PATCH 04/18] semanal --- mypy/semanal.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 71a8323be292..f2d7a31d7fae 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -904,8 +904,13 @@ def analyze_metaclass(self, defn: ClassDef) -> None: if defn.metaclass == '': self.fail("Dynamic metaclass not supported for '%s'" % defn.name, defn) return - sym = self.lookup_qualified(defn.metaclass, defn) - if sym is not None and not isinstance(sym.node, TypeInfo): + sym = self.lookup(defn.metaclass, defn) + if sym is not None and isinstance(sym.node, TypeInfo): + inst = fill_typevars(sym.node) + assert isinstance(inst, Instance) + defn.info.metaclass_type = inst + return + else: self.fail("Invalid metaclass '%s'" % defn.metaclass, defn) def object_type(self) -> Instance: From 41ecc8469a7d69d63a9757ed5ff5e4574b975739 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 2 Nov 2016 23:59:17 +0200 Subject: [PATCH 05/18] types --- mypy/types.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/types.py b/mypy/types.py index 3d27d52d8a87..4275403f5844 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -643,7 +643,8 @@ def copy_modified(self, ) def is_type_obj(self) -> bool: - return self.fallback.type is not None and self.fallback.type.fullname() == 'builtins.type' + t = self.fallback.type + return t is not None and t.is_metaclass() def is_concrete_type_obj(self) -> bool: return self.is_type_obj() and self.is_classmethod_class From 6dc66b2f24b3d1c8de0303bb5d06657ddfe83899 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 3 Nov 2016 00:20:25 +0200 Subject: [PATCH 06/18] testcheck --- test-data/unit/lib-stub/abc.pyi | 2 +- test-data/unit/semanal-errors.test | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test-data/unit/lib-stub/abc.pyi b/test-data/unit/lib-stub/abc.pyi index 4afe734d29c1..9208f42e9635 100644 --- a/test-data/unit/lib-stub/abc.pyi +++ b/test-data/unit/lib-stub/abc.pyi @@ -1,3 +1,3 @@ -class ABCMeta: pass +class ABCMeta(type): pass abstractmethod = object() abstractproperty = object() diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index aa45dde1faca..6e91427da7e0 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -962,13 +962,17 @@ class A(Generic[T], Generic[S]): pass \ [out] [case testInvalidMetaclass] -class A(metaclass=x): pass # E: Name 'x' is not defined +class A(metaclass=x): pass [out] +main:3: error: Name 'x' is not defined +main:3: error: Invalid metaclass 'x' [case testInvalidQualifiedMetaclass] import abc -class A(metaclass=abc.Foo): pass # E: Name 'abc.Foo' is not defined +class A(metaclass=abc.Foo): pass [out] +main:3: error: Name 'abc.Foo' is not defined +main:3: error: Invalid metaclass 'abc.Foo' [case testNonClassMetaclass] def f(): pass From 314a300954d63d24308b596ca9258155f04fac82 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 3 Nov 2016 02:13:43 +0200 Subject: [PATCH 07/18] fix name lookup and parse errors --- mypy/semanal.py | 2 +- test-data/unit/semanal-errors.test | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index f2d7a31d7fae..320cb0b6cd7f 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -904,7 +904,7 @@ def analyze_metaclass(self, defn: ClassDef) -> None: if defn.metaclass == '': self.fail("Dynamic metaclass not supported for '%s'" % defn.name, defn) return - sym = self.lookup(defn.metaclass, defn) + sym = self.lookup_qualified(defn.metaclass, defn) if sym is not None and isinstance(sym.node, TypeInfo): inst = fill_typevars(sym.node) assert isinstance(inst, Instance) diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index 6e91427da7e0..d948eb524119 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -964,15 +964,15 @@ class A(Generic[T], Generic[S]): pass \ [case testInvalidMetaclass] class A(metaclass=x): pass [out] -main:3: error: Name 'x' is not defined -main:3: error: Invalid metaclass 'x' +main:1: error: Name 'x' is not defined +main:1: error: Invalid metaclass 'x' [case testInvalidQualifiedMetaclass] import abc class A(metaclass=abc.Foo): pass [out] -main:3: error: Name 'abc.Foo' is not defined -main:3: error: Invalid metaclass 'abc.Foo' +main:2: error: Name 'abc.Foo' is not defined +main:2: error: Invalid metaclass 'abc.Foo' [case testNonClassMetaclass] def f(): pass From 3a053616e3a62d9ebade34a33e2ae8a14f879107 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 14 Dec 2016 01:49:59 +0200 Subject: [PATCH 08/18] Follow metaclass resollution as described in docs https://docs.python.org/3/reference/datamodel.html#determining-the-appropriate-metaclass --- mypy/nodes.py | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 06df3dba2381..89a02cb5d799 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1946,25 +1946,41 @@ def __init__(self, names: 'SymbolTable', defn: ClassDef, module_name: str) -> No for vd in defn.type_vars: self.type_vars.append(vd.name) - _metaclass_type = None # type: Optional[mypy.types.Instance] + declared_metaclass_type = None # type: Optional[mypy.types.Instance] @property def metaclass_type(self) -> 'Optional[mypy.types.Instance]': - if self._metaclass_type: - return self._metaclass_type - if self._fullname == 'builtins.type': - return mypy.types.Instance(self, []) + """ (From the docs) + The appropriate metaclass is determined as follows: + * if no bases and no explicit metaclass are given, then type() is used + * if an explicit metaclass is given and it is not an instance of type(), + then it is used directly as the metaclass + * if an instance of type() is given as the explicit metaclass, or bases are defined, + then the most derived metaclass is used + + The most derived metaclass is selected from the explicitly specified metaclass (if any) + and the metaclasses (i.e. type(cls)) of all specified base classes. + The most derived metaclass is one which is a subtype of all of these candidate metaclasses. + If none of the candidate metaclasses meets that criterion, it is a TypeError. + """ if self.mro is None: # XXX why does this happen? return None - if len(self.mro) > 1: - return self.mro[1].metaclass_type - # FIX: assert False + declared = self.declared_metaclass_type + if declared is not None and not declared.type.has_base('builtins.type'): + return declared + if self._fullname == 'builtins.type': + return mypy.types.Instance(self, []) + candidates = ({declared} | {s.metaclass_type for s in self.mro[1:]}) - {None} + for c in candidates: + if all(other in c.type.mro for other in candidates): + return c + # TypeError return None @metaclass_type.setter def metaclass_type(self, value: 'mypy.types.Instance'): - self._metaclass_type = value + self.declared_metaclass_type = value def is_metaclass(self) -> bool: return self.has_base('builtins.type') @@ -2083,6 +2099,7 @@ def serialize(self) -> JsonDict: 'type_vars': self.type_vars, 'bases': [b.serialize() for b in self.bases], '_promote': None if self._promote is None else self._promote.serialize(), + 'declared_metaclass_type': self.declared_metaclass_type, 'tuple_type': None if self.tuple_type is None else self.tuple_type.serialize(), 'typeddict_type': None if self.typeddict_type is None else self.typeddict_type.serialize(), @@ -2104,6 +2121,7 @@ def deserialize(cls, data: JsonDict) -> 'TypeInfo': ti.bases = [mypy.types.Instance.deserialize(b) for b in data['bases']] ti._promote = (None if data['_promote'] is None else mypy.types.Type.deserialize(data['_promote'])) + ti.declared_metaclass_type = data['declared_metaclass_type'] ti.tuple_type = (None if data['tuple_type'] is None else mypy.types.TupleType.deserialize(data['tuple_type'])) ti.typeddict_type = (None if data['typeddict_type'] is None From c42f777f718941c14d791ae02536598c5321afe4 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 14 Dec 2016 03:16:22 +0200 Subject: [PATCH 09/18] Some metaclass consistency checks --- mypy/nodes.py | 26 ++++---------------------- mypy/semanal.py | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 89a02cb5d799..16bac4f8d36c 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1947,22 +1947,9 @@ def __init__(self, names: 'SymbolTable', defn: ClassDef, module_name: str) -> No self.type_vars.append(vd.name) declared_metaclass_type = None # type: Optional[mypy.types.Instance] + metaclass_type = None # type: Optional[mypy.types.Instance] - @property - def metaclass_type(self) -> 'Optional[mypy.types.Instance]': - """ (From the docs) - The appropriate metaclass is determined as follows: - * if no bases and no explicit metaclass are given, then type() is used - * if an explicit metaclass is given and it is not an instance of type(), - then it is used directly as the metaclass - * if an instance of type() is given as the explicit metaclass, or bases are defined, - then the most derived metaclass is used - - The most derived metaclass is selected from the explicitly specified metaclass (if any) - and the metaclasses (i.e. type(cls)) of all specified base classes. - The most derived metaclass is one which is a subtype of all of these candidate metaclasses. - If none of the candidate metaclasses meets that criterion, it is a TypeError. - """ + def calculate_metaclass_type(self) -> 'Optional[mypy.types.Instance]': if self.mro is None: # XXX why does this happen? return None @@ -1971,17 +1958,12 @@ def metaclass_type(self) -> 'Optional[mypy.types.Instance]': return declared if self._fullname == 'builtins.type': return mypy.types.Instance(self, []) - candidates = ({declared} | {s.metaclass_type for s in self.mro[1:]}) - {None} + candidates = {s.declared_metaclass_type for s in self.mro} - {None} for c in candidates: - if all(other in c.type.mro for other in candidates): + if all(other.type in c.type.mro for other in candidates): return c - # TypeError return None - @metaclass_type.setter - def metaclass_type(self, value: 'mypy.types.Instance'): - self.declared_metaclass_type = value - def is_metaclass(self) -> bool: return self.has_base('builtins.type') diff --git a/mypy/semanal.py b/mypy/semanal.py index 320cb0b6cd7f..0b0932bdd263 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -905,13 +905,20 @@ def analyze_metaclass(self, defn: ClassDef) -> None: self.fail("Dynamic metaclass not supported for '%s'" % defn.name, defn) return sym = self.lookup_qualified(defn.metaclass, defn) - if sym is not None and isinstance(sym.node, TypeInfo): - inst = fill_typevars(sym.node) - assert isinstance(inst, Instance) - defn.info.metaclass_type = inst + if sym is None: + # Probably a name error - it is already handled elsewhere return - else: + if not isinstance(sym.node, TypeInfo): self.fail("Invalid metaclass '%s'" % defn.metaclass, defn) + return + inst = fill_typevars(sym.node) + assert isinstance(inst, Instance) + defn.info.declared_metaclass_type = inst + defn.info.metaclass_type = defn.info.calculate_metaclass_type() + if defn.info.metaclass_type is None: + # Inconsistency may happen due to multiple baseclasses even in classes that + # do not declare explicit metaclass, but it's harder to catch at this stage + self.fail("Inconsistent metaclass structure for '%s'" % defn.name, defn) def object_type(self) -> Instance: return self.named_type('__builtins__.object') From 5de0e809824dfa6ba18f5ef43cab4e5dd5459e2b Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 14 Dec 2016 03:21:42 +0200 Subject: [PATCH 10/18] Add tests --- test-data/unit/check-classes.test | 43 ++++++++++++++++++++++++++++++ test-data/unit/semanal-errors.test | 13 +++------ 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 9e46ef98e8ad..47b5cc28d1e9 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2759,3 +2759,46 @@ class B(A): class C(B): x = '' [out] + +[case testInvalidMetaclassStructure] +class X(type): pass +class Y(type): pass +class A(metaclass=X): pass +class B(A, metaclass=Y): pass # E: Inconsistent metaclass structure for 'B' + +[case testMetaclassNoTypeReveal] +class M: + x = 0 # type: int + +class A(metaclass=M): pass + +reveal_type(A.x) # E: Revealed type is 'builtins.int' + +[case testMetaclassTypeReveal] +from typing import Type +class M(type): + x = 0 # type: int + +class A(metaclass=M): pass + +reveal_type(A.x) # E: Revealed type is 'builtins.int' + +def f(TA: Type[A]): + reveal_type(TA) # E: Revealed type is 'Type[__main__.A]' + reveal_type(TA.x) # E: Revealed type is 'builtins.int' + +[case testMetaclassIterable] +from typing import Iterable, Iterator + +class BadMeta(type): + def __iter__(self) -> Iterator[int]: yield 1 + +class Bad(metaclass=BadMeta): pass + +for _ in Bad: pass # E: Iterable expected + +class GoodMeta(type, Iterable[int]): + def __iter__(self) -> Iterator[int]: yield 1 + +class Good(metaclass=GoodMeta): pass +for _ in Good: pass diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index d948eb524119..4cf1e521c065 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -962,22 +962,15 @@ class A(Generic[T], Generic[S]): pass \ [out] [case testInvalidMetaclass] -class A(metaclass=x): pass -[out] -main:1: error: Name 'x' is not defined -main:1: error: Invalid metaclass 'x' +class A(metaclass=x): pass # E: Name 'x' is not defined [case testInvalidQualifiedMetaclass] import abc -class A(metaclass=abc.Foo): pass -[out] -main:2: error: Name 'abc.Foo' is not defined -main:2: error: Invalid metaclass 'abc.Foo' +class A(metaclass=abc.Foo): pass # E: Name 'abc.Foo' is not defined [case testNonClassMetaclass] def f(): pass -class A(metaclass=f): pass # E: Invalid metaclass 'f' -[out] +class A(metaclass=f): pass # E: Invalid metaclass 'f' [case testInvalidTypevarArguments] from typing import TypeVar From cf02b0da75109ff0eaeac373418b98392b82e68b Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 14 Dec 2016 03:42:16 +0200 Subject: [PATCH 11/18] serialization --- mypy/nodes.py | 13 ++++++++----- mypy/semanal.py | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 16bac4f8d36c..b01d905ccf76 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1946,19 +1946,19 @@ def __init__(self, names: 'SymbolTable', defn: ClassDef, module_name: str) -> No for vd in defn.type_vars: self.type_vars.append(vd.name) - declared_metaclass_type = None # type: Optional[mypy.types.Instance] + declared_metaclass = None # type: Optional[mypy.types.Instance] metaclass_type = None # type: Optional[mypy.types.Instance] def calculate_metaclass_type(self) -> 'Optional[mypy.types.Instance]': if self.mro is None: # XXX why does this happen? return None - declared = self.declared_metaclass_type + declared = self.declared_metaclass if declared is not None and not declared.type.has_base('builtins.type'): return declared if self._fullname == 'builtins.type': return mypy.types.Instance(self, []) - candidates = {s.declared_metaclass_type for s in self.mro} - {None} + candidates = {s.declared_metaclass for s in self.mro} - {None} for c in candidates: if all(other.type in c.type.mro for other in candidates): return c @@ -2081,7 +2081,8 @@ def serialize(self) -> JsonDict: 'type_vars': self.type_vars, 'bases': [b.serialize() for b in self.bases], '_promote': None if self._promote is None else self._promote.serialize(), - 'declared_metaclass_type': self.declared_metaclass_type, + 'declared_metaclass': (None if self.declared_metaclass is None + else self.declared_metaclass.serialize()), 'tuple_type': None if self.tuple_type is None else self.tuple_type.serialize(), 'typeddict_type': None if self.typeddict_type is None else self.typeddict_type.serialize(), @@ -2103,7 +2104,9 @@ def deserialize(cls, data: JsonDict) -> 'TypeInfo': ti.bases = [mypy.types.Instance.deserialize(b) for b in data['bases']] ti._promote = (None if data['_promote'] is None else mypy.types.Type.deserialize(data['_promote'])) - ti.declared_metaclass_type = data['declared_metaclass_type'] + ti.declared_metaclass = (None if data['declared_metaclass'] is None + else mypy.types.Instance.deserialize(data['declared_metaclass'])) + ti.metaclass_type = ti.calculate_metaclass_type() ti.tuple_type = (None if data['tuple_type'] is None else mypy.types.TupleType.deserialize(data['tuple_type'])) ti.typeddict_type = (None if data['typeddict_type'] is None diff --git a/mypy/semanal.py b/mypy/semanal.py index 0b0932bdd263..bf5e14739bbb 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -913,7 +913,7 @@ def analyze_metaclass(self, defn: ClassDef) -> None: return inst = fill_typevars(sym.node) assert isinstance(inst, Instance) - defn.info.declared_metaclass_type = inst + defn.info.declared_metaclass = inst defn.info.metaclass_type = defn.info.calculate_metaclass_type() if defn.info.metaclass_type is None: # Inconsistency may happen due to multiple baseclasses even in classes that From f90fc8067c1e563a90c947cceab249b6358c75d0 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 14 Dec 2016 11:50:49 +0200 Subject: [PATCH 12/18] Turns out subtyping is enough for iterable --- mypy/checker.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 09d83bbaf4ae..4faf953d6e5f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2029,14 +2029,9 @@ def analyze_iterable_item_type(self, expr: Expression) -> Type: return joined else: # Non-tuple iterable. - if isinstance(iterable, CallableType) and iterable.is_type_obj(): - self.check_subtype(iterable.fallback, - self.named_generic_type('typing.Iterable', [AnyType()]), - expr, messages.ITERABLE_EXPECTED) - else: - self.check_subtype(iterable, - self.named_generic_type('typing.Iterable', [AnyType()]), - expr, messages.ITERABLE_EXPECTED) + self.check_subtype(iterable, + self.named_generic_type('typing.Iterable', [AnyType()]), + expr, messages.ITERABLE_EXPECTED) echk = self.expr_checker method = echk.analyze_external_member_access('__iter__', iterable, expr) From ac49100c1b0bda7be007e97da9000efc4bbc505f Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 14 Dec 2016 11:55:02 +0200 Subject: [PATCH 13/18] simplify diff --- test-data/unit/semanal-errors.test | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index 4cf1e521c065..aa45dde1faca 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -962,15 +962,18 @@ class A(Generic[T], Generic[S]): pass \ [out] [case testInvalidMetaclass] -class A(metaclass=x): pass # E: Name 'x' is not defined +class A(metaclass=x): pass # E: Name 'x' is not defined +[out] [case testInvalidQualifiedMetaclass] import abc -class A(metaclass=abc.Foo): pass # E: Name 'abc.Foo' is not defined +class A(metaclass=abc.Foo): pass # E: Name 'abc.Foo' is not defined +[out] [case testNonClassMetaclass] def f(): pass -class A(metaclass=f): pass # E: Invalid metaclass 'f' +class A(metaclass=f): pass # E: Invalid metaclass 'f' +[out] [case testInvalidTypevarArguments] from typing import TypeVar From 4c70367b3698a5244d0865a68e9f783bc876b379 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 14 Dec 2016 11:57:25 +0200 Subject: [PATCH 14/18] simplify diff cont. --- mypy/checker.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 4faf953d6e5f..84e73fc531c9 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2030,8 +2030,10 @@ def analyze_iterable_item_type(self, expr: Expression) -> Type: else: # Non-tuple iterable. self.check_subtype(iterable, - self.named_generic_type('typing.Iterable', [AnyType()]), + self.named_generic_type('typing.Iterable', + [AnyType()]), expr, messages.ITERABLE_EXPECTED) + echk = self.expr_checker method = echk.analyze_external_member_access('__iter__', iterable, expr) From 7876d3fe1ab4296772fee5c9df49d43cc0228c7c Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 15 Dec 2016 01:11:20 +0200 Subject: [PATCH 15/18] fix inference --- mypy/constraints.py | 2 ++ test-data/unit/check-classes.test | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/mypy/constraints.py b/mypy/constraints.py index 533a436448ec..d10b33efbe17 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -294,6 +294,8 @@ def visit_type_var(self, template: TypeVarType) -> List[Constraint]: def visit_instance(self, template: Instance) -> List[Constraint]: actual = self.actual res = [] # type: List[Constraint] + if isinstance(actual, CallableType) and actual.fallback is not None: + actual = actual.fallback if isinstance(actual, Instance): instance = actual if (self.direction == SUBTYPE_OF and diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 47b5cc28d1e9..ad87987e037f 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2802,3 +2802,7 @@ class GoodMeta(type, Iterable[int]): class Good(metaclass=GoodMeta): pass for _ in Good: pass +reveal_type(list(Good)) # E: Revealed type is 'builtins.list[builtins.int*]' + +[builtins fixtures/list.pyi] + From c256da5b47fd7e511a4f6307118db88597a93871 Mon Sep 17 00:00:00 2001 From: elazar Date: Tue, 7 Feb 2017 01:45:35 +0200 Subject: [PATCH 16/18] do not initialize metaclass from deserialize --- mypy/nodes.py | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index b01d905ccf76..b6374a251897 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1876,6 +1876,10 @@ class is generic then it will be a type constructor of higher kind. # Method Resolution Order: the order of looking up attributes. The first # value always to refers to this class. mro = None # type: List[TypeInfo] + + declared_metaclass = None # type: Optional[mypy.types.Instance] + metaclass_type = None # type: mypy.types.Instance + subtypes = None # type: Set[TypeInfo] # Direct subclasses encountered so far names = None # type: SymbolTable # Names defined directly in this type is_abstract = False # Does the class have any abstract attributes? @@ -1946,27 +1950,6 @@ def __init__(self, names: 'SymbolTable', defn: ClassDef, module_name: str) -> No for vd in defn.type_vars: self.type_vars.append(vd.name) - declared_metaclass = None # type: Optional[mypy.types.Instance] - metaclass_type = None # type: Optional[mypy.types.Instance] - - def calculate_metaclass_type(self) -> 'Optional[mypy.types.Instance]': - if self.mro is None: - # XXX why does this happen? - return None - declared = self.declared_metaclass - if declared is not None and not declared.type.has_base('builtins.type'): - return declared - if self._fullname == 'builtins.type': - return mypy.types.Instance(self, []) - candidates = {s.declared_metaclass for s in self.mro} - {None} - for c in candidates: - if all(other.type in c.type.mro for other in candidates): - return c - return None - - def is_metaclass(self) -> bool: - return self.has_base('builtins.type') - def name(self) -> str: """Short name.""" return self.defn.name @@ -2026,6 +2009,21 @@ def calculate_mro(self) -> None: self.mro = mro self.is_enum = self._calculate_is_enum() + def calculate_metaclass_type(self) -> 'Optional[mypy.types.Instance]': + declared = self.declared_metaclass + if declared is not None and not declared.type.has_base('builtins.type'): + return declared + if self._fullname == 'builtins.type': + return mypy.types.Instance(self, []) + candidates = {s.declared_metaclass for s in self.mro} - {None} + for c in candidates: + if all(other.type in c.type.mro for other in candidates): + return c + return None + + def is_metaclass(self) -> bool: + return self.has_base('builtins.type') + def _calculate_is_enum(self) -> bool: """ If this is "enum.Enum" itself, then yes, it's an enum. @@ -2106,7 +2104,7 @@ def deserialize(cls, data: JsonDict) -> 'TypeInfo': else mypy.types.Type.deserialize(data['_promote'])) ti.declared_metaclass = (None if data['declared_metaclass'] is None else mypy.types.Instance.deserialize(data['declared_metaclass'])) - ti.metaclass_type = ti.calculate_metaclass_type() + # NOTE: ti.metaclass_type and ti.mro will be set in the fixup phase. ti.tuple_type = (None if data['tuple_type'] is None else mypy.types.TupleType.deserialize(data['tuple_type'])) ti.typeddict_type = (None if data['typeddict_type'] is None From d65c3b4efd0008c2e6d5b922b0c0c7319aa743a6 Mon Sep 17 00:00:00 2001 From: elazar Date: Tue, 7 Feb 2017 01:50:48 +0200 Subject: [PATCH 17/18] fail for metaclass(Tuple) --- mypy/semanal.py | 4 +++- test-data/unit/check-classes.test | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index bf5e14739bbb..01d96da85171 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -912,7 +912,9 @@ def analyze_metaclass(self, defn: ClassDef) -> None: self.fail("Invalid metaclass '%s'" % defn.metaclass, defn) return inst = fill_typevars(sym.node) - assert isinstance(inst, Instance) + if not isinstance(inst, Instance): + self.fail("Invalid metaclass '%s'" % defn.metaclass, defn) + return defn.info.declared_metaclass = inst defn.info.metaclass_type = defn.info.calculate_metaclass_type() if defn.info.metaclass_type is None: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index ad87987e037f..0bcce2f87b57 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2806,3 +2806,10 @@ reveal_type(list(Good)) # E: Revealed type is 'builtins.list[builtins.int*]' [builtins fixtures/list.pyi] +[case testMetaclassTuple] +from typing import Tuple + +class M(Tuple[int]): pass +class C(metaclass=M): pass # E: Invalid metaclass 'M' + +[builtins fixtures/tuple.pyi] \ No newline at end of file From b083a9748f7b01564e8142abc734c87989e68bd2 Mon Sep 17 00:00:00 2001 From: elazar Date: Tue, 7 Feb 2017 02:14:55 +0200 Subject: [PATCH 18/18] dont use set. fail for metaclass(Tuple) --- mypy/nodes.py | 2 +- mypy/semanal.py | 6 ++---- test-data/unit/check-classes.test | 2 -- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index b6374a251897..8d0d15af4c6a 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2015,7 +2015,7 @@ def calculate_metaclass_type(self) -> 'Optional[mypy.types.Instance]': return declared if self._fullname == 'builtins.type': return mypy.types.Instance(self, []) - candidates = {s.declared_metaclass for s in self.mro} - {None} + candidates = [s.declared_metaclass for s in self.mro if s.declared_metaclass is not None] for c in candidates: if all(other.type in c.type.mro for other in candidates): return c diff --git a/mypy/semanal.py b/mypy/semanal.py index 01d96da85171..66553325ece2 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -908,13 +908,11 @@ def analyze_metaclass(self, defn: ClassDef) -> None: if sym is None: # Probably a name error - it is already handled elsewhere return - if not isinstance(sym.node, TypeInfo): + if not isinstance(sym.node, TypeInfo) or sym.node.tuple_type is not None: self.fail("Invalid metaclass '%s'" % defn.metaclass, defn) return inst = fill_typevars(sym.node) - if not isinstance(inst, Instance): - self.fail("Invalid metaclass '%s'" % defn.metaclass, defn) - return + assert isinstance(inst, Instance) defn.info.declared_metaclass = inst defn.info.metaclass_type = defn.info.calculate_metaclass_type() if defn.info.metaclass_type is None: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 0bcce2f87b57..af69bf7c4c30 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2781,8 +2781,6 @@ class M(type): class A(metaclass=M): pass -reveal_type(A.x) # E: Revealed type is 'builtins.int' - def f(TA: Type[A]): reveal_type(TA) # E: Revealed type is 'Type[__main__.A]' reveal_type(TA.x) # E: Revealed type is 'builtins.int'