Skip to content

Commit 60527de

Browse files
elazargilevkivskyi
authored andcommitted
Make ClassDef.metaclass an Expression (#3848)
Fix #3847.
1 parent 3171c05 commit 60527de

File tree

7 files changed

+37
-58
lines changed

7 files changed

+37
-58
lines changed

mypy/fastparse.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -479,20 +479,14 @@ def fail_arg(msg: str, arg: ast3.arg) -> None:
479479
@with_line
480480
def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef:
481481
self.class_nesting += 1
482-
metaclass_arg = find(lambda x: x.arg == 'metaclass', n.keywords)
483-
metaclass = None
484-
if metaclass_arg:
485-
metaclass = stringify_name(metaclass_arg.value)
486-
if metaclass is None:
487-
metaclass = '<error>' # To be reported later
488482
keywords = [(kw.arg, self.visit(kw.value))
489483
for kw in n.keywords if kw.arg]
490484

491485
cdef = ClassDef(n.name,
492486
self.as_required_block(n.body, n.lineno),
493487
None,
494488
self.translate_expr_list(n.bases),
495-
metaclass=metaclass,
489+
metaclass=dict(keywords).get('metaclass'),
496490
keywords=keywords)
497491
cdef.decorators = self.translate_expr_list(n.decorator_list)
498492
self.class_nesting -= 1

mypy/nodes.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,7 @@ class ClassDef(Statement):
684684
# Base class expressions (not semantically analyzed -- can be arbitrary expressions)
685685
base_type_exprs = None # type: List[Expression]
686686
info = None # type: TypeInfo # Related TypeInfo
687-
metaclass = '' # type: Optional[str]
687+
metaclass = None # type: Optional[Expression]
688688
decorators = None # type: List[Expression]
689689
keywords = None # type: OrderedDict[str, Expression]
690690
analyzed = None # type: Optional[Expression]
@@ -695,7 +695,7 @@ def __init__(self,
695695
defs: 'Block',
696696
type_vars: Optional[List['mypy.types.TypeVarDef']] = None,
697697
base_type_exprs: Optional[List[Expression]] = None,
698-
metaclass: Optional[str] = None,
698+
metaclass: Optional[Expression] = None,
699699
keywords: Optional[List[Tuple[str, Expression]]] = None) -> None:
700700
self.name = name
701701
self.defs = defs
@@ -712,12 +712,12 @@ def is_generic(self) -> bool:
712712
return self.info.is_generic()
713713

714714
def serialize(self) -> JsonDict:
715-
# Not serialized: defs, base_type_exprs, decorators, analyzed (for named tuples etc.)
715+
# Not serialized: defs, base_type_exprs, metaclass, decorators,
716+
# analyzed (for named tuples etc.)
716717
return {'.class': 'ClassDef',
717718
'name': self.name,
718719
'fullname': self.fullname,
719720
'type_vars': [v.serialize() for v in self.type_vars],
720-
'metaclass': self.metaclass,
721721
}
722722

723723
@classmethod
@@ -726,7 +726,6 @@ def deserialize(self, data: JsonDict) -> 'ClassDef':
726726
res = ClassDef(data['name'],
727727
Block([]),
728728
[mypy.types.TypeVarDef.deserialize(v) for v in data['type_vars']],
729-
metaclass=data['metaclass'],
730729
)
731730
res.fullname = data['fullname']
732731
return res

mypy/semanal.py

Lines changed: 17 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,26 +1090,18 @@ def analyze_base_classes(self, defn: ClassDef) -> None:
10901090
if defn.info.is_enum and defn.type_vars:
10911091
self.fail("Enum class cannot be generic", defn)
10921092

1093-
def check_with_metaclass(self, defn: ClassDef) -> Tuple[List[Expression], Optional[str]]:
1093+
def check_with_metaclass(self, defn: ClassDef) -> Tuple[List[Expression],
1094+
Optional[Expression]]:
10941095
# Special-case six.with_metaclass(M, B1, B2, ...).
1095-
base_type_exprs, metaclass = defn.base_type_exprs, defn.metaclass
1096-
if metaclass is None and len(base_type_exprs) == 1:
1097-
base_expr = base_type_exprs[0]
1096+
if defn.metaclass is None and len(defn.base_type_exprs) == 1:
1097+
base_expr = defn.base_type_exprs[0]
10981098
if isinstance(base_expr, CallExpr) and isinstance(base_expr.callee, RefExpr):
10991099
base_expr.callee.accept(self)
11001100
if (base_expr.callee.fullname == 'six.with_metaclass'
11011101
and len(base_expr.args) >= 1
11021102
and all(kind == ARG_POS for kind in base_expr.arg_kinds)):
1103-
metaclass_expr = base_expr.args[0]
1104-
if isinstance(metaclass_expr, NameExpr):
1105-
metaclass = metaclass_expr.name
1106-
elif isinstance(metaclass_expr, MemberExpr):
1107-
metaclass = get_member_expr_fullname(metaclass_expr)
1108-
else:
1109-
self.fail("Dynamic metaclass not supported for '%s'" % defn.name,
1110-
metaclass_expr)
1111-
return (base_expr.args[1:], metaclass)
1112-
return (base_type_exprs, metaclass)
1103+
return (base_expr.args[1:], base_expr.args[0])
1104+
return (defn.base_type_exprs, defn.metaclass)
11131105

11141106
def expr_to_analyzed_type(self, expr: Expression) -> Type:
11151107
if isinstance(expr, CallExpr):
@@ -1158,7 +1150,6 @@ def is_base_class(self, t: TypeInfo, s: TypeInfo) -> bool:
11581150
return False
11591151

11601152
def analyze_metaclass(self, defn: ClassDef) -> None:
1161-
error_context = defn # type: Context
11621153
if defn.metaclass is None and self.options.python_version[0] == 2:
11631154
# Look for "__metaclass__ = <metaclass>" in Python 2.
11641155
for body_node in defn.defs.body:
@@ -1168,26 +1159,16 @@ def analyze_metaclass(self, defn: ClassDef) -> None:
11681159
elif isinstance(body_node, AssignmentStmt) and len(body_node.lvalues) == 1:
11691160
lvalue = body_node.lvalues[0]
11701161
if isinstance(lvalue, NameExpr) and lvalue.name == "__metaclass__":
1171-
error_context = body_node.rvalue
1172-
if isinstance(body_node.rvalue, NameExpr):
1173-
name = body_node.rvalue.name
1174-
elif isinstance(body_node.rvalue, MemberExpr):
1175-
name = get_member_expr_fullname(body_node.rvalue)
1176-
else:
1177-
name = None
1178-
if name:
1179-
defn.metaclass = name
1180-
else:
1181-
self.fail(
1182-
"Dynamic metaclass not supported for '%s'" % defn.name,
1183-
body_node
1184-
)
1185-
return
1162+
defn.metaclass = body_node.rvalue
11861163
if defn.metaclass:
1187-
if defn.metaclass == '<error>':
1188-
self.fail("Dynamic metaclass not supported for '%s'" % defn.name, error_context)
1164+
if isinstance(defn.metaclass, NameExpr):
1165+
metaclass_name = defn.metaclass.name
1166+
elif isinstance(defn.metaclass, MemberExpr):
1167+
metaclass_name = get_member_expr_fullname(defn.metaclass)
1168+
else:
1169+
self.fail("Dynamic metaclass not supported for '%s'" % defn.name, defn.metaclass)
11891170
return
1190-
sym = self.lookup_qualified(defn.metaclass, error_context)
1171+
sym = self.lookup_qualified(metaclass_name, defn.metaclass)
11911172
if sym is None:
11921173
# Probably a name error - it is already handled elsewhere
11931174
return
@@ -1199,10 +1180,11 @@ def analyze_metaclass(self, defn: ClassDef) -> None:
11991180
# attributes, similar to an 'Any' base class.
12001181
return
12011182
if not isinstance(sym.node, TypeInfo) or sym.node.tuple_type is not None:
1202-
self.fail("Invalid metaclass '%s'" % defn.metaclass, defn)
1183+
self.fail("Invalid metaclass '%s'" % metaclass_name, defn.metaclass)
12031184
return
12041185
if not sym.node.is_metaclass():
1205-
self.fail("Metaclasses not inheriting from 'type' are not supported", defn)
1186+
self.fail("Metaclasses not inheriting from 'type' are not supported",
1187+
defn.metaclass)
12061188
return
12071189
inst = fill_typevars(sym.node)
12081190
assert isinstance(inst, Instance)

mypy/treetransform.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def visit_class_def(self, node: ClassDef) -> ClassDef:
177177
self.block(node.defs),
178178
node.type_vars,
179179
self.expressions(node.base_type_exprs),
180-
node.metaclass)
180+
self.optional_expr(node.metaclass))
181181
new.fullname = node.fullname
182182
new.info = node.info
183183
new.decorators = [self.expr(decorator)

test-data/unit/parse.test

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2480,7 +2480,7 @@ class Foo(metaclass=Bar): pass
24802480
MypyFile:1(
24812481
ClassDef:1(
24822482
Foo
2483-
Metaclass(Bar)
2483+
Metaclass(NameExpr(Bar))
24842484
PassStmt:1()))
24852485

24862486
[case testQualifiedMetaclass]
@@ -2489,7 +2489,9 @@ class Foo(metaclass=foo.Bar): pass
24892489
MypyFile:1(
24902490
ClassDef:1(
24912491
Foo
2492-
Metaclass(foo.Bar)
2492+
Metaclass(MemberExpr:1(
2493+
NameExpr(foo)
2494+
Bar))
24932495
PassStmt:1()))
24942496

24952497
[case testBaseAndMetaclass]
@@ -2498,7 +2500,7 @@ class Foo(foo.bar[x], metaclass=Bar): pass
24982500
MypyFile:1(
24992501
ClassDef:1(
25002502
Foo
2501-
Metaclass(Bar)
2503+
Metaclass(NameExpr(Bar))
25022504
BaseTypeExpr(
25032505
IndexExpr:1(
25042506
MemberExpr:1(
@@ -2521,7 +2523,7 @@ class Foo(_root=None, metaclass=Bar): pass
25212523
MypyFile:1(
25222524
ClassDef:1(
25232525
Foo
2524-
Metaclass(Bar)
2526+
Metaclass(NameExpr(Bar))
25252527
PassStmt:1()))
25262528

25272529
[case testClassKeywordArgsAfterMeta]
@@ -2530,7 +2532,7 @@ class Foo(metaclass=Bar, _root=None): pass
25302532
MypyFile:1(
25312533
ClassDef:1(
25322534
Foo
2533-
Metaclass(Bar)
2535+
Metaclass(NameExpr(Bar))
25342536
PassStmt:1()))
25352537

25362538
[case testNamesThatAreNoLongerKeywords]

test-data/unit/semanal-abstractclasses.test

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ MypyFile:1(
1313
Import:2(typing)
1414
ClassDef:4(
1515
A
16-
Metaclass(ABCMeta)
16+
Metaclass(NameExpr(ABCMeta [abc.ABCMeta]))
1717
Decorator:5(
1818
Var(g)
1919
FuncDef:6(
@@ -49,11 +49,11 @@ MypyFile:1(
4949
Import:2(typing)
5050
ClassDef:4(
5151
A
52-
Metaclass(ABCMeta)
52+
Metaclass(NameExpr(ABCMeta [abc.ABCMeta]))
5353
PassStmt:4())
5454
ClassDef:5(
5555
B
56-
Metaclass(ABCMeta)
56+
Metaclass(NameExpr(ABCMeta [abc.ABCMeta]))
5757
PassStmt:5())
5858
ClassDef:6(
5959
C
@@ -106,7 +106,7 @@ MypyFile:1(
106106
Import:3(typing)
107107
ClassDef:5(
108108
A
109-
Metaclass(ABCMeta)
109+
Metaclass(NameExpr(ABCMeta [abc.ABCMeta]))
110110
Decorator:6(
111111
Var(g)
112112
FuncDef:7(

test-data/unit/semanal-classes.test

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,9 @@ MypyFile:1(
411411
Import:1(abc)
412412
ClassDef:2(
413413
A
414-
Metaclass(abc.ABCMeta)
414+
Metaclass(MemberExpr:2(
415+
NameExpr(abc)
416+
ABCMeta [abc.ABCMeta]))
415417
PassStmt:2()))
416418

417419
[case testStaticMethod]

0 commit comments

Comments
 (0)