Skip to content

Commit 7e65f1e

Browse files
authored
[mypyc] Respect declared metaclasses for non-extension classes (#8119)
1 parent 821630c commit 7e65f1e

File tree

4 files changed

+57
-20
lines changed

4 files changed

+57
-20
lines changed

mypy/semanal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1578,7 +1578,7 @@ def update_metaclass(self, defn: ClassDef) -> None:
15781578
if len(defn.base_type_exprs) == 1:
15791579
base_expr = defn.base_type_exprs[0]
15801580
if isinstance(base_expr, CallExpr) and isinstance(base_expr.callee, RefExpr):
1581-
base_expr.callee.accept(self)
1581+
base_expr.accept(self)
15821582
if (base_expr.callee.fullname in {'six.with_metaclass',
15831583
'future.utils.with_metaclass',
15841584
'past.utils.with_metaclass'}

mypyc/genops.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,10 +1375,7 @@ def load_non_ext_class(self, ir: ClassIR, non_ext: NonExtClassInfo, line: int) -
13751375

13761376
self.finish_non_ext_dict(non_ext, line)
13771377

1378-
metaclass = self.primitive_op(type_object_op, [], line)
1379-
metaclass = self.primitive_op(py_calc_meta_op, [metaclass, non_ext.bases], line)
1380-
1381-
class_type_obj = self.py_call(metaclass,
1378+
class_type_obj = self.py_call(non_ext.metaclass,
13821379
[cls_name, non_ext.bases, non_ext.dict],
13831380
line)
13841381
return class_type_obj
@@ -1453,16 +1450,22 @@ def add_non_ext_class_attr(self, non_ext: NonExtClassInfo, lvalue: NameExpr,
14531450
):
14541451
attr_to_cache.append(lvalue)
14551452

1456-
def setup_non_ext_dict(self, cdef: ClassDef, bases: Value) -> Value:
1453+
def find_non_ext_metaclass(self, cdef: ClassDef, bases: Value) -> Value:
1454+
"""Find the metaclass of a class from its defs and bases. """
1455+
if cdef.metaclass:
1456+
declared_metaclass = self.accept(cdef.metaclass)
1457+
else:
1458+
declared_metaclass = self.primitive_op(type_object_op, [], cdef.line)
1459+
1460+
return self.primitive_op(py_calc_meta_op, [declared_metaclass, bases], cdef.line)
1461+
1462+
def setup_non_ext_dict(self, cdef: ClassDef, metaclass: Value, bases: Value) -> Value:
14571463
"""
14581464
Initialize the class dictionary for a non-extension class. This class dictionary
14591465
is passed to the metaclass constructor.
14601466
"""
14611467

14621468
# Check if the metaclass defines a __prepare__ method, and if so, call it.
1463-
metaclass = self.primitive_op(type_object_op, [], cdef.line)
1464-
metaclass = self.primitive_op(py_calc_meta_op, [metaclass, bases],
1465-
cdef.line)
14661469
has_prepare = self.primitive_op(py_hasattr_op,
14671470
[metaclass,
14681471
self.load_static_unicode('__prepare__')], cdef.line)
@@ -1506,6 +1509,7 @@ def dataclass_non_ext_info(self, cdef: ClassDef) -> Optional[NonExtClassInfo]:
15061509
self.primitive_op(new_dict_op, [], cdef.line),
15071510
self.add(TupleSet([], cdef.line)),
15081511
self.primitive_op(new_dict_op, [], cdef.line),
1512+
self.primitive_op(type_object_op, [], cdef.line),
15091513
)
15101514
else:
15111515
return None
@@ -1560,12 +1564,13 @@ def visit_class_def(self, cdef: ClassDef) -> None:
15601564
dataclass_non_ext = self.dataclass_non_ext_info(cdef)
15611565
else:
15621566
non_ext_bases = self.populate_non_ext_bases(cdef)
1563-
non_ext_dict = self.setup_non_ext_dict(cdef, non_ext_bases)
1567+
non_ext_metaclass = self.find_non_ext_metaclass(cdef, non_ext_bases)
1568+
non_ext_dict = self.setup_non_ext_dict(cdef, non_ext_metaclass, non_ext_bases)
15641569
# We populate __annotations__ for non-extension classes
15651570
# because dataclasses uses it to determine which attributes to compute on.
15661571
# TODO: Maybe generate more precise types for annotations
15671572
non_ext_anns = self.primitive_op(new_dict_op, [], cdef.line)
1568-
non_ext = NonExtClassInfo(non_ext_dict, non_ext_bases, non_ext_anns)
1573+
non_ext = NonExtClassInfo(non_ext_dict, non_ext_bases, non_ext_anns, non_ext_metaclass)
15691574
dataclass_non_ext = None
15701575
type_obj = None
15711576

mypyc/ops.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2022,17 +2022,15 @@ class NonExtClassInfo:
20222022
"""Information needed to construct a non-extension class.
20232023
20242024
2025-
Includes the class dictionary, a tuple of base classes, and
2026-
the class annotations dictionary.
2025+
Includes the class dictionary, a tuple of base classes,
2026+
the class annotations dictionary, and the metaclass.
20272027
"""
20282028

2029-
def __init__(self,
2030-
non_ext_dict: Value,
2031-
non_ext_bases: Value,
2032-
non_ext_anns: Value) -> None:
2033-
self.dict = non_ext_dict
2034-
self.bases = non_ext_bases
2035-
self.anns = non_ext_anns
2029+
def __init__(self, dict: Value, bases: Value, anns: Value, metaclass: Value) -> None:
2030+
self.dict = dict
2031+
self.bases = bases
2032+
self.anns = anns
2033+
self.metaclass = metaclass
20362034

20372035

20382036
LiteralsMap = Dict[Tuple[Type[object], Union[int, float, str, bytes, complex]], str]

mypyc/test-data/run-classes.test

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,40 @@ try:
11051105
except TypeError as e:
11061106
assert(str(e) == "mypyc classes can't have a metaclass")
11071107

1108+
[case testMetaclass]
1109+
from meta import Meta
1110+
import six
1111+
1112+
class Nothing1(metaclass=Meta):
1113+
pass
1114+
1115+
def ident(x): return x
1116+
1117+
@ident
1118+
class Test:
1119+
pass
1120+
1121+
class Nothing2(six.with_metaclass(Meta, Test)):
1122+
pass
1123+
1124+
@six.add_metaclass(Meta)
1125+
class Nothing3:
1126+
pass
1127+
1128+
[file meta.py]
1129+
from typing import Any
1130+
class Meta(type):
1131+
def __new__(mcs, name, bases, dct):
1132+
dct['X'] = 10
1133+
return super().__new__(mcs, name, bases, dct)
1134+
1135+
1136+
[file driver.py]
1137+
from native import Nothing1, Nothing2, Nothing3
1138+
assert Nothing1.X == 10
1139+
assert Nothing2.X == 10
1140+
assert Nothing3.X == 10
1141+
11081142
[case testPickling]
11091143
from mypy_extensions import trait
11101144
from typing import Any, TypeVar, Generic

0 commit comments

Comments
 (0)