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') diff --git a/mypy/checker.py b/mypy/checker.py index 8244fffa21cf..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)) @@ -1791,10 +1793,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, @@ -1946,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( @@ -2214,10 +2219,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..9825c2ec4959 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -176,6 +176,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) @@ -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 = 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__') diff --git a/mypy/nodes.py b/mypy/nodes.py index e8a6a573087a..4fda2afdb589 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1903,6 +1903,26 @@ class is generic then it will be a type constructor of higher kind. '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 + return None + + @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.""" self.names = names @@ -1931,6 +1951,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 f921d8ac8479..a9c008e98df9 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -902,8 +902,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: 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 9c80b590cd38..0f9673ba2f4d 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,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() diff --git a/test-data/unit/semanal-classes.test b/test-data/unit/semanal-classes.test index 431261fa3a18..e0da578aead6 100644 --- a/test-data/unit/semanal-classes.test +++ b/test-data/unit/semanal-classes.test @@ -394,14 +394,14 @@ 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) + Metaclass(ABCMeta) PassStmt:2())) [case testStaticMethod] diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index 2f3e05ea0a4f..ad1fa0884ce9 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:1: error: Name 'x' is not defined +main:1: 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 diff --git a/typeshed b/typeshed index 3ea39a7c1b78..fed966cf7f5b 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 3ea39a7c1b78f59df0ea1469ead4f42050d8ad38 +Subproject commit fed966cf7f5b3cddb731b846f7781b863b47a7cc