Skip to content

Commit 7e65f1e

Browse files
authored
[mypyc] Respect declared metaclasses for non-extension classes (python#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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy