Skip to content

Commit fa5d942

Browse files
authored
[mypyc] Fix error value check for GetAttr that allows nullable values (#19378)
Also rename `allow_null` to `allow_error_value` to make it clear that this works with non-pointer types such as tuples.
1 parent 0b7afda commit fa5d942

File tree

4 files changed

+43
-22
lines changed

4 files changed

+43
-22
lines changed

mypyc/codegen/emitfunc.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,16 @@ def visit_goto(self, op: Goto) -> None:
209209
if op.label is not self.next_block:
210210
self.emit_line("goto %s;" % self.label(op.label))
211211

212+
def error_value_check(self, value: Value, compare: str) -> str:
213+
typ = value.type
214+
if isinstance(typ, RTuple):
215+
# TODO: What about empty tuple?
216+
return self.emitter.tuple_undefined_check_cond(
217+
typ, self.reg(value), self.c_error_value, compare
218+
)
219+
else:
220+
return f"{self.reg(value)} {compare} {self.c_error_value(typ)}"
221+
212222
def visit_branch(self, op: Branch) -> None:
213223
true, false = op.true, op.false
214224
negated = op.negated
@@ -225,15 +235,8 @@ def visit_branch(self, op: Branch) -> None:
225235
expr_result = self.reg(op.value)
226236
cond = f"{neg}{expr_result}"
227237
elif op.op == Branch.IS_ERROR:
228-
typ = op.value.type
229238
compare = "!=" if negated else "=="
230-
if isinstance(typ, RTuple):
231-
# TODO: What about empty tuple?
232-
cond = self.emitter.tuple_undefined_check_cond(
233-
typ, self.reg(op.value), self.c_error_value, compare
234-
)
235-
else:
236-
cond = f"{self.reg(op.value)} {compare} {self.c_error_value(typ)}"
239+
cond = self.error_value_check(op.value, compare)
237240
else:
238241
assert False, "Invalid branch"
239242

@@ -358,8 +361,8 @@ def get_attr_expr(self, obj: str, op: GetAttr | SetAttr, decl_cl: ClassIR) -> st
358361
return f"({cast}{obj})->{self.emitter.attr(op.attr)}"
359362

360363
def visit_get_attr(self, op: GetAttr) -> None:
361-
if op.allow_null:
362-
self.get_attr_with_allow_null(op)
364+
if op.allow_error_value:
365+
self.get_attr_with_allow_error_value(op)
363366
return
364367
dest = self.reg(op)
365368
obj = self.reg(op.obj)
@@ -429,8 +432,11 @@ def visit_get_attr(self, op: GetAttr) -> None:
429432
elif not always_defined:
430433
self.emitter.emit_line("}")
431434

432-
def get_attr_with_allow_null(self, op: GetAttr) -> None:
433-
"""Handle GetAttr with allow_null=True which allows NULL without raising AttributeError."""
435+
def get_attr_with_allow_error_value(self, op: GetAttr) -> None:
436+
"""Handle GetAttr with allow_error_value=True.
437+
438+
This allows NULL or other error value without raising AttributeError.
439+
"""
434440
dest = self.reg(op)
435441
obj = self.reg(op.obj)
436442
rtype = op.class_type
@@ -443,7 +449,8 @@ def get_attr_with_allow_null(self, op: GetAttr) -> None:
443449

444450
# Only emit inc_ref if not NULL
445451
if attr_rtype.is_refcounted and not op.is_borrowed:
446-
self.emitter.emit_line(f"if ({dest} != NULL) {{")
452+
check = self.error_value_check(op, "!=")
453+
self.emitter.emit_line(f"if ({check}) {{")
447454
self.emitter.emit_inc_ref(dest, attr_rtype)
448455
self.emitter.emit_line("}")
449456

mypyc/ir/ops.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -811,17 +811,23 @@ class GetAttr(RegisterOp):
811811
error_kind = ERR_MAGIC
812812

813813
def __init__(
814-
self, obj: Value, attr: str, line: int, *, borrow: bool = False, allow_null: bool = False
814+
self,
815+
obj: Value,
816+
attr: str,
817+
line: int,
818+
*,
819+
borrow: bool = False,
820+
allow_error_value: bool = False,
815821
) -> None:
816822
super().__init__(line)
817823
self.obj = obj
818824
self.attr = attr
819-
self.allow_null = allow_null
825+
self.allow_error_value = allow_error_value
820826
assert isinstance(obj.type, RInstance), "Attribute access not supported: %s" % obj.type
821827
self.class_type = obj.type
822828
attr_type = obj.type.attr_type(attr)
823829
self.type = attr_type
824-
if allow_null:
830+
if allow_error_value:
825831
self.error_kind = ERR_NEVER
826832
elif attr_type.error_overlap:
827833
self.error_kind = ERR_MAGIC_OVERLAPPING

mypyc/irbuild/builder.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -709,13 +709,9 @@ def read(
709709
assert False, "Unsupported lvalue: %r" % target
710710

711711
def read_nullable_attr(self, obj: Value, attr: str, line: int = -1) -> Value:
712-
"""Read an attribute that might be NULL without raising AttributeError.
713-
714-
This is used for reading spill targets in try/finally blocks where NULL
715-
indicates the non-return path was taken.
716-
"""
712+
"""Read an attribute that might have an error value without raising AttributeError."""
717713
assert isinstance(obj.type, RInstance) and obj.type.class_ir.is_ext_class
718-
return self.add(GetAttr(obj, attr, line, allow_null=True))
714+
return self.add(GetAttr(obj, attr, line, allow_error_value=True))
719715

720716
def assign(self, target: Register | AssignmentTarget, rvalue_reg: Value, line: int) -> None:
721717
if isinstance(target, Register):

mypyc/test/test_emitfunc.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ def add_local(name: str, rtype: RType) -> Register:
111111
"y": int_rprimitive,
112112
"i1": int64_rprimitive,
113113
"i2": int32_rprimitive,
114+
"t": RTuple([object_rprimitive, object_rprimitive]),
114115
}
115116
ir.bitmap_attrs = ["i1", "i2"]
116117
compute_vtable(ir)
@@ -418,6 +419,17 @@ def test_get_attr_with_bitmap(self) -> None:
418419
""",
419420
)
420421

422+
def test_get_attr_nullable_with_tuple(self) -> None:
423+
self.assert_emit(
424+
GetAttr(self.r, "t", 1, allow_error_value=True),
425+
"""cpy_r_r0 = ((mod___AObject *)cpy_r_r)->_t;
426+
if (cpy_r_r0.f0 != NULL) {
427+
CPy_INCREF(cpy_r_r0.f0);
428+
CPy_INCREF(cpy_r_r0.f1);
429+
}
430+
""",
431+
)
432+
421433
def test_set_attr(self) -> None:
422434
self.assert_emit(
423435
SetAttr(self.r, "y", self.m, 1),

0 commit comments

Comments
 (0)