@@ -28558,6 +28558,15 @@ def poly_dispatch_return_type(mname)
2855828558 @needs_rb_value = 1
2855928559 return "poly"
2856028560 end
28561+ # Setters: mname ends with "=" and at least one class has an
28562+ # attr_writer for the bare name. Return type is the ivar type
28563+ # (Ruby returns the rhs from `x = v`); without this, the result
28564+ # tmp's C type defaults to `mrb_int` and `tmp = rhs` mismatches
28565+ # for non-int slots.
28566+ setter_bname = ""
28567+ if mname.length > 1 && mname[mname.length - 1] == "="
28568+ setter_bname = mname[0, mname.length - 1]
28569+ end
2856128570 common = ""
2856228571 ci = 0
2856328572 while ci < @cls_names.length
@@ -28567,6 +28576,9 @@ def poly_dispatch_return_type(mname)
2856728576 elsif cls_has_attr_reader(ci, mname) == 1
2856828577 # An attr_reader returns the ivar type. Issue #119.
2856928578 rt = cls_ivar_type(ci, "@" + mname)
28579+ elsif setter_bname != "" && cls_has_attr_writer(ci, setter_bname) == 1
28580+ # An attr_writer setter returns the ivar's type.
28581+ rt = cls_ivar_type(ci, "@" + setter_bname)
2857028582 end
2857128583 if rt != ""
2857228584 if common == ""
@@ -28851,6 +28863,39 @@ def compile_poly_method_call(nid, rc, mname)
2885128863 else
2885228864 emit(" if (" + recv_tmp + ".cls_id == " + i.to_s + ") " + tmp + " = " + rhs + ";")
2885328865 end
28866+ elsif mname.length > 1 && mname[mname.length - 1] == "=" && cls_has_attr_writer(i, mname[0, mname.length - 1]) == 1
28867+ # An auto-registered attr_writer setter (`obj.x = v`) on a
28868+ # poly-typed receiver. Without this arm the cls_id dispatch
28869+ # finds neither a real method nor an attr_reader, falls
28870+ # through silently, and the assignment never executes —
28871+ # `obj.x = v` becomes a no-op.
28872+ bname = mname[0, mname.length - 1]
28873+ raw_val = arg_compiled.length > 0 ? arg_compiled[0] : "0"
28874+ raw_t = arg_types.length > 0 ? arg_types[0] : ""
28875+ slot_t = cls_ivar_type(i, "@" + bname)
28876+ # Match the rhs to the slot's concrete C type for *this arm*.
28877+ # Three cases:
28878+ # slot poly + arg concrete -> box the arg.
28879+ # slot concrete + arg poly -> unbox the arg.
28880+ # else -> direct assign.
28881+ # Different arms in the dispatch may take different branches
28882+ # (e.g. one slot is `int`, another is `string`).
28883+ store_val = raw_val
28884+ if slot_t == "poly" && raw_t != "poly" && raw_t != ""
28885+ store_val = box_value_to_poly(raw_t, raw_val)
28886+ elsif slot_t != "poly" && raw_t == "poly"
28887+ store_val = unbox_poly_to(slot_t, raw_val)
28888+ end
28889+ ivar_lhs = "((sp_" + cname + " *)" + recv_tmp + ".v.p)->" + sanitize_ivar("@" + bname)
28890+ # `x = v` returns v in Ruby. The result tmp's type is set by
28891+ # poly_dispatch_return_type (poly when slot types diverge,
28892+ # otherwise the common slot type). Box / coerce the stored
28893+ # value into the tmp's expected shape.
28894+ rhs_for_tmp = store_val
28895+ if is_poly_ret == 1
28896+ rhs_for_tmp = box_val_to_poly(store_val, slot_t)
28897+ end
28898+ emit(" if (" + recv_tmp + ".cls_id == " + i.to_s + ") { " + ivar_lhs + " = " + store_val + "; " + tmp + " = " + rhs_for_tmp + "; }")
2885428899 end
2885528900 i = i + 1
2885628901 end
@@ -29254,7 +29299,13 @@ def box_non_nullable_value_to_poly(at, val)
2925429299 return "sp_box_bool(" + val + ")"
2925529300 end
2925629301 if at == "nil"
29257- return "sp_box_nil()"
29302+ # `val` may be a side-effecting call (e.g. an arm in a
29303+ # cls_id-dispatched table where the user-defined override
29304+ # returns nil because its body is a `puts` / similar). Drop
29305+ # the value but keep the side effect via the comma operator;
29306+ # otherwise the generated dispatch silently elides the call
29307+ # for any subclass whose method's static return type is nil.
29308+ return "((void)(" + val + "), sp_box_nil())"
2925829309 end
2925929310 if at == "symbol"
2926029311 return "sp_box_sym(" + val + ")"
0 commit comments