From f6685b1bd163d7b5d21941d6950555649b4efe81 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 28 Oct 2016 13:21:30 +0300 Subject: [PATCH 1/7] add enough support for metaclasses to iterate over enums --- mypy/checker.py | 14 ++++++-------- mypy/checkexpr.py | 2 +- mypy/checkmember.py | 4 ++-- mypy/nodes.py | 12 ++++++++++++ mypy/semanal.py | 8 ++++++-- mypy/types.py | 8 ++++---- test-data/unit/pythoneval-enum.test | 13 +++++++++++++ 7 files changed, 44 insertions(+), 17 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 8244fffa21cf..873cb60dcad6 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1791,10 +1791,12 @@ 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(): + print(iterable.fallback) + 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, @@ -2214,10 +2216,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 111a758a733a..3c5b0d73cad6 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1607,7 +1607,7 @@ def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type: args = self.chk.function_stack[-1].arguments # An empty args with super() is an error; we need something in declared_self if not args: - self.chk.fail('super() requires at least on positional argument', e) + self.chk.fail('super() requires at least one positional argument', e) return AnyType() declared_self = args[0].variable.type return analyze_member_access(name=e.name, typ=fill_typevars(e.info), node=e, diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 5624c014e813..d01f72989f7d 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -175,7 +175,7 @@ def analyze_member_access(name: str, original_type=original_type) if result: return result - fallback = builtin_type('builtins.type') + fallback = item.type.metaclass_type 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) @@ -437,7 +437,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 if init_method.info.fullname() == 'builtins.object': # No non-default __init__ -> look at __new__ instead. new_method = info.get_method('__new__') diff --git a/mypy/nodes.py b/mypy/nodes.py index e8a6a573087a..7d3900df4934 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1902,6 +1902,18 @@ class is generic then it will be a type constructor of higher kind. 'is_abstract', 'is_enum', 'fallback_to_any', 'is_named_tuple', 'is_typed_dict', 'is_newtype' ] + + _metaclass_type = None # type: Optional[Instance] + + @property + def metaclass_type(self) -> 'mypy.types.Instance': + if self._metaclass_type: + return self._metaclass_type + return self.mro[1].metaclass_type + + @metaclass_type.setter + def metaclass_type(self, value: 'mypy.types.Instance'): + self._metaclass_type = value def __init__(self, names: 'SymbolTable', defn: ClassDef, module_name: str) -> None: """Initialize a TypeInfo.""" diff --git a/mypy/semanal.py b/mypy/semanal.py index f921d8ac8479..394917143738 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -902,8 +902,12 @@ 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: + assert isinstance(sym.node, TypeInfo) + defn.info.metaclass_type = fill_typevars(sym.node) + return + else: self.fail("Invalid metaclass '%s'" % defn.metaclass, defn) def object_type(self) -> Instance: diff --git a/mypy/types.py b/mypy/types.py index 9c80b590cd38..0552bba2fca4 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -508,6 +508,9 @@ class FunctionLike(Type): can_be_false = False + # Corresponding instance type (e.g. builtins.type) + fallback = None # type: Instance + @abstractmethod def is_type_obj(self) -> bool: pass @@ -523,9 +526,6 @@ def items(self) -> List['CallableType']: pass @abstractmethod def with_name(self, name: str) -> 'FunctionLike': pass - # Corresponding instance type (e.g. builtins.type) - fallback = None # type: Instance - @classmethod def deserialize(cls, data: JsonDict) -> 'FunctionLike': return cast(FunctionLike, super().deserialize(data)) @@ -624,7 +624,7 @@ def copy_modified(self, ) def is_type_obj(self) -> bool: - return self.fallback.type is not None and self.fallback.type.fullname() == 'builtins.type' + return self.fallback.type is not None # and self.fallback.type.fullname() == 'builtins.type' def is_concrete_type_obj(self) -> bool: return self.is_type_obj() and self.is_classmethod_class diff --git a/test-data/unit/pythoneval-enum.test b/test-data/unit/pythoneval-enum.test index 8044e999d882..546c6958407c 100644 --- a/test-data/unit/pythoneval-enum.test +++ b/test-data/unit/pythoneval-enum.test @@ -118,3 +118,16 @@ takes_int(SomeExtIntEnum.x) def takes_some_ext_int_enum(s: SomeExtIntEnum): pass takes_some_ext_int_enum(SomeExtIntEnum.x) + +[case testEnumIter] +from typing import List +from enum import Enum +class Medal(Enum): + gold = 1 + silver = 2 + bronze = 3 + +for x in Medal: + reveal_type(x) + +x = list(Medal) # type: List[Medal] From 677110d950c796228076d2a8c5b26d6f6191aa4d Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 28 Oct 2016 14:30:46 +0300 Subject: [PATCH 2/7] pass testcheck --- mypy/checkmember.py | 6 ++++-- mypy/nodes.py | 14 +++++++++++--- mypy/semanal.py | 4 +++- mypy/typefixture.py | 1 + mypy/types.py | 3 ++- test-data/unit/lib-stub/abc.pyi | 2 +- 6 files changed, 22 insertions(+), 8 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index d01f72989f7d..9825c2ec4959 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -175,7 +175,9 @@ def analyze_member_access(name: str, original_type=original_type) if result: return result - fallback = item.type.metaclass_type + 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) @@ -437,7 +439,7 @@ def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) -> # Must be an invalid class definition. return AnyType() else: - fallback = info.metaclass_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__') diff --git a/mypy/nodes.py b/mypy/nodes.py index 7d3900df4934..1c5733e15a76 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1903,13 +1903,18 @@ class is generic then it will be a type constructor of higher kind. 'is_typed_dict', 'is_newtype' ] - _metaclass_type = None # type: Optional[Instance] + _metaclass_type = None # type: Optional[mypy.types.Instance] @property - def metaclass_type(self) -> 'mypy.types.Instance': + def metaclass_type(self) -> 'Optional[mypy.types.Instance]': if self._metaclass_type: return self._metaclass_type - return self.mro[1].metaclass_type + if self._fullname == 'builtins.type': + return mypy.types.Instance(self, []) + 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'): @@ -1943,6 +1948,9 @@ def is_generic(self) -> bool: """Is the type generic (i.e. does it have type variables)?""" return len(self.type_vars) > 0 + def is_metaclass(self) -> bool: + return self.has_base('builtins.type') + def get(self, name: str) -> 'SymbolTableNode': for cls in self.mro: n = cls.names.get(name) diff --git a/mypy/semanal.py b/mypy/semanal.py index 394917143738..6a5e60833c86 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -905,7 +905,9 @@ def analyze_metaclass(self, defn: ClassDef) -> None: sym = self.lookup(defn.metaclass, defn) if sym is not None: assert isinstance(sym.node, TypeInfo) - defn.info.metaclass_type = fill_typevars(sym.node) + inst = fill_typevars(sym.node) + assert isinstance(inst, Instance) + defn.info.metaclass_type = inst return else: self.fail("Invalid metaclass '%s'" % defn.metaclass, defn) diff --git a/mypy/typefixture.py b/mypy/typefixture.py index a5fac17300dd..a889c34e84a3 100644 --- a/mypy/typefixture.py +++ b/mypy/typefixture.py @@ -102,6 +102,7 @@ def make_type_var(name: str, id: int, values: List[Type], upper_bound: Type, # Instance types self.std_tuple = Instance(self.std_tuplei, []) # tuple self.type_type = Instance(self.type_typei, []) # type + self.oi.metaclass_type = self.type_type self.function = Instance(self.functioni, []) # function TODO self.a = Instance(self.ai, []) # A self.b = Instance(self.bi, []) # B diff --git a/mypy/types.py b/mypy/types.py index 0552bba2fca4..0f9673ba2f4d 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -624,7 +624,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 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() From 3576c65ca40ed63447dd22a3d7616cfd04adbeec Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 28 Oct 2016 14:57:33 +0300 Subject: [PATCH 3/7] adapt tests --- mypy/checker.py | 3 +++ mypy/nodes.py | 7 +++++-- test-data/unit/pythoneval-enum.test | 5 +++-- test-data/unit/semanal-classes.test | 6 +++--- test-data/unit/semanal-errors.test | 8 ++++++-- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 873cb60dcad6..cab4440e3737 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1793,6 +1793,9 @@ def analyze_iterable_item_type(self, expr: Expression) -> Type: # Non-tuple iterable. if isinstance(iterable, CallableType) and iterable.is_type_obj(): print(iterable.fallback) + 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()]), diff --git a/mypy/nodes.py b/mypy/nodes.py index 1c5733e15a76..4fda2afdb589 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1902,15 +1902,18 @@ class is generic then it will be a type constructor of higher kind. 'is_abstract', 'is_enum', 'fallback_to_any', 'is_named_tuple', 'is_typed_dict', 'is_newtype' ] - + _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 diff --git a/test-data/unit/pythoneval-enum.test b/test-data/unit/pythoneval-enum.test index 546c6958407c..6de938514f4a 100644 --- a/test-data/unit/pythoneval-enum.test +++ b/test-data/unit/pythoneval-enum.test @@ -128,6 +128,7 @@ class Medal(Enum): bronze = 3 for x in Medal: - reveal_type(x) + reveal_type(x) # E: Revealed type is '_program.Medal*' -x = list(Medal) # type: List[Medal] +-- TODO: +-- reveal_type(list(Medal)) # type: List[Medal] diff --git a/test-data/unit/semanal-classes.test b/test-data/unit/semanal-classes.test index 431261fa3a18..4ccaf92d315d 100644 --- a/test-data/unit/semanal-classes.test +++ b/test-data/unit/semanal-classes.test @@ -394,11 +394,11 @@ MypyFile:1( NameExpr(x [l])))))))) [case testQualifiedMetaclass] -import abc -class A(metaclass=abc.ABCMeta): pass +from abc import ABCMeta +class A(metaclass=ABCMeta): pass [out] MypyFile:1( - Import:1(abc) + ImportFrom:1(abc, [ABCMeta]) ClassDef:2( A Metaclass(abc.ABCMeta) diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index 2f3e05ea0a4f..548d36380920 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -983,13 +983,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:2: error: Name 'x' is not defined +main:2: 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:2: error: Name 'abc.Foo' is not defined +main:2: error: Invalid metaclass 'abc.Foo' [case testNonClassMetaclass] def f(): pass From 23aa317ad050434788e553d47580f845b018bf0b Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 28 Oct 2016 15:10:28 +0300 Subject: [PATCH 4/7] adapt typeshed to metaclasses --- typeshed | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typeshed b/typeshed index 3ea39a7c1b78..491e4d7bd86b 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 3ea39a7c1b78f59df0ea1469ead4f42050d8ad38 +Subproject commit 491e4d7bd86b41b13da25f1c9ad85b22a8916e76 From b4e65d1b54d7ef79263c9d033b75412ed811f7b2 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 28 Oct 2016 17:04:05 +0300 Subject: [PATCH 5/7] fix tests and assertion --- mypy/checker.py | 6 +++--- mypy/semanal.py | 3 +-- test-data/unit/pythoneval-enum.test | 14 -------------- test-data/unit/semanal-classes.test | 2 +- test-data/unit/semanal-errors.test | 4 ++-- typeshed | 2 +- 6 files changed, 8 insertions(+), 23 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index cab4440e3737..6e595d3d08b7 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1294,6 +1294,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)) @@ -1792,7 +1794,6 @@ def analyze_iterable_item_type(self, expr: Expression) -> Type: else: # Non-tuple iterable. if isinstance(iterable, CallableType) and iterable.is_type_obj(): - print(iterable.fallback) self.check_subtype(iterable.fallback, self.named_generic_type('typing.Iterable', [AnyType()]), expr, messages.ITERABLE_EXPECTED) @@ -1951,8 +1952,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.named_type('typing.Iterable'))): + elif self.type_is_iterable(subexpr_type): if self.is_async_def(subexpr_type) and not self.has_coroutine_decorator(return_type): self.msg.yield_from_invalid_operand_type(subexpr_type, e) iter_method_type = self.expr_checker.analyze_external_member_access( diff --git a/mypy/semanal.py b/mypy/semanal.py index 6a5e60833c86..a9c008e98df9 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -903,8 +903,7 @@ def analyze_metaclass(self, defn: ClassDef) -> None: self.fail("Dynamic metaclass not supported for '%s'" % defn.name, defn) return sym = self.lookup(defn.metaclass, defn) - if sym is not None: - assert isinstance(sym.node, TypeInfo) + if sym is not None and isinstance(sym.node, TypeInfo): inst = fill_typevars(sym.node) assert isinstance(inst, Instance) defn.info.metaclass_type = inst diff --git a/test-data/unit/pythoneval-enum.test b/test-data/unit/pythoneval-enum.test index 6de938514f4a..8044e999d882 100644 --- a/test-data/unit/pythoneval-enum.test +++ b/test-data/unit/pythoneval-enum.test @@ -118,17 +118,3 @@ takes_int(SomeExtIntEnum.x) def takes_some_ext_int_enum(s: SomeExtIntEnum): pass takes_some_ext_int_enum(SomeExtIntEnum.x) - -[case testEnumIter] -from typing import List -from enum import Enum -class Medal(Enum): - gold = 1 - silver = 2 - bronze = 3 - -for x in Medal: - reveal_type(x) # E: Revealed type is '_program.Medal*' - --- TODO: --- reveal_type(list(Medal)) # type: List[Medal] diff --git a/test-data/unit/semanal-classes.test b/test-data/unit/semanal-classes.test index 4ccaf92d315d..e0da578aead6 100644 --- a/test-data/unit/semanal-classes.test +++ b/test-data/unit/semanal-classes.test @@ -401,7 +401,7 @@ MypyFile:1( ImportFrom:1(abc, [ABCMeta]) ClassDef:2( A - Metaclass(abc.ABCMeta) + Metaclass(ABCMeta) PassStmt:2())) [case testStaticMethod] diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index 548d36380920..ad1fa0884ce9 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -985,8 +985,8 @@ class A(Generic[T], Generic[S]): pass \ [case testInvalidMetaclass] class A(metaclass=x): pass [out] -main:2: error: Name 'x' is not defined -main:2: error: Invalid metaclass 'x' +main:1: error: Name 'x' is not defined +main:1: error: Invalid metaclass 'x' [case testInvalidQualifiedMetaclass] import abc diff --git a/typeshed b/typeshed index 491e4d7bd86b..36ae68b8f60f 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 491e4d7bd86b41b13da25f1c9ad85b22a8916e76 +Subproject commit 36ae68b8f60fce755378d13aa3ac20e60541f1bd From b08354c3713ff3cbf4ec0a726da00d8c77dd187d Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 28 Oct 2016 17:50:15 +0300 Subject: [PATCH 6/7] change depndency order --- mypy/build.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypy/build.py b/mypy/build.py index 0932fb3279f4..6b8bbbf125d9 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -1594,6 +1594,9 @@ def process_graph(graph: Graph, manager: BuildManager) -> None: # part of a small cycle involving at least {builtins, abc, # typing}. Of these, builtins must be processed last or else # some builtin objects will be incompletely processed.) + if 'abc' in ascc: + scc.remove('abc') + scc.append('abc') if 'builtins' in ascc: scc.remove('builtins') scc.append('builtins') From 2971e8e14228f80c24567e1b7596532df098de01 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Sun, 30 Oct 2016 22:43:59 +0200 Subject: [PATCH 7/7] typeshed --- typeshed | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typeshed b/typeshed index 36ae68b8f60f..fed966cf7f5b 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 36ae68b8f60fce755378d13aa3ac20e60541f1bd +Subproject commit fed966cf7f5b3cddb731b846f7781b863b47a7cc