diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 8ba3f193e9622..758ef4d24a39b 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -465,12 +465,6 @@ def fail_arg(msg: str, arg: ast3.arg) -> None: @with_line def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef: self.class_nesting += 1 - metaclass_arg = find(lambda x: x.arg == 'metaclass', n.keywords) - metaclass = None - if metaclass_arg: - metaclass = stringify_name(metaclass_arg.value) - if metaclass is None: - metaclass = '' # To be reported later keywords = [(kw.arg, self.visit(kw.value)) for kw in n.keywords] @@ -478,7 +472,7 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef: self.as_block(n.body, n.lineno), None, self.translate_expr_list(n.bases), - metaclass=metaclass, + metaclass=dict(keywords).get('metaclass'), keywords=keywords) cdef.decorators = self.translate_expr_list(n.decorator_list) self.class_nesting -= 1 diff --git a/mypy/nodes.py b/mypy/nodes.py index 400339c083c10..3a165dda30374 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -730,7 +730,7 @@ class ClassDef(Statement): # Base class expressions (not semantically analyzed -- can be arbitrary expressions) base_type_exprs = None # type: List[Expression] info = None # type: TypeInfo # Related TypeInfo - metaclass = '' # type: Optional[str] + metaclass = None # type: Optional[Expression] decorators = None # type: List[Expression] keywords = None # type: OrderedDict[str, Expression] analyzed = None # type: Optional[Expression] @@ -741,7 +741,7 @@ def __init__(self, defs: 'Block', type_vars: Optional[List['mypy.types.TypeVarDef']] = None, base_type_exprs: Optional[List[Expression]] = None, - metaclass: Optional[str] = None, + metaclass: Optional[Expression] = None, keywords: Optional[List[Tuple[str, Expression]]] = None) -> None: self.name = name self.defs = defs @@ -758,12 +758,12 @@ def is_generic(self) -> bool: return self.info.is_generic() def serialize(self) -> JsonDict: - # Not serialized: defs, base_type_exprs, decorators, analyzed (for named tuples etc.) + # Not serialized: defs, base_type_exprs, metaclass, decorators, + # analyzed (for named tuples etc.) return {'.class': 'ClassDef', 'name': self.name, 'fullname': self.fullname, 'type_vars': [v.serialize() for v in self.type_vars], - 'metaclass': self.metaclass, } @classmethod @@ -772,7 +772,6 @@ def deserialize(self, data: JsonDict) -> 'ClassDef': res = ClassDef(data['name'], Block([]), [mypy.types.TypeVarDef.deserialize(v) for v in data['type_vars']], - metaclass=data['metaclass'], ) res.fullname = data['fullname'] return res diff --git a/mypy/semanal.py b/mypy/semanal.py index e413c7fd58824..f611d328aca78 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1089,26 +1089,18 @@ def analyze_base_classes(self, defn: ClassDef) -> None: if defn.info.is_enum and defn.type_vars: self.fail("Enum class cannot be generic", defn) - def check_with_metaclass(self, defn: ClassDef) -> Tuple[List[Expression], Optional[str]]: + def check_with_metaclass(self, defn: ClassDef) -> Tuple[List[Expression], + Optional[Expression]]: # Special-case six.with_metaclass(M, B1, B2, ...). - base_type_exprs, metaclass = defn.base_type_exprs, defn.metaclass - if metaclass is None and len(base_type_exprs) == 1: - base_expr = base_type_exprs[0] + if defn.metaclass is None and len(defn.base_type_exprs) == 1: + base_expr = defn.base_type_exprs[0] if isinstance(base_expr, CallExpr) and isinstance(base_expr.callee, RefExpr): base_expr.callee.accept(self) if (base_expr.callee.fullname == 'six.with_metaclass' and len(base_expr.args) >= 1 and all(kind == ARG_POS for kind in base_expr.arg_kinds)): - metaclass_expr = base_expr.args[0] - if isinstance(metaclass_expr, NameExpr): - metaclass = metaclass_expr.name - elif isinstance(metaclass_expr, MemberExpr): - metaclass = get_member_expr_fullname(metaclass_expr) - else: - self.fail("Dynamic metaclass not supported for '%s'" % defn.name, - metaclass_expr) - return (base_expr.args[1:], metaclass) - return (base_type_exprs, metaclass) + return (base_expr.args[1:], base_expr.args[0]) + return (defn.base_type_exprs, defn.metaclass) def expr_to_analyzed_type(self, expr: Expression) -> Type: if isinstance(expr, CallExpr): @@ -1157,7 +1149,6 @@ def is_base_class(self, t: TypeInfo, s: TypeInfo) -> bool: return False def analyze_metaclass(self, defn: ClassDef) -> None: - error_context = defn # type: Context if defn.metaclass is None and self.options.python_version[0] == 2: # Look for "__metaclass__ = " in Python 2. for body_node in defn.defs.body: @@ -1167,26 +1158,16 @@ def analyze_metaclass(self, defn: ClassDef) -> None: elif isinstance(body_node, AssignmentStmt) and len(body_node.lvalues) == 1: lvalue = body_node.lvalues[0] if isinstance(lvalue, NameExpr) and lvalue.name == "__metaclass__": - error_context = body_node.rvalue - if isinstance(body_node.rvalue, NameExpr): - name = body_node.rvalue.name - elif isinstance(body_node.rvalue, MemberExpr): - name = get_member_expr_fullname(body_node.rvalue) - else: - name = None - if name: - defn.metaclass = name - else: - self.fail( - "Dynamic metaclass not supported for '%s'" % defn.name, - body_node - ) - return + defn.metaclass = body_node.rvalue if defn.metaclass: - if defn.metaclass == '': - self.fail("Dynamic metaclass not supported for '%s'" % defn.name, error_context) + if isinstance(defn.metaclass, NameExpr): + metaclass_name = defn.metaclass.name + elif isinstance(defn.metaclass, MemberExpr): + metaclass_name = get_member_expr_fullname(defn.metaclass) + else: + self.fail("Dynamic metaclass not supported for '%s'" % defn.name, defn.metaclass) return - sym = self.lookup_qualified(defn.metaclass, error_context) + sym = self.lookup_qualified(metaclass_name, defn.metaclass) if sym is None: # Probably a name error - it is already handled elsewhere return @@ -1198,10 +1179,11 @@ def analyze_metaclass(self, defn: ClassDef) -> None: # attributes, similar to an 'Any' base class. return if not isinstance(sym.node, TypeInfo) or sym.node.tuple_type is not None: - self.fail("Invalid metaclass '%s'" % defn.metaclass, defn) + self.fail("Invalid metaclass '%s'" % metaclass_name, defn.metaclass) return if not sym.node.is_metaclass(): - self.fail("Metaclasses not inheriting from 'type' are not supported", defn) + self.fail("Metaclasses not inheriting from 'type' are not supported", + defn.metaclass) return inst = fill_typevars(sym.node) assert isinstance(inst, Instance) diff --git a/mypy/treetransform.py b/mypy/treetransform.py index d2e5d112db350..1222dcc072000 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -177,7 +177,7 @@ def visit_class_def(self, node: ClassDef) -> ClassDef: self.block(node.defs), node.type_vars, self.expressions(node.base_type_exprs), - node.metaclass) + self.optional_expr(node.metaclass)) new.fullname = node.fullname new.info = node.info new.decorators = [self.expr(decorator) diff --git a/test-data/unit/parse.test b/test-data/unit/parse.test index 39cbd5878148a..417d37c54c030 100644 --- a/test-data/unit/parse.test +++ b/test-data/unit/parse.test @@ -2480,7 +2480,7 @@ class Foo(metaclass=Bar): pass MypyFile:1( ClassDef:1( Foo - Metaclass(Bar) + Metaclass(NameExpr(Bar)) PassStmt:1())) [case testQualifiedMetaclass] @@ -2489,7 +2489,9 @@ class Foo(metaclass=foo.Bar): pass MypyFile:1( ClassDef:1( Foo - Metaclass(foo.Bar) + Metaclass(MemberExpr:1( + NameExpr(foo) + Bar)) PassStmt:1())) [case testBaseAndMetaclass] @@ -2498,7 +2500,7 @@ class Foo(foo.bar[x], metaclass=Bar): pass MypyFile:1( ClassDef:1( Foo - Metaclass(Bar) + Metaclass(NameExpr(Bar)) BaseTypeExpr( IndexExpr:1( MemberExpr:1( @@ -2521,7 +2523,7 @@ class Foo(_root=None, metaclass=Bar): pass MypyFile:1( ClassDef:1( Foo - Metaclass(Bar) + Metaclass(NameExpr(Bar)) PassStmt:1())) [case testClassKeywordArgsAfterMeta] @@ -2530,7 +2532,7 @@ class Foo(metaclass=Bar, _root=None): pass MypyFile:1( ClassDef:1( Foo - Metaclass(Bar) + Metaclass(NameExpr(Bar)) PassStmt:1())) [case testNamesThatAreNoLongerKeywords] diff --git a/test-data/unit/semanal-abstractclasses.test b/test-data/unit/semanal-abstractclasses.test index b5147bd5cdb7a..dfd5dee1554aa 100644 --- a/test-data/unit/semanal-abstractclasses.test +++ b/test-data/unit/semanal-abstractclasses.test @@ -13,7 +13,7 @@ MypyFile:1( Import:2(typing) ClassDef:4( A - Metaclass(ABCMeta) + Metaclass(NameExpr(ABCMeta [abc.ABCMeta])) Decorator:5( Var(g) FuncDef:6( @@ -49,11 +49,11 @@ MypyFile:1( Import:2(typing) ClassDef:4( A - Metaclass(ABCMeta) + Metaclass(NameExpr(ABCMeta [abc.ABCMeta])) PassStmt:4()) ClassDef:5( B - Metaclass(ABCMeta) + Metaclass(NameExpr(ABCMeta [abc.ABCMeta])) PassStmt:5()) ClassDef:6( C @@ -106,7 +106,7 @@ MypyFile:1( Import:3(typing) ClassDef:5( A - Metaclass(ABCMeta) + Metaclass(NameExpr(ABCMeta [abc.ABCMeta])) Decorator:6( Var(g) FuncDef:7( diff --git a/test-data/unit/semanal-classes.test b/test-data/unit/semanal-classes.test index 22beb8d16f7a3..d68aa1741f88a 100644 --- a/test-data/unit/semanal-classes.test +++ b/test-data/unit/semanal-classes.test @@ -411,7 +411,9 @@ MypyFile:1( Import:1(abc) ClassDef:2( A - Metaclass(abc.ABCMeta) + Metaclass(MemberExpr:2( + NameExpr(abc) + ABCMeta [abc.ABCMeta])) PassStmt:2())) [case testStaticMethod]