Skip to content

Commit fa3566a

Browse files
authored
[mypyc] Transform object.__new__ inside __new__ (#19866)
#19739 introduced support for compiling `__new__` methods in native classes. Native classes differ internally from regular python types which results in `TypeError`s being raised when `object.__new__(cls)` is called when `cls` is a native class. To avoid making this call, `super().__new__(cls)` is transformed into a call to an internal setup function. I forgot to replicate this for calls to equivalent `object.__new__(cls)` calls so this PR fixes that. This introduced a regression because before my changes, `__new__` methods with `object.__new__(cls)` were effectively ignored at runtime, and after my changes they started raising `TypeError`s. Note that these calls are left as-is outside of `__new__` methods so it's still possible to trigger the `TypeError` but that is not a regression as this was the case before. For example this code: ``` class Test: pass t = object.__new__(Test) ``` results in `TypeError: object.__new__(Test) is not safe, use Test.__new__()`. This differs from interpreted python but the error message is actually correct in that using `Test.__new__(Test)` instead works.
1 parent feeb3f0 commit fa3566a

File tree

6 files changed

+620
-28
lines changed

6 files changed

+620
-28
lines changed

mypyc/irbuild/builder.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,6 +1437,10 @@ def add_function(self, func_ir: FuncIR, line: int) -> None:
14371437
self.function_names.add(name)
14381438
self.functions.append(func_ir)
14391439

1440+
def get_current_class_ir(self) -> ClassIR | None:
1441+
type_info = self.fn_info.fitem.info
1442+
return self.mapper.type_to_ir.get(type_info)
1443+
14401444

14411445
def gen_arg_defaults(builder: IRBuilder) -> None:
14421446
"""Generate blocks for arguments that have default values.

mypyc/irbuild/expression.py

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@
5757
from mypyc.ir.ops import (
5858
Assign,
5959
BasicBlock,
60-
Call,
6160
ComparisonOp,
6261
Integer,
6362
LoadAddress,
@@ -98,7 +97,11 @@
9897
join_formatted_strings,
9998
tokenizer_printf_style,
10099
)
101-
from mypyc.irbuild.specialize import apply_function_specialization, apply_method_specialization
100+
from mypyc.irbuild.specialize import (
101+
apply_function_specialization,
102+
apply_method_specialization,
103+
translate_object_new,
104+
)
102105
from mypyc.primitives.bytes_ops import bytes_slice_op
103106
from mypyc.primitives.dict_ops import dict_get_item_op, dict_new_op, exact_dict_set_item_op
104107
from mypyc.primitives.generic_ops import iter_op, name_op
@@ -473,35 +476,15 @@ def translate_super_method_call(builder: IRBuilder, expr: CallExpr, callee: Supe
473476
if callee.name in base.method_decls:
474477
break
475478
else:
479+
if callee.name == "__new__":
480+
result = translate_object_new(builder, expr, MemberExpr(callee.call, "__new__"))
481+
if result:
482+
return result
476483
if ir.is_ext_class and ir.builtin_base is None and not ir.inherits_python:
477484
if callee.name == "__init__" and len(expr.args) == 0:
478485
# Call translates to object.__init__(self), which is a
479486
# no-op, so omit the call.
480487
return builder.none()
481-
elif callee.name == "__new__":
482-
# object.__new__(cls)
483-
assert (
484-
len(expr.args) == 1
485-
), f"Expected object.__new__() call to have exactly 1 argument, got {len(expr.args)}"
486-
typ_arg = expr.args[0]
487-
method_args = builder.fn_info.fitem.arg_names
488-
if (
489-
isinstance(typ_arg, NameExpr)
490-
and len(method_args) > 0
491-
and method_args[0] == typ_arg.name
492-
):
493-
subtype = builder.accept(expr.args[0])
494-
return builder.add(Call(ir.setup, [subtype], expr.line))
495-
496-
if callee.name == "__new__":
497-
call = "super().__new__()"
498-
if not ir.is_ext_class:
499-
builder.error(f"{call} not supported for non-extension classes", expr.line)
500-
if ir.inherits_python:
501-
builder.error(
502-
f"{call} not supported for classes inheriting from non-native classes",
503-
expr.line,
504-
)
505488
return translate_call(builder, expr, callee)
506489

507490
decl = base.method_decl(callee.name)

mypyc/irbuild/specialize.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@
3030
NameExpr,
3131
RefExpr,
3232
StrExpr,
33+
SuperExpr,
3334
TupleExpr,
3435
Var,
3536
)
3637
from mypy.types import AnyType, TypeOfAny
3738
from mypyc.ir.ops import (
3839
BasicBlock,
40+
Call,
3941
Extend,
4042
Integer,
4143
RaiseStandardError,
@@ -68,6 +70,7 @@
6870
is_list_rprimitive,
6971
is_uint8_rprimitive,
7072
list_rprimitive,
73+
object_rprimitive,
7174
set_rprimitive,
7275
str_rprimitive,
7376
uint8_rprimitive,
@@ -1002,3 +1005,44 @@ def translate_ord(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value
10021005
if isinstance(arg, (StrExpr, BytesExpr)) and len(arg.value) == 1:
10031006
return Integer(ord(arg.value))
10041007
return None
1008+
1009+
1010+
@specialize_function("__new__", object_rprimitive)
1011+
def translate_object_new(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
1012+
fn = builder.fn_info
1013+
if fn.name != "__new__":
1014+
return None
1015+
1016+
is_super_new = isinstance(expr.callee, SuperExpr)
1017+
is_object_new = (
1018+
isinstance(callee, MemberExpr)
1019+
and isinstance(callee.expr, NameExpr)
1020+
and callee.expr.fullname == "builtins.object"
1021+
)
1022+
if not (is_super_new or is_object_new):
1023+
return None
1024+
1025+
ir = builder.get_current_class_ir()
1026+
if ir is None:
1027+
return None
1028+
1029+
call = '"object.__new__()"'
1030+
if not ir.is_ext_class:
1031+
builder.error(f"{call} not supported for non-extension classes", expr.line)
1032+
return None
1033+
if ir.inherits_python:
1034+
builder.error(
1035+
f"{call} not supported for classes inheriting from non-native classes", expr.line
1036+
)
1037+
return None
1038+
if len(expr.args) != 1:
1039+
builder.error(f"{call} supported only with 1 argument, got {len(expr.args)}", expr.line)
1040+
return None
1041+
1042+
typ_arg = expr.args[0]
1043+
method_args = fn.fitem.arg_names
1044+
if isinstance(typ_arg, NameExpr) and len(method_args) > 0 and method_args[0] == typ_arg.name:
1045+
subtype = builder.accept(expr.args[0])
1046+
return builder.add(Call(ir.setup, [subtype], expr.line))
1047+
1048+
return None

mypyc/irbuild/statement.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
ListExpr,
3434
Lvalue,
3535
MatchStmt,
36+
NameExpr,
3637
OperatorAssignmentStmt,
3738
RaiseStmt,
3839
ReturnStmt,
@@ -170,10 +171,43 @@ def transform_return_stmt(builder: IRBuilder, stmt: ReturnStmt) -> None:
170171
builder.nonlocal_control[-1].gen_return(builder, retval, stmt.line)
171172

172173

174+
def check_unsupported_cls_assignment(builder: IRBuilder, stmt: AssignmentStmt) -> None:
175+
fn = builder.fn_info
176+
method_args = fn.fitem.arg_names
177+
if fn.name != "__new__" or len(method_args) == 0:
178+
return
179+
180+
ir = builder.get_current_class_ir()
181+
if ir is None or ir.inherits_python or not ir.is_ext_class:
182+
return
183+
184+
cls_arg = method_args[0]
185+
186+
def flatten(lvalues: list[Expression]) -> list[Expression]:
187+
flat = []
188+
for lvalue in lvalues:
189+
if isinstance(lvalue, (TupleExpr, ListExpr)):
190+
flat += flatten(lvalue.items)
191+
else:
192+
flat.append(lvalue)
193+
return flat
194+
195+
lvalues = flatten(stmt.lvalues)
196+
197+
for lvalue in lvalues:
198+
if isinstance(lvalue, NameExpr) and lvalue.name == cls_arg:
199+
# Disallowed because it could break the transformation of object.__new__ calls
200+
# inside __new__ methods.
201+
builder.error(
202+
f'Assignment to argument "{cls_arg}" in "__new__" method unsupported', stmt.line
203+
)
204+
205+
173206
def transform_assignment_stmt(builder: IRBuilder, stmt: AssignmentStmt) -> None:
174207
lvalues = stmt.lvalues
175208
assert lvalues
176209
builder.disallow_class_assignments(lvalues, stmt.line)
210+
check_unsupported_cls_assignment(builder, stmt)
177211
first_lvalue = lvalues[0]
178212
if stmt.type and isinstance(stmt.rvalue, TempNode):
179213
# This is actually a variable annotation without initializer. Don't generate

0 commit comments

Comments
 (0)