Skip to content

Commit 2f15dc3

Browse files
Merge branch 'master' into any-all-for-helper
2 parents 819efe2 + 44bbb18 commit 2f15dc3

File tree

16 files changed

+1256
-16
lines changed

16 files changed

+1256
-16
lines changed

mypyc/codegen/emitclass.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def dunder_attr_slot(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
6262
"__hash__": ("tp_hash", generate_hash_wrapper),
6363
"__get__": ("tp_descr_get", generate_get_wrapper),
6464
"__getattr__": ("tp_getattro", dunder_attr_slot),
65+
"__setattr__": ("tp_setattro", dunder_attr_slot),
6566
}
6667

6768
AS_MAPPING_SLOT_DEFS: SlotTable = {

mypyc/irbuild/builder.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
Integer,
7777
IntOp,
7878
LoadStatic,
79+
MethodCall,
7980
Op,
8081
PrimitiveDescription,
8182
RaiseStandardError,
@@ -700,7 +701,12 @@ def get_assignment_target(
700701
assert False, "Unsupported lvalue: %r" % lvalue
701702

702703
def read(
703-
self, target: Value | AssignmentTarget, line: int = -1, can_borrow: bool = False
704+
self,
705+
target: Value | AssignmentTarget,
706+
line: int = -1,
707+
*,
708+
can_borrow: bool = False,
709+
allow_error_value: bool = False,
704710
) -> Value:
705711
if isinstance(target, Value):
706712
return target
@@ -716,7 +722,15 @@ def read(
716722
if isinstance(target, AssignmentTargetAttr):
717723
if isinstance(target.obj.type, RInstance) and target.obj.type.class_ir.is_ext_class:
718724
borrow = can_borrow and target.can_borrow
719-
return self.add(GetAttr(target.obj, target.attr, line, borrow=borrow))
725+
return self.add(
726+
GetAttr(
727+
target.obj,
728+
target.attr,
729+
line,
730+
borrow=borrow,
731+
allow_error_value=allow_error_value,
732+
)
733+
)
720734
else:
721735
return self.py_get_attr(target.obj, target.attr, line)
722736

@@ -735,8 +749,15 @@ def assign(self, target: Register | AssignmentTarget, rvalue_reg: Value, line: i
735749
self.add(Assign(target.register, rvalue_reg))
736750
elif isinstance(target, AssignmentTargetAttr):
737751
if isinstance(target.obj_type, RInstance):
738-
rvalue_reg = self.coerce_rvalue(rvalue_reg, target.type, line)
739-
self.add(SetAttr(target.obj, target.attr, rvalue_reg, line))
752+
setattr = target.obj_type.class_ir.get_method("__setattr__")
753+
if setattr:
754+
key = self.load_str(target.attr)
755+
boxed_reg = self.builder.box(rvalue_reg)
756+
call = MethodCall(target.obj, setattr.name, [key, boxed_reg], line)
757+
self.add(call)
758+
else:
759+
rvalue_reg = self.coerce_rvalue(rvalue_reg, target.type, line)
760+
self.add(SetAttr(target.obj, target.attr, rvalue_reg, line))
740761
else:
741762
key = self.load_str(target.attr)
742763
boxed_reg = self.builder.box(rvalue_reg)

mypyc/irbuild/expression.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
apply_function_specialization,
102102
apply_method_specialization,
103103
translate_object_new,
104+
translate_object_setattr,
104105
)
105106
from mypyc.primitives.bytes_ops import bytes_slice_op
106107
from mypyc.primitives.dict_ops import dict_get_item_op, dict_new_op, exact_dict_set_item_op
@@ -480,6 +481,12 @@ def translate_super_method_call(builder: IRBuilder, expr: CallExpr, callee: Supe
480481
result = translate_object_new(builder, expr, MemberExpr(callee.call, "__new__"))
481482
if result:
482483
return result
484+
elif callee.name == "__setattr__":
485+
result = translate_object_setattr(
486+
builder, expr, MemberExpr(callee.call, "__setattr__")
487+
)
488+
if result:
489+
return result
483490
if ir.is_ext_class and ir.builtin_base is None and not ir.inherits_python:
484491
if callee.name == "__init__" and len(expr.args) == 0:
485492
# Call translates to object.__init__(self), which is a

mypyc/irbuild/for_helpers.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,12 @@ def for_loop_helper_with_index(
185185

186186
builder.push_loop_stack(step_block, exit_block)
187187

188-
builder.goto_and_activate(condition_block)
188+
if isinstance(length, Integer) and length.value > 0:
189+
builder.goto(body_block)
190+
builder.activate_block(condition_block)
191+
else:
192+
builder.goto_and_activate(condition_block)
193+
189194
for_gen.gen_condition()
190195

191196
builder.activate_block(body_block)

mypyc/irbuild/function.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
from mypyc.ir.rtypes import (
5858
RInstance,
5959
bool_rprimitive,
60+
c_int_rprimitive,
6061
dict_rprimitive,
6162
int_rprimitive,
6263
object_rprimitive,
@@ -415,6 +416,34 @@ def generate_getattr_wrapper(builder: IRBuilder, cdef: ClassDef, getattr: FuncDe
415416
builder.add(Return(getattr_result, line))
416417

417418

419+
def generate_setattr_wrapper(builder: IRBuilder, cdef: ClassDef, setattr: FuncDef) -> None:
420+
"""
421+
Generate a wrapper function for __setattr__ that can be put into the tp_setattro slot.
422+
The wrapper takes two arguments besides self - attribute name and the new value.
423+
Returns 0 on success and -1 on failure. Restrictions are similar to the __getattr__
424+
wrapper above.
425+
426+
This one is simpler because to match interpreted python semantics it's enough to always
427+
call the user-provided function, including for names matching regular attributes.
428+
"""
429+
name = setattr.name + "__wrapper"
430+
ir = builder.mapper.type_to_ir[cdef.info]
431+
line = setattr.line
432+
433+
error_base = f'"__setattr__" not supported in class "{cdef.name}" because '
434+
if ir.allow_interpreted_subclasses:
435+
builder.error(error_base + "it allows interpreted subclasses", line)
436+
if ir.inherits_python:
437+
builder.error(error_base + "it inherits from a non-native class", line)
438+
439+
with builder.enter_method(ir, name, c_int_rprimitive, internal=True):
440+
attr_arg = builder.add_argument("attr", object_rprimitive)
441+
value_arg = builder.add_argument("value", object_rprimitive)
442+
443+
builder.gen_method_call(builder.self(), setattr.name, [attr_arg, value_arg], None, line)
444+
builder.add(Return(Integer(0, c_int_rprimitive), line))
445+
446+
418447
def handle_ext_method(builder: IRBuilder, cdef: ClassDef, fdef: FuncDef) -> None:
419448
# Perform the function of visit_method for methods inside extension classes.
420449
name = fdef.name
@@ -483,6 +512,8 @@ def handle_ext_method(builder: IRBuilder, cdef: ClassDef, fdef: FuncDef) -> None
483512

484513
if fdef.name == "__getattr__":
485514
generate_getattr_wrapper(builder, cdef, fdef)
515+
elif fdef.name == "__setattr__":
516+
generate_setattr_wrapper(builder, cdef, fdef)
486517

487518

488519
def handle_non_ext_method(

mypyc/irbuild/specialize.py

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
Integer,
4343
RaiseStandardError,
4444
Register,
45+
SetAttr,
4546
Truncate,
4647
Unreachable,
4748
Value,
@@ -100,6 +101,7 @@
100101
isinstance_dict,
101102
)
102103
from mypyc.primitives.float_ops import isinstance_float
104+
from mypyc.primitives.generic_ops import generic_setattr
103105
from mypyc.primitives.int_ops import isinstance_int
104106
from mypyc.primitives.list_ops import isinstance_list, new_list_set_item_op
105107
from mypyc.primitives.misc_ops import isinstance_bool
@@ -1051,19 +1053,24 @@ def translate_ord(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value
10511053
return None
10521054

10531055

1054-
@specialize_function("__new__", object_rprimitive)
1055-
def translate_object_new(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
1056-
fn = builder.fn_info
1057-
if fn.name != "__new__":
1058-
return None
1059-
1060-
is_super_new = isinstance(expr.callee, SuperExpr)
1061-
is_object_new = (
1056+
def is_object(callee: RefExpr) -> bool:
1057+
"""Returns True for object.<name> calls."""
1058+
return (
10621059
isinstance(callee, MemberExpr)
10631060
and isinstance(callee.expr, NameExpr)
10641061
and callee.expr.fullname == "builtins.object"
10651062
)
1066-
if not (is_super_new or is_object_new):
1063+
1064+
1065+
def is_super_or_object(expr: CallExpr, callee: RefExpr) -> bool:
1066+
"""Returns True for super().<name> or object.<name> calls."""
1067+
return isinstance(expr.callee, SuperExpr) or is_object(callee)
1068+
1069+
1070+
@specialize_function("__new__", object_rprimitive)
1071+
def translate_object_new(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
1072+
fn = builder.fn_info
1073+
if fn.name != "__new__" or not is_super_or_object(expr, callee):
10671074
return None
10681075

10691076
ir = builder.get_current_class_ir()
@@ -1090,3 +1097,30 @@ def translate_object_new(builder: IRBuilder, expr: CallExpr, callee: RefExpr) ->
10901097
return builder.add(Call(ir.setup, [subtype], expr.line))
10911098

10921099
return None
1100+
1101+
1102+
@specialize_function("__setattr__", object_rprimitive)
1103+
def translate_object_setattr(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None:
1104+
is_super = isinstance(expr.callee, SuperExpr)
1105+
is_object_callee = is_object(callee)
1106+
if not ((is_super and len(expr.args) >= 2) or (is_object_callee and len(expr.args) >= 3)):
1107+
return None
1108+
1109+
self_reg = builder.accept(expr.args[0]) if is_object_callee else builder.self()
1110+
ir = builder.get_current_class_ir()
1111+
if ir and (not ir.is_ext_class or ir.builtin_base or ir.inherits_python):
1112+
return None
1113+
# Need to offset by 1 for super().__setattr__ calls because there is no self arg in this case.
1114+
name_idx = 0 if is_super else 1
1115+
value_idx = 1 if is_super else 2
1116+
attr_name = expr.args[name_idx]
1117+
attr_value = expr.args[value_idx]
1118+
value = builder.accept(attr_value)
1119+
1120+
if isinstance(attr_name, StrExpr) and ir and ir.has_attr(attr_name.value):
1121+
name = attr_name.value
1122+
value = builder.coerce(value, ir.attributes[name], expr.line)
1123+
return builder.add(SetAttr(self_reg, name, value, expr.line))
1124+
1125+
name_reg = builder.accept(attr_name)
1126+
return builder.call_c(generic_setattr, [self_reg, name_reg, value], expr.line)

mypyc/irbuild/statement.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -829,7 +829,14 @@ def transform_try_finally_stmt_async(
829829
# Check if we have a return value
830830
if ret_reg:
831831
return_block, check_old_exc = BasicBlock(), BasicBlock()
832-
builder.add(Branch(builder.read(ret_reg), check_old_exc, return_block, Branch.IS_ERROR))
832+
builder.add(
833+
Branch(
834+
builder.read(ret_reg, allow_error_value=True),
835+
check_old_exc,
836+
return_block,
837+
Branch.IS_ERROR,
838+
)
839+
)
833840

834841
builder.activate_block(return_block)
835842
builder.nonlocal_control[-1].gen_return(builder, builder.read(ret_reg), -1)

mypyc/lib-rt/CPy.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,9 @@ void CPyTrace_LogEvent(const char *location, const char *line, const char *op, c
952952
static inline PyObject *CPyObject_GenericGetAttr(PyObject *self, PyObject *name) {
953953
return _PyObject_GenericGetAttrWithDict(self, name, NULL, 1);
954954
}
955+
static inline int CPyObject_GenericSetAttr(PyObject *self, PyObject *name, PyObject *value) {
956+
return _PyObject_GenericSetAttrWithDict(self, name, value, NULL);
957+
}
955958

956959
#if CPY_3_11_FEATURES
957960
PyObject *CPy_GetName(PyObject *obj);

mypyc/primitives/generic_ops.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,3 +410,10 @@
410410
error_kind=ERR_NEVER,
411411
returns_null=True,
412412
)
413+
414+
generic_setattr = custom_op(
415+
arg_types=[object_rprimitive, object_rprimitive, object_rprimitive],
416+
return_type=c_int_rprimitive,
417+
c_function_name="CPyObject_GenericSetAttr",
418+
error_kind=ERR_NEG_INT,
419+
)

mypyc/test-data/fixtures/ir.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def __init__(self) -> None: pass
4545
def __eq__(self, x: object) -> bool: pass
4646
def __ne__(self, x: object) -> bool: pass
4747
def __str__(self) -> str: pass
48+
def __setattr__(self, k: str, v: object) -> None: pass
4849

4950
class type:
5051
def __init__(self, o: object) -> None: ...

0 commit comments

Comments
 (0)