From c20bd4734baab129d86984ab27e5eec544d53503 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Wed, 7 Oct 2026 19:59:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(gen):=20=E5=80=BC=E5=BC=95=E7=94=A8?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8F=96=E5=9D=80=E6=8E=A5=E5=85=A5=E8=99=9A?= =?UTF-8?q?=E5=88=86=E6=B4=BE/=E6=9E=84=E9=80=A0/=E5=B1=9E=E6=80=A7/?= =?UTF-8?q?=E9=9D=99=E6=80=81=E7=AD=89=E5=85=A8=E9=83=A8=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CallGenBase 新增 _vpi_auto_addr_mode(把原先埋在 _coerce_args_to_def_params 里的 vpi 注解解析抽成唯一实现,两处共用)与 _apply_auto_addr_to_call_args(同模块先查 AutoAddrModes,跨模块回退 vpi 注解)。接入原先完全不查表的路径:虚分派(原在 vpi 取址前 return)、_call_method_on_ptr 覆盖的构造 __new__/__init__(含泛型)与 dunder 分派、属性 getter/setter/deleter、静态/类方法、super()、FakeDuck;属性 setter 由 AssignGenMixin 传入 rhs AST 以支持 &T 取左值地址。 --- App/lib/core/Gen/AssignGenMixin.vp | 3 +- App/lib/core/Gen/Call/CallGenBase.vp | 84 ++++++++++++++++++++++++++++ App/lib/core/Gen/Call/CallGenCall.vp | 18 +++++- App/lib/core/Gen/Call/CallGenCtor.vp | 18 ++++-- App/lib/core/Gen/Call/CallGenFd.vp | 3 + App/lib/core/Gen/Call/CallGenLib.vp | 8 ++- App/lib/core/Gen/Call/CallGenProp.vp | 28 +++++++--- 7 files changed, 143 insertions(+), 19 deletions(-) diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index 9be2a65..7d5ac41 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -2082,7 +2082,8 @@ novirtual class AssignGenMixin: # Narrow target to ast.Attribute* explicitly (required under the strict pointer rules) at_tg: ast.Attribute* = ast.Attribute*(target) # property setter: when attr is a setter, emit the setter call and skip the field store - if self.Gen.CallGenProp._try_property_setter(pool, builder, mod, at_tg, rhs_val, trans) == 1: continue + # (asgn.value gives the setter value param's AST for value-reference auto-address). + if self.Gen.CallGenProp._try_property_setter(pool, builder, mod, at_tg, rhs_val, trans, asgn.value) == 1: continue # FakeDuck: write per-instance str fields through the {var}__meta__ shadow struct fd_an: ast.Attribute* = at_tg if fd_an is not None and fd_an.value is not None and fd_an.attr is not None: diff --git a/App/lib/core/Gen/Call/CallGenBase.vp b/App/lib/core/Gen/Call/CallGenBase.vp index 5552968..896460d 100644 --- a/App/lib/core/Gen/Call/CallGenBase.vp +++ b/App/lib/core/Gen/Call/CallGenBase.vp @@ -176,6 +176,90 @@ novirtual class CallGenBase: return slot return av + # _vpi_auto_addr_mode - value-reference mode of parameter idx for a cross-module ({sha1}-mangled) + # callee, read from its vpi definition-side param annotations. The parsing (split / ensure-load / + # lookup / segment decode) lives here only; _coerce_args_to_def_params and _apply_auto_addr_to_call_args + # both call it, so there is a single implementation. cur_sha1 is the module being compiled: its own + # functions are covered by AutoAddrModes, so the (guaranteed-miss) vpi load is skipped for them. + # Returns 0=none / 1=ref / 2=temp. + def _vpi_auto_addr_mode(self, call_name: str, cur_sha1: str, idx: int) -> int: + """Cross-module ({sha1}.cls.method / {sha1}.func) value-reference mode from the vpi cache; 0 if none/unknown.""" + if call_name is None or idx < 0: return 0 + if GenApi.sha1_prefix_len(call_name) == 0: return 0 + # Same-module callee: AutoAddrModes (registration during this compilation) is authoritative. + if cur_sha1 is not None and string.strncmp(call_name, cur_sha1, 16) == 0: return 0 + # split {sha1}.{rest}: if rest has '.', it is {cls}.{method}, else {func} + rest: str = call_name + 17 + cls_nm: str = None + meth_nm: str = rest + dot_r: str = string.strrchr(rest, 46) + if dot_r is not None: + cls_len: sizet = uint64(dot_r) - uint64(rest) + if cls_len > 0: + cls_buf: bytes = stdlib.malloc(cls_len + 1) + if cls_buf is not None: + string.strncpy(cls_buf, rest, cls_len) + cls_buf[cls_len] = '\0' + cls_nm = cls_buf + meth_nm = dot_r + 1 + if meth_nm is None or meth_nm[0] == '\0': + if cls_nm is not None: stdlib.free(cls_nm) + return 0 + sha1_buf: bytes = stdlib.malloc(17) + if sha1_buf is None: + if cls_nm is not None: stdlib.free(cls_nm) + return 0 + string.strncpy(sha1_buf, call_name, 16) + sha1_buf[16] = '\0' + if self.TempDir is not None and self.TempDirLen > 0: VpiReader._ensure_vpi_param_annot_loaded(sha1_buf, self.TempDir) + prm_annots: str = VpiReader._lookup_vpi_param_annots(sha1_buf, cls_nm, meth_nm) + mode: int = 0 + if prm_annots is not None: mode = VpiReader._vpi_param_annot_seg_mode(prm_annots, idx) + stdlib.free(sha1_buf) + if cls_nm is not None: stdlib.free(cls_nm) + return mode + + # _apply_auto_addr_to_call_args - auto-address the user arguments of a call whose definition params + # are value references (&T -> take the lvalue address / &temp T -> copy into a temp slot). The + # per-parameter mode is the same-module AutoAddrModes table first, then (for a {sha1}-mangled callee) + # its vpi annotations. head/count is the emitted argument chain; the first start_idx slots are + # non-user (self/obj/cls); arg_nodes holds the source ASTs of the user arguments, indexed by + # (param index - start_idx). Returns the rebuilt chain head; nodes beyond count (e.g. defaults + # appended by the caller) are preserved. Idempotent: _apply_auto_addr_arg passes an already-pointer + # argument through, so a caller that also ran translate_call_arg does not double-address. + def _apply_auto_addr_to_call_args(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, + trans: HT.Translator*, callee_name: str, arg_nodes: list[ast.AST*]*, + head: mlirlite.Value*, count: int, start_idx: int) -> mlirlite.Value*: + """Apply value-reference auto-address (&T / &temp T) to a call's user args; returns the chain head.""" + if head is None or count <= 0: return head + if callee_name is None or callee_name[0] == '\0': return head + if trans is None: return head + new_head: mlirlite.Value* = None + new_tail: mlirlite.Value* = None + cur: mlirlite.Value* = head + idx: int = 0 + while cur is not None and idx < count: + node_v: mlirlite.Value* = cur + if idx >= start_idx: + # Redirect to the canonical CallGenBase sub-object: AutoAddrModes / TempDir live there + # (registration targets self.Gen.CallGenBase), not on the sibling Call* sub-object. + mode: int = self.Gen.CallGenBase._auto_addr_mode(callee_name, idx) + if mode == 0: mode = self.Gen.CallGenBase._vpi_auto_addr_mode(callee_name, trans.ModuleSha1, idx) + if mode != 0: + arg_i: int = idx - start_idx + arg_node: ast.AST* = None + if arg_nodes is not None and arg_i < len(arg_nodes): arg_node = arg_nodes.get(arg_i) + node_v = self.Gen.CallGenBase._apply_auto_addr_arg(builder, pool, mod, trans, mode, arg_node, cur) + if node_v is None: node_v = cur + if new_head is None: new_head = node_v + else: mlirlite.value_set_next(new_tail, node_v) + new_tail = node_v + cur = mlirlite.value_get_next(cur) + idx += 1 + if new_head is None: return head + mlirlite.value_set_next(new_tail, cur) + return new_head + def _lookup_module_sha1(self, mod_name: str) -> str: """Look up the SHA1 for a module name via StubMerger.LookupSha1ByModName (exact match + package fallback); None if absent.""" return StubMergerStore.LookupSha1ByModName(mod_name) diff --git a/App/lib/core/Gen/Call/CallGenCall.vp b/App/lib/core/Gen/Call/CallGenCall.vp index 24387e1..b7603e1 100644 --- a/App/lib/core/Gen/Call/CallGenCall.vp +++ b/App/lib/core/Gen/Call/CallGenCall.vp @@ -141,7 +141,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase): # first argument (goes through virtual dispatch if the method is in the vtable). # ============================================================ - def _call_method_on_ptr(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, class_name: str, method_name: str, self_ptr: mlirlite.Value*, extra_args: sizet*, extra_count: int, trans: HT.Translator*, node: ast.AST* = None, is_volatile: int = 0) -> mlirlite.Value*: + def _call_method_on_ptr(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, class_name: str, method_name: str, self_ptr: mlirlite.Value*, extra_args: sizet*, extra_count: int, trans: HT.Translator*, node: ast.AST* = None, is_volatile: int = 0, arg_nodes: list[ast.AST*]* = None) -> mlirlite.Value*: """Call a method on a struct pointer; adds a volatile barrier for constructors.""" if pool is None or builder is None or class_name is None or method_name is None: return None if self_ptr is None: return None @@ -295,6 +295,13 @@ novirtual class CallGenCall(CallGenBase.CallGenBase): total_count: int = 1 + extra_count + # Value-reference (&T / &temp T) params: auto-address the user args before default-filling and + # the param-type coercion below (a temp copy must be taken from the source-typed value). call_name + # is the resolved call symbol ({sha}.Class.method / Class.method), matching the registration key. + head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, call_name, arg_nodes, head, total_count, 1) + # Recover the tail after the (possibly rebuilt) prefix so default args append to the end. + tail = head + while tail is not None and mlirlite.value_get_next(tail) is not None: tail = mlirlite.value_get_next(tail) # Default-argument filling: if the call site supplies fewer args than the # definition, fill the missing ones (defined default, else zero). mdf_defaults: t.CPtr = None @@ -1202,6 +1209,9 @@ novirtual class CallGenCall(CallGenBase.CallGenBase): if mc_has_vt == 1 and mc_method_idx >= 0: # Virtual dispatch must pass the adjusted self_arg (multi-inheritance # this-adjust) but read the vtable from the object head obj_ptr. + # Virtual dispatch returns before the cross-module vpi coercion below, so the value-reference + # (&T / &temp T) auto-address must be applied here (same-module table first, then vpi). + head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, call_name, cargs, head, total_count, 1) return self._do_virtual_call(pool, builder, mod, class_name, method_name, self_arg, head, total_count, found_func, call_ret_ty, obj_ptr) # Cross-module call: coerce args to the vpi definition-side params (width @@ -1510,7 +1520,9 @@ novirtual class CallGenCall(CallGenBase.CallGenBase): pp_sp = mlirlite.param_get_next(pp_sp) pi_sp += 1 if pp_sp is not None: pty_sp = mlirlite.param_get_ty(pp_sp) - arg_val_sp: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node_sp, None, 0, trans, pty_sp) + # Value-reference params (&T / &temp T): translate_call_arg applies the same-module auto-address + # before the coercion below (cross-module vpi addressing is applied in bulk for the whole chain). + arg_val_sp: mlirlite.Value* = self.Gen.CallGenBase.translate_call_arg(builder, pool, mod, trans, call_name, int(ai_sp) + 1, arg_node_sp, pty_sp, None) if arg_val_sp is None: continue if pty_sp is not None: arg_val_sp = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val_sp, pty_sp, arg_node_sp) else: @@ -1549,6 +1561,8 @@ novirtual class CallGenCall(CallGenBase.CallGenBase): if anc_sha1_sp is None or self._dep_text_ll_exists(pool, anc_sha1_sp) == 0: ret_ty_sp = mlirlite.Int32(pool) else: self._fail_method_ret_ty(method_name, cl) # 7. Cross-module: create the declare; emit the call. + # Value-reference (&T / &temp T) params: auto-address the parent-method args (self is slot 0). + head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, call_name, cargs_sp, head, arg_count_sp, 1) if found_func is None: self._ensure_method_declare(pool, mod, call_name, ret_ty_sp, head, arg_count_sp) is_variadic_sp: int = self.Gen.CallGenVa._check_and_promote_variadic_call(pool, builder, mod, call_name, head, arg_count_sp, cargs_sp, 1, trans) return mlirlite.build_call(builder, call_name, head, arg_count_sp, ret_ty_sp, is_variadic_sp) diff --git a/App/lib/core/Gen/Call/CallGenCtor.vp b/App/lib/core/Gen/Call/CallGenCtor.vp index 4b6aa43..66c993f 100644 --- a/App/lib/core/Gen/Call/CallGenCtor.vp +++ b/App/lib/core/Gen/Call/CallGenCtor.vp @@ -127,7 +127,7 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase): new_arg_vals[new_arg_count] = sizet(new_av) new_arg_count += 1 - new_ptr: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name, "__new__", tmp, new_arg_vals, new_arg_count, trans, diag_node_ct, is_volatile) + new_ptr: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name, "__new__", tmp, new_arg_vals, new_arg_count, trans, diag_node_ct, is_volatile, cargs) if new_ptr is not None: storage_ptr = new_ptr # __new__ declares `-> t.CPtr` (i8*) but the class type is Ptr(struct_ty). @@ -159,7 +159,7 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase): arg_vals[real_arg_count] = sizet(av) real_arg_count += 1 - self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name, "__init__", storage_ptr, arg_vals, real_arg_count, trans, diag_node_ct, is_volatile) + self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name, "__init__", storage_ptr, arg_vals, real_arg_count, trans, diag_node_ct, is_volatile, cargs) elif has_new_flag == 0: # No __init__ and no __new__: store args field by field # (with __new__, args were already consumed and must not be stored). @@ -394,7 +394,7 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase): if new_arg_count < 32: new_arg_vals[new_arg_count] = sizet(nkw_av) new_arg_count += 1 - new_ptr: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name, "__new__", tmp, new_arg_vals, new_arg_count, trans, diag_node_ct, is_volatile) + new_ptr: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name, "__new__", tmp, new_arg_vals, new_arg_count, trans, diag_node_ct, is_volatile, cargs) if new_ptr is not None: storage_ptr = new_ptr # __new__ declares `-> t.CPtr` (i8*) but the class type is Ptr(struct_ty). @@ -429,7 +429,7 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase): init_arg_count += 1 # call __init__(storage_ptr, *args) - self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name, "__init__", storage_ptr, init_arg_vals, init_arg_count, trans, diag_node_ct, is_volatile) + self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, class_name, "__init__", storage_ptr, init_arg_vals, init_arg_count, trans, diag_node_ct, is_volatile, cargs) # return the pointer return storage_ptr @@ -850,13 +850,19 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase): # volatile prefix: `a: T = volatile list[int](mb)` -- the ctor writes are volatile gc_volatile: int = cl.IsVolatile + # AST nodes matching extra_args (positional slots) for the value-reference (&T / &temp T) + # auto-address inside _call_method_on_ptr. With keyword args the slot mapping needs the + # __init__ param table, so it is skipped there (temp copies still work; a ref lvalue is skipped). + gc_arg_nodes: list[ast.AST*]* = cargs + if kw_count > 0: gc_arg_nodes = None + # Storage pointer: alloca by default, or __new__'s return when present (__new__ is # optional; matches _translate_struct_ctor to avoid over-declaring for classes without it). storage_ptr: mlirlite.Value* = tmp has_new_flag: int = 0 if struct_entry is not None: has_new_flag = struct_entry.HasNew if has_new_flag != 0: - new_ptr: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "__new__", tmp, extra_args, arg_count, trans, None, gc_volatile) + new_ptr: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "__new__", tmp, extra_args, arg_count, trans, None, gc_volatile, gc_arg_nodes) if new_ptr is not None: storage_ptr = new_ptr # __new__ declares `-> t.CPtr` (i8*) but the class type is Ptr(struct_ty). @@ -871,6 +877,6 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase): self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "__before_init__", storage_ptr, None, 0, trans, None, gc_volatile) # call __init__(storage_ptr, *args) (arg_count = slots 0..highest filled) - self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "__init__", storage_ptr, extra_args, arg_count, trans, None, gc_volatile) + self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "__init__", storage_ptr, extra_args, arg_count, trans, None, gc_volatile, gc_arg_nodes) return storage_ptr \ No newline at end of file diff --git a/App/lib/core/Gen/Call/CallGenFd.vp b/App/lib/core/Gen/Call/CallGenFd.vp index cd3512e..54f8a9f 100644 --- a/App/lib/core/Gen/Call/CallGenFd.vp +++ b/App/lib/core/Gen/Call/CallGenFd.vp @@ -124,6 +124,9 @@ novirtual class CallGenFd(CallGenBase.CallGenBase): if fd_exact is None: fd_exact = self._LookupMethodRetTyFromTextLL(pool, fd_sha1, fd_cls, method_name) if fd_exact is not None: fd_ret_ty = fd_exact fd_total: int = 1 + fd_can + # Value-reference (&T / &temp T) params of the resolved extension method: auto-address the + # user args (the shadow self pointer occupies slot 0). + fd_head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, fd_fname, fd_args, fd_head, fd_total, 1) return mlirlite.build_call(builder, fd_fname, fd_head, fd_total, fd_ret_ty, 0) # _inject_requires_fields - FakeDuck: inject requires fields from the with context. # For each class __requires__ field (registered by fd1), call _find_provider(field) at runtime; if non-NULL bitcast and store it (conditional inject); skip when _find_provider is unregistered. diff --git a/App/lib/core/Gen/Call/CallGenLib.vp b/App/lib/core/Gen/Call/CallGenLib.vp index dce1c3d..a4c0d28 100644 --- a/App/lib/core/Gen/Call/CallGenLib.vp +++ b/App/lib/core/Gen/Call/CallGenLib.vp @@ -209,9 +209,11 @@ novirtual class CallGenLib(CallGenBase.CallGenBase): arg_node: ast.AST* = None if cargs is not None and arg_idx >= 0 and arg_idx < len(cargs): arg_node = cargs.get(arg_idx) # Value-reference params (&T / &temp T): auto-address the argument (cross-module callee). - # The mode comes from the definition-side vpi annotation segment; if the arg is not a - # pointer it is addressed (ref: lvalue) or copied into a temp slot (temp). - mode_cm: int = VpiReader._vpi_param_annot_seg_mode(prm_annots, idx) + # The mode comes from the definition-side vpi annotation segment (shared resolver); if the + # arg is not a pointer it is addressed (ref: lvalue) or copied into a temp slot (temp). + vpi_cur_sha: str = None + if trans is not None: vpi_cur_sha = trans.ModuleSha1 + mode_cm: int = self.Gen.CallGenBase._vpi_auto_addr_mode(call_name, vpi_cur_sha, idx) if mode_cm != 0 and trans is not None and trans.Module is not None: node_v = self.Gen.CallGenBase._apply_auto_addr_arg(builder, pool, trans.Module, trans, mode_cm, arg_node, node_v) # "str / bytes are not generic pointer wildcards": void* / t.CPtr / rawptr may diff --git a/App/lib/core/Gen/Call/CallGenProp.vp b/App/lib/core/Gen/Call/CallGenProp.vp index bbac388..a83f2dc 100644 --- a/App/lib/core/Gen/Call/CallGenProp.vp +++ b/App/lib/core/Gen/Call/CallGenProp.vp @@ -23,7 +23,7 @@ novirtual class CallGenProp(CallGenBase.CallGenBase): # _translate_property_call - translate a property/static method call. # Resolve the call name like _translate_method_call; call_head/call_count is the prebuilt arg list. - def _translate_property_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, class_name: str, method_name: str, call_head: mlirlite.Value*, call_count: int, trans: HT.Translator*) -> mlirlite.Value*: + def _translate_property_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, class_name: str, method_name: str, call_head: mlirlite.Value*, call_count: int, trans: HT.Translator*, arg_nodes: list[ast.AST*]* = None, start_idx: int = 0) -> mlirlite.Value*: """Translate a property/static method call, return the call result Value""" if pool is None or builder is None or class_name is None or method_name is None: return None @@ -74,6 +74,10 @@ novirtual class CallGenProp(CallGenBase.CallGenBase): if cls_sha1_rt is None or self._dep_text_ll_exists(pool, cls_sha1_rt) == 0: call_ret_ty = mlirlite.Int32(pool) else: call_ret_ty = self._fail_method_ret_ty(method_name) + # Value-reference (&T / &temp T) params: auto-address the user args before param-type + # coercion (self/obj or the classmethod cls occupies index 0, skipped via start_idx). + call_head = self._apply_auto_addr_to_call_args(builder, pool, mod, trans, call_name, arg_nodes, call_head, call_count, start_idx) + # Coerce args to the declared param types (same as _translate_method_call); target func: found_func, # else the mangled module declare. Otherwise call(i32) vs declare(i64) mismatches and llc errors. tgt_func_pc: mlirlite.Function* = found_func @@ -135,13 +139,13 @@ novirtual class CallGenProp(CallGenBase.CallGenBase): if class_name is None: return None meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, at_node.attr) if meta & GenTypes.META_PROP_GETTER == 0: return None - # Single-arg call: obj -> getter + # Single-arg call: obj -> getter (no user args to auto-address; obj is slot 0) mlirlite.value_set_next(obj_ptr, None) - return self._translate_property_call(pool, builder, mod, class_name, at_node.attr, obj_ptr, 1, trans) + return self._translate_property_call(pool, builder, mod, class_name, at_node.attr, obj_ptr, 1, trans, None, 1) # _try_property_setter - try to translate obj.attr = v into a property setter call. # If the class has an @x.setter for attr, emit call Class.attr$set(obj, v) and return 1; else 0. - def _try_property_setter(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, target_attr: ast.Attribute*, rhs_val: mlirlite.Value*, trans: HT.Translator*) -> int: + def _try_property_setter(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, target_attr: ast.Attribute*, rhs_val: mlirlite.Value*, trans: HT.Translator*, rhs_node: ast.AST* = None) -> int: """Try a property setter assignment, return 1=handled (call emitted)""" if pool is None or builder is None or mod is None or trans is None: return 0 if target_attr is None or target_attr.attr is None or rhs_val is None: return 0 @@ -160,7 +164,13 @@ novirtual class CallGenProp(CallGenBase.CallGenBase): setter_name_buf: str = pool.alloc(128) if setter_name_buf is None: return 0 viperlib.snprintf(setter_name_buf, 128, "%s$set", target_attr.attr) - self._translate_property_call(pool, builder, mod, class_name, setter_name_buf, obj_val, 2, trans) + # Value-reference (&T / &temp T) setter value param: expose the rhs AST as user arg 0 + # (obj is slot 0) so the helper can take the lvalue address / build a temp copy. + set_arg_nodes: list[ast.AST*]* = None + if rhs_node is not None: + set_arg_nodes = list[ast.AST*](pool, 1) + if set_arg_nodes is not None: set_arg_nodes.append(rhs_node) + self._translate_property_call(pool, builder, mod, class_name, setter_name_buf, obj_val, 2, trans, set_arg_nodes, 1) return 1 # _try_property_deleter - try to translate del obj.attr into a property deleter call. # If the class has an @x.deleter for attr, emit call Class.attr$del(obj) and return 1; else 0. @@ -181,7 +191,7 @@ novirtual class CallGenProp(CallGenBase.CallGenBase): del_name_buf: str = pool.alloc(128) if del_name_buf is None: return 0 viperlib.snprintf(del_name_buf, 128, "%s$del", target_attr.attr) - self._translate_property_call(pool, builder, mod, class_name, del_name_buf, obj_val, 1, trans) + self._translate_property_call(pool, builder, mod, class_name, del_name_buf, obj_val, 1, trans, None, 1) return 1 # _translate_static_class_call - translate ClassName.method(args) for static/class methods. # @staticmethod: call Class.method(args...). @classmethod: prepend a stack-allocated class pointer. @@ -232,7 +242,11 @@ novirtual class CallGenProp(CallGenBase.CallGenBase): # Zero-arg @staticmethod (e.g. PyListObj.new()): head=None is a valid no-arg call, so still route # through _translate_property_call; returning None would fall back to bare-name lookup and fail. - return self._translate_property_call(pool, builder, mod, class_name, method_name, head, total_count, trans) + # Value-reference (&T / &temp T) user args: static methods have no self slot (start_idx=0), + # classmethods carry the cls pointer at slot 0 (start_idx=1). + sc_start_idx: int = 0 + if is_class != 0: sc_start_idx = 1 + return self._translate_property_call(pool, builder, mod, class_name, method_name, head, total_count, trans, cargs, sc_start_idx) # _is_list_builtin_method - test whether (class_name, method_name) is a list generic builtin. # list[T] methods are inlined by translate_call's list branch, not registered as functions.