import lib.core.Gen.GenTypes as GenTypes import lib.core.Gen.GenApi as GenApi from stdint import * import ast import mlirlite import memhub import string import stdio import viperlib import lib.core.Gen.Translator as HT from . import CallGenBase # CallGenProp - split from CallGenMixin (stage B): property access (getter/setter/deleter / static class calls / list builtins) # ============================================================ novirtual class CallGenProp(CallGenBase.CallGenBase): """Property access (getter/setter/deleter / static class calls / list builtins)""" # Property methods (@property/@x.setter/@x.deleter) and static/class call helpers. # Translate obj.prop / obj.prop=v / del obj.prop / Class.method() into the matching {sha1}.ClassName.prop call. # _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*, 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 # Build the lookup name: ClassName.method_name lookup_name: str = pool.alloc(128) if lookup_name is None: return None viperlib.snprintf(lookup_name, 128, "%s.%s", class_name, method_name) # Look up the function (exact/suffix match) found_func: mlirlite.Function* = self.find_func_in_module(mod, lookup_name) # Determine the call name call_name: str = lookup_name if found_func is not None: found_name: str = mlirlite.function_get_name(found_func) if found_name is not None: call_name = found_name else: # Not found: build the call name from the class's owning module SHA1 cls_sha1_pc: str = self.Gen.StructGen.get_struct_sha1(class_name) if cls_sha1_pc is not None: mlen_pc: sizet = string.strlen(cls_sha1_pc) + string.strlen(lookup_name) + 2 mangled_pc: str = pool.alloc(mlen_pc) if mangled_pc is not None: viperlib.snprintf(mangled_pc, mlen_pc, "%s.%s", cls_sha1_pc, lookup_name) call_name = mangled_pc # Determine the return type i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool) call_ret_ty: mlirlite.LLVMType* = i32_ty if found_func is not None: frt: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(found_func) if frt is not None: call_ret_ty = frt else: sp_ty1: mlirlite.LLVMType* = self._special_method_ret_ty(pool, class_name, method_name) if sp_ty1 is not None: call_ret_ty = sp_ty1 else: cls_sha1_rt: str = self.Gen.StructGen.get_struct_sha1(class_name) # Authoritative source: vpi stub return-annotation cache (in place, no hardcoded whitelist) vpi_ret_ty: mlirlite.LLVMType* = self._lookup_method_ret_ty_via_vpi(pool, cls_sha1_rt, class_name, method_name) if vpi_ret_ty is not None: call_ret_ty = vpi_ret_ty else: # Fallback: parse the module text.ll define line (legacy form not covered by vpi) looked_up_ty: mlirlite.LLVMType* = self._LookupMethodRetTyFromTextLL(pool, cls_sha1_rt, class_name, method_name) if looked_up_ty is not None: call_ret_ty = looked_up_ty else: # Phase A deps not ready (cls_sha1_rt None / the defining module's text.ll missing): # keep the i32 default (build_call(Void) returning None would break expression translation). 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, None, class_name, None, cls_sha1_rt) # 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 if tgt_func_pc is None: tgt_func_pc = self.find_func_in_module(mod, call_name) if tgt_func_pc is not None: prev_pc: mlirlite.Value* = None cur_arg_pc: mlirlite.Value* = call_head cur_param_pc: mlirlite.Param* = mlirlite.function_get_param_head(tgt_func_pc) while cur_arg_pc is not None and cur_param_pc is not None: param_ty_pc: mlirlite.LLVMType* = mlirlite.param_get_ty(cur_param_pc) if param_ty_pc is not None and cur_arg_pc.Ty is not None: coerced_pc: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, cur_arg_pc, param_ty_pc) if coerced_pc is not None and coerced_pc != cur_arg_pc: # Keep the list intact: the new Value inherits the node's Next; predecessor points to it mlirlite.value_set_next(coerced_pc, cur_arg_pc.Next) if prev_pc is None: call_head = coerced_pc else: mlirlite.value_set_next(prev_pc, coerced_pc) cur_arg_pc = coerced_pc prev_pc = cur_arg_pc cur_arg_pc = cur_arg_pc.Next cur_param_pc = mlirlite.param_get_next(cur_param_pc) # Ensure the declare exists, then call self._ensure_method_declare(pool, mod, call_name, call_ret_ty, call_head, call_count) return mlirlite.build_call(builder, call_name, call_head, call_count, call_ret_ty, 0) # _resolve_obj_class_name - resolve an object expression's class short name. # Prefer the variable annotation (AnnotClassName); fall back to the LLVM type (Ptr(Struct)). def _resolve_obj_class_name(self, pool: memhub.MemBuddy*, trans: HT.Translator*, obj_node: ast.AST*) -> str: """Resolve an object expression's class short name, None on failure""" if pool is None or trans is None or obj_node is None: return None if obj_node.kind() == ast.ASTKind.Name: obj_nm: ast.Name* = ast.Name*(obj_node) if obj_nm.id is not None: var_entry: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, obj_nm.id) annot_val: str = self.Gen.VarGen.var_annot_class_name(var_entry) if annot_val is not None: # [D32] Split the qualified name only on '.' outside brackets: the old strrchr over the # whole string picked the '.' inside "list[sha1.AST*]", yielding a bogus short name. n_av: sizet = string.strlen(annot_val) last_dot_av: sizet = n_av depth_av: int = 0 for ai_av in range(int(n_av)): ch_av: int = annot_val[ai_av] if ch_av == '[': depth_av += 1 elif ch_av == ']': depth_av -= 1 elif ch_av == '.' and depth_av == 0: last_dot_av = ai_av if last_dot_av < n_av: return annot_val + last_dot_av + 1 return annot_val return None # _try_property_getter_attr - try to translate obj.attr into a property getter call. # If the class has an @property/@x.getter for attr, emit call Class.attr(obj); else return None. def _try_property_getter_attr(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, at_node: ast.Attribute*, obj_ptr: mlirlite.Value*, trans: HT.Translator*, fallback_class_name: str = None) -> mlirlite.Value*: """Try a property getter access, return the call result Value; None if not a getter.""" if at_node is None or at_node.attr is None or obj_ptr is None or trans is None: return None class_name: str = self._resolve_obj_class_name(pool, trans, at_node.value) if class_name is None: class_name = fallback_class_name 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 (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, 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*, 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 class_name: str = self._resolve_obj_class_name(pool, trans, target_attr.value) if class_name is None: return 0 meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, target_attr.attr) if meta & GenTypes.META_PROP_SETTER == 0: return 0 # Translate the object value (self argument) obj_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, target_attr.value, None, 0, trans) if obj_val is None: return 0 # Build the argument list: obj -> rhs mlirlite.value_set_next(obj_val, None) mlirlite.value_set_next(rhs_val, None) mlirlite.value_set_next(obj_val, rhs_val) # The setter function name carries the $set suffix 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) # 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. def _try_property_deleter(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, target_attr: ast.Attribute*, trans: HT.Translator*) -> int: """Try a property deleter, 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: return 0 class_name: str = self._resolve_obj_class_name(pool, trans, target_attr.value) if class_name is None: return 0 meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, target_attr.attr) if meta & GenTypes.META_PROP_DELETER == 0: return 0 # Translate the object value (self argument) obj_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, target_attr.value, None, 0, trans) if obj_val is None: return 0 mlirlite.value_set_next(obj_val, None) # The deleter function name carries the $del suffix 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, 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. def _translate_static_class_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, class_name: str, method_name: str, cl: ast.Call*, trans: HT.Translator*) -> mlirlite.Value*: """Translate a ClassName static/class method call, return the call result Value; None otherwise""" if pool is None or builder is None or mod is None or trans is None: return None if class_name is None or method_name is None or cl is None: return None meta: int = self.Gen.ClassGenBase.lookup_method_meta(class_name, method_name) is_static: int = 0 is_class: int = 0 if meta & GenTypes.META_STATIC != 0: is_static = 1 if meta & GenTypes.META_CLASS != 0: is_class = 1 if is_static == 0 and is_class == 0: return None # Build the argument list head: mlirlite.Value* = None total_count: int = 0 # @classmethod: prepend a stack-allocated class pointer as the cls argument if is_class != 0: struct_ty_cc: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(class_name) if struct_ty_cc is None: return None cls_ptr: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, struct_ty_cc) if cls_ptr is None: return None mlirlite.value_set_next(cls_ptr, None) head = cls_ptr total_count = 1 # Translate the call arguments cargs: list[ast.AST*]* = cl.args can: sizet = 0 if cargs is not None: can = len(cargs) tail: mlirlite.Value* = head for ai in range(int(can)): arg_node: ast.AST* = cargs.get(ai) if arg_node is None: continue arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node, None, 0, trans) if arg_val is None: continue mlirlite.value_set_next(arg_val, None) if head is None: head = arg_val tail = arg_val else: mlirlite.value_set_next(tail, arg_val) tail = arg_val total_count += 1 # 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. # 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. def _is_list_builtin_method(self, class_name: str, method_name: str) -> int: """Test whether (class_name, method_name) is a list generic builtin method""" if class_name is None or method_name is None: return 0 # Class name must start with "list" (list / list[AST*] specializations) if string.strncmp(class_name, "list", 4) != 0: return 0 # Method name must be one of the list builtin methods if string.strcmp(method_name, "append") == 0: return 1 if string.strcmp(method_name, "get") == 0: return 1 if string.strcmp(method_name, "set") == 0: return 1 if string.strcmp(method_name, "pop") == 0: return 1 if string.strcmp(method_name, "clear") == 0: return 1 if string.strcmp(method_name, "__iter__") == 0: return 1 if string.strcmp(method_name, "__len__") == 0: return 1 if string.strcmp(method_name, "__getitem__") == 0: return 1 if string.strcmp(method_name, "__setitem__") == 0: return 1 return 0 # _strip_module_sha1_prefix - strip the cross-module specialization sha1 prefix. # Receiver struct names often carry a "{16 hex}." prefix; use GenApi.sha1_prefix_len (C10). def _strip_module_sha1_prefix(self, name: str) -> str: """Strip the "{16hex}." prefix (return the input unchanged if absent; returns a substring of the input)""" if name is None: return None pfx: int = GenApi.sha1_prefix_len(name) return name + pfx if pfx != 0 else name # _resolve_generic_receiver_name - resolve the receiver's actual generic specialization name (IR truth). # Strip the sha1 prefix, take the base before '['; bare generic base => fail-fast (D25), else None. def _resolve_generic_receiver_name(self, pool: memhub.MemBuddy*, struct_ty: mlirlite.LLVMType*, method_name: str, cl: ast.Call*) -> str: """Return the actual generic specialization name (sha1 stripped) or None; a bare generic base fails fast""" if struct_ty is None: return None ty_nm: str = mlirlite.get_struct_name(struct_ty) if ty_nm is None or ty_nm[0] == '\0': return None short_nm: str = self._strip_module_sha1_prefix(ty_nm) lb: str = string.strchr(short_nm, '[') bare_len: sizet = 0 if lb is not None: bare_len = sizet(uint64(lb) - uint64(short_nm)) else: bare_len = string.strlen(short_nm) if bare_len == 0: return None base_nm: str = pool.alloc(bare_len + 1) if base_nm is None: return None string.strncpy(base_nm, short_nm, bare_len) base_nm[bare_len] = '\0' # Base is not a registered generic template (plain class receiver) => not generic; return None if self.Gen.ClassGenGeneric._find_generic_template(base_nm) is None: return None if lb is not None: return short_nm # Bare generic base (no type args): element type unknown -> fail-fast (D25) self.Gen.TypeGenErr.fatal_error(cl, f"the actual struct type of the receiver of method '{method_name}' is a bare generic base " f"'{short_nm}*' (type arguments lost), so the element type is unknown; " f"silently using the default element type is forbidden (treating elements as i32 would truncate pointers); " f"make the variable annotation/type carry type arguments (e.g. list[ast.AST*])", "TypeError") return None # _is_list_builtin_receiver - test list builtin methods from the receiver's actual type. # Prefer the actual LLVM struct name (sha1 stripped); fall back to the annotation/registered name. def _is_list_builtin_receiver(self, pool: memhub.MemBuddy*, recv_val: mlirlite.Value*, fallback_name: str, method_name: str, cl: ast.Call*) -> int: """Test list builtin methods from the receiver's actual type (fall back to the annotation name if unavailable)""" sty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(recv_val) if sty is not None: ty_nm: str = mlirlite.get_struct_name(sty) if ty_nm is not None and ty_nm[0] != '\0': # Generic template receiver: bare base fail-fast; with type args use the actual specialization act_nm: str = self._resolve_generic_receiver_name(pool, sty, method_name, cl) if act_nm is not None: return self._is_list_builtin_method(act_nm, method_name) # Non-generic template receiver: judge by the actual short name (sha1 stripped) return self._is_list_builtin_method(self._strip_module_sha1_prefix(ty_nm), method_name) # Actual type unavailable -> fall back to the annotation/registered name return self._is_list_builtin_method(fallback_name, method_name)