import lib.core.Gen.GenTypes as GenTypes import t, c from stdint import * import ast import mlirlite import memhub import string import viperlib import lib.core.VLogger as VLogger import lib.core.Gen.Translator as HT import lib.core.CompilerFactory as CompilerFactory from . import ClassGenBase import lib.Stub.StubMerger.StubMergerStore as StubMergerStore import lib.core.Gen.Type.TypeSpec as TSpec # Generic specialization recursion depth limit (guards against generic loops): # normal nesting is <= 3 (e.g. list[list[str]]); 64 is far beyond sane, so hitting it means a loop. define GENERIC_SPEC_MAX_DEPTH = 64 # Generic template table capacity (aligned with the template count the GenericClassNames pool tables can hold) define GENERIC_TEMPLATE_MAX = 512 # Generic specialization cache table capacity define SPEC_CACHE_MAX = 512 # Signedness sentinel meaning "take from Spec.IsSigned" (distinct from the three TSpec.TS_SIGN_* values) define SIGN_FROM_SPEC = -2 # ClassGenGeneric - generic template registration / specialization / cloning novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase): """Generic template registration, specialization and cloning.""" # Cluster C: generic template registration/specialization (P5-2 phase 3 migration; bodies moved # from HandlesClassDef.vp unchanged; _generic_class_* tables now live in self.GenericClassNames etc.). def _is_generic_class(self, tp: list[str]*) -> int: """Return 1 when the class type_params mark a generic class; 0 otherwise.""" if tp is None: return 0 return 1 def _dup_perm_str(self, s: str) -> str: """Deep-copy a string into the permanent pool; return the input when unavailable.""" if s is None: return None perm: memhub.MemBuddy* = CompilerFactory.factory.PermPool if perm is None: return s n: sizet = string.strlen(s) b: str = perm.alloc(n + 1) if b is None: return s string.strcpy(b, s) return b def _register_generic_template(self, pool: memhub.MemBuddy*, cd: ast.ClassDef*, module_sha1: str) -> None: """Register a generic class template (name, ClassDef node, source module SHA1).""" if cd is None or cd.name is None: return perm: memhub.MemBuddy* = CompilerFactory.factory.PermPool table_pool: memhub.MemBuddy* = perm if table_pool is None: table_pool = pool if self.GenericClassNames is None: self.GenericClassNames = list[str](table_pool, 8) self.GenericClassNodes = list[ast.ClassDef*](table_pool, 8) self.GenericClassSha1s = list[str](table_pool, 8) self.GenericClassFiles = list[str](table_pool, 8) if self.GenericClassTpParams is None: if perm is None: return self.GenericClassTpParams = perm.alloc(GENERIC_TEMPLATE_MAX * 8) if self.GenericClassTpParams is None: return string.memset(self.GenericClassTpParams, 0, GENERIC_TEMPLATE_MAX * 8) # Assign the list to a local to trigger type inference (avoids field __len__ failure) names_local: list[str]* = self.GenericClassNames cn: sizet = len(names_local) for i in range(cn): nm: str = names_local.get(i) if nm is not None and string.strcmp(nm, cd.name) == 0: # A duplicate name must not simply return: the first-registered node may lack # type_params, so backfill the empty tp slot and replace the node with the richer one. if self.GenericClassTpParams is not None and i < GENERIC_TEMPLATE_MAX and self.GenericClassTpParams[i] == 0: back_buf: str = self._copy_type_params_stdlib(cd.type_params) if back_buf is not None: self.GenericClassTpParams[i] = sizet(back_buf) if self.GenericClassNodes is not None: self.GenericClassNodes.set(i, cd) return # Deep-copy type_params to stdlib (the template node's list is pool-allocated and may be recycled) tp_names_buf: str = self._copy_type_params_stdlib(cd.type_params) self.GenericClassNames.append(self._dup_perm_str(cd.name)) self.GenericClassNodes.append(cd) self.GenericClassSha1s.append(self._dup_perm_str(module_sha1)) # Also store the template source file path (for error reporting; see the field comment) self.GenericClassFiles.append(self._dup_perm_str(self.Gen.TypeGenResolve.get_current_file())) if tp_names_buf is not None and cn < GENERIC_TEMPLATE_MAX: self.GenericClassTpParams[cn] = sizet(tp_names_buf) def _copy_type_params_stdlib(self, tp: list[str]*) -> str: """Deep-copy template type_params into a space-separated stdlib buffer; None on failure.""" if tp is None: return None tp_n: sizet = len(tp) if tp_n == 0: return None # Compute total length: each name length + (n-1) spaces + NUL total: sizet = 1 ti: sizet for ti in range(tp_n): tp_name: str = tp.get(ti) if tp_name is not None: total += string.strlen(tp_name) + 1 buf: str = CompilerFactory.factory.PermPool.alloc(total) if buf is None: return None buf[0] = '\0' for ti in range(tp_n): tp_name = tp.get(ti) if tp_name is None: continue if ti > 0 and buf[0] != '\0': string.strcat(buf, " ") string.strcat(buf, tp_name) return buf # Generic function templates (PEP 695 def f[T](...)): mirrors the generic class path; keys include # the defining module SHA1, call sites infer args, and specialization emits a define (or a declare). def _is_generic_function(self, tp: list[str]*) -> int: """Return 1 when the function type_params mark a generic function; 0 otherwise.""" if tp is None: return 0 return 1 def _register_generic_func_template(self, pool: memhub.MemBuddy*, fd: ast.FunctionDef*, module_sha1: str) -> None: """Register a generic function template keyed by "{module_sha1}.{name}".""" if fd is None or fd.name is None or module_sha1 is None: return perm: memhub.MemBuddy* = CompilerFactory.factory.PermPool table_pool: memhub.MemBuddy* = perm if table_pool is None: table_pool = pool if self.GenericFuncKeys is None: self.GenericFuncKeys = list[str](table_pool, 8) self.GenericFuncNodes = list[ast.FunctionDef*](table_pool, 8) self.GenericFuncSha1s = list[str](table_pool, 8) self.GenericFuncFiles = list[str](table_pool, 8) if self.GenericFuncTpParams is None: if perm is None: return self.GenericFuncTpParams = perm.alloc(GENERIC_TEMPLATE_MAX * 8) if self.GenericFuncTpParams is None: return string.memset(self.GenericFuncTpParams, 0, GENERIC_TEMPLATE_MAX * 8) # Key = "{sha1}.{name}" (built in a new buffer to avoid in-place strcat overflow) key_len: sizet = string.strlen(module_sha1) + string.strlen(fd.name) + 2 key_buf: str = table_pool.alloc(key_len) if key_buf is None: return viperlib.snprintf(key_buf, key_len, "%s.%s", module_sha1, fd.name) keys_local: list[str]* = self.GenericFuncKeys kn: sizet = len(keys_local) ki: sizet for ki in range(kn): knm: str = keys_local.get(ki) if knm is not None and string.strcmp(knm, key_buf) == 0: return tp_names_buf: str = self._copy_type_params_stdlib(fd.type_params) self.GenericFuncKeys.append(self._dup_perm_str(key_buf)) self.GenericFuncNodes.append(fd) self.GenericFuncSha1s.append(self._dup_perm_str(module_sha1)) self.GenericFuncFiles.append(self._dup_perm_str(self.Gen.TypeGenResolve.get_current_file())) if tp_names_buf is not None and kn < GENERIC_TEMPLATE_MAX: self.GenericFuncTpParams[kn] = sizet(tp_names_buf) def _find_generic_func_template(self, key: str) -> ast.FunctionDef*: """Find a generic function template node by "{sha1}.{fname}" key; None when absent.""" if self.GenericFuncKeys is None or key is None: return None keys_local: list[str]* = self.GenericFuncKeys nodes_local: list[ast.FunctionDef*]* = self.GenericFuncNodes kn: sizet = len(keys_local) i: sizet for i in range(kn): knm: str = keys_local.get(i) if knm is not None and string.strcmp(knm, key) == 0: return nodes_local.get(i) return None def _find_generic_func_template_sha1(self, key: str) -> str: """Find a generic function template's source module SHA1 by key; None when absent.""" if self.GenericFuncKeys is None or key is None: return None keys_local: list[str]* = self.GenericFuncKeys sha1s_local: list[str]* = self.GenericFuncSha1s kn: sizet = len(keys_local) i: sizet for i in range(kn): knm: str = keys_local.get(i) if knm is not None and string.strcmp(knm, key) == 0: return sha1s_local.get(i) return None def _find_generic_func_template_file(self, key: str) -> str: """Find a generic function template's source file path by key; None when absent.""" if self.GenericFuncKeys is None or key is None: return None keys_local: list[str]* = self.GenericFuncKeys files_local: list[str]* = self.GenericFuncFiles kn: sizet = len(keys_local) i: sizet for i in range(kn): knm: str = keys_local.get(i) if knm is not None and string.strcmp(knm, key) == 0: return files_local.get(i) return None def _find_generic_func_tp_params(self, pool: memhub.MemBuddy*, key: str) -> list[str]*: """Rebuild a generic function template's type-param name list; None when absent.""" if self.GenericFuncKeys is None or key is None or self.GenericFuncTpParams is None: return None keys_local: list[str]* = self.GenericFuncKeys kn: sizet = len(keys_local) i: sizet for i in range(kn): knm: str = keys_local.get(i) if knm is not None and string.strcmp(knm, key) == 0: if i >= GENERIC_TEMPLATE_MAX: return None buf_addr: sizet = self.GenericFuncTpParams[i] if buf_addr == 0: return None buf_tp: str = rawptr(buf_addr) tp_list: list[str]* = list[str](pool, 8) if tp_list is None: return None seg_start: sizet = 0 blen: sizet = string.strlen(buf_tp) for pos in range(blen + 1): if buf_tp[pos] == ' ' or buf_tp[pos] == '\0': seg_len: sizet = pos - seg_start if seg_len > 0: seg: str = pool.alloc(seg_len + 1) if seg is not None: string.strncpy(seg, buf_tp + seg_start, seg_len) seg[seg_len] = '\0' tp_list.append(seg) seg_start = pos + 1 return tp_list return None def _find_generic_func_param_slot(self, fd: ast.FunctionDef*, tp_name: str) -> int: """Return the 0-based slot of the parameter annotated exactly as tp_name; -1 when absent.""" if fd is None or tp_name is None or fd.args is None: return -1 if fd.args.kind() != ast.ASTKind.Arguments: return -1 ags: ast.Arguments* = ast.Arguments*(fd.args) if ags.args is None: return -1 alist: list[ast.AST*]* = ags.args an: sizet = len(alist) ai: sizet for ai in range(an): pa: ast.AST* = alist.get(ai) if pa is None or pa.kind() != ast.ASTKind.Arg: continue ag: ast.Arg* = ast.Arg*(pa) if self.Gen.CallGenCtor._annotation_refers_type_param(ag.annotation, tp_name) != 0: return int(ai) return -1 def _infer_generic_func_type_arg(self, pool: memhub.MemBuddy*, arg_node: ast.AST*, func_name: str, trans: HT.Translator*) -> str: """Infer a generic function's type argument name from a call argument AST; None when unknown.""" if arg_node is None: return None k: int = arg_node.kind() if k == ast.ASTKind.Constant: cn: ast.Constant* = ast.Constant*(arg_node) if cn.const_kind == ast.CONST_FLOAT: return "double" if cn.const_kind == ast.CONST_CHAR: return "char" if cn.const_kind == ast.CONST_STR: return "str" return "int" # Unary +/- constant (clamp(-3, ...)): the type is determined by the operand if k == ast.ASTKind.UnaryOp: uo: ast.UnaryOp* = ast.UnaryOp*(arg_node) return self._infer_generic_func_type_arg(pool, uo.operand, func_name, trans) if k == ast.ASTKind.Name: nm: ast.Name* = ast.Name*(arg_node) if nm.id is not None and trans is not None: ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id) if ve is not None: cn_nm: str = self.Gen.VarGen.var_annot_class_name(ve) pl: int = 0 if ve.Spec is not None: pl = ve.Spec.PtrLevel if cn_nm is not None: return self.Gen.CallGenCtor._ptr_layer_type_arg(pool, cn_nm, pl, trans._from_imports) # Builtin scalars (skipped by extract_class_name_from_annotation, SourceName None): # reverse-map the Spec's LLVM form to a parseable type name. return self._infer_scalar_type_name_from_spec(pool, ve.Spec) return None if k == ast.ASTKind.Attribute: cn_at: str = self.Gen.ExprGenAttr._resolve_attr_field_class(pool, trans, arg_node) pl_at: int = 0 fe: GenTypes.FieldEntry* = self.Gen.ExprGenAttr._resolve_attr_field_entry(pool, trans, arg_node) if fe is not None and fe.Spec is not None: pl_at = fe.Spec.PtrLevel fi: str = None if trans is not None: fi = trans._from_imports if cn_at is not None: return self.Gen.CallGenCtor._ptr_layer_type_arg(pool, cn_at, pl_at, fi) # Builtin scalar field: reverse-map from Spec, same as the Name branch if fe is not None: return self._infer_scalar_type_name_from_spec(pool, fe.Spec) return None if k == ast.ASTKind.Call: cl_f: ast.Call* = ast.Call*(arg_node) if cl_f.func is not None: fk_f: int = cl_f.func.kind() # Only accept conversion calls whose callee resolves to a scalar type, mapping by exact # bit width; non-scalar/unresolvable yields None. No name-substring guessing (never i64->i32). if (fk_f == ast.ASTKind.Name or fk_f == ast.ASTKind.Attribute) and trans is not None: cty_f: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, cl_f.func, trans._imported_modules, trans._from_imports, trans, 0) if cty_f is not None: # Signedness can only come from the callee's source text (LLVM iN/uN look alike, # SpecFromLLVMType always says SIGNED); reuse spec_sign_of_annot to pick the exact type. callee_txt: str = None if fk_f == ast.ASTKind.Name: callee_txt = ast.Name*(cl_f.func).id else: callee_txt = ast.Attribute*(cl_f.func).attr sign_f: int = TSpec.TS_SIGN_UNKNOWN if callee_txt is not None: sign_f = self.Gen.TypeGenCType.spec_sign_of_annot(callee_txt, 0) return self.infer_scalar_type_arg_from_llvm_ty(pool, cty_f, sign_f) return None return None def _infer_scalar_type_name_from_spec(self, pool: memhub.MemBuddy*, sp: TSpec.TypeSpec*, sign_override: int = SIGN_FROM_SPEC) -> str: """Reverse-map a variable's TypeSpec to a resolvable builtin scalar type name; None otherwise.""" if pool is None or sp is None: return None buf: str = pool.alloc(32) if buf is None: return None buf[0] = '\0' # SpecToLLVMText returns 0=success / 1=failure; treating 0 as failure made this always return # None for builtin scalars, so scalar-typed params could never be inferred. if TSpec.SpecToLLVMText(sp, buf, 32) != 0: return None sign: int = sign_override if sign == SIGN_FROM_SPEC: # SpecFromLLVMType always reports TS_SIGN_SIGNED (iN/uN are indistinguishable), so only an # annotation-side Spec (via SpecWithSign) makes this field trustworthy. sign = sp.IsSigned # ---- Integers: exact bit width + consistent signedness (table in docstring) ---- if string.strcmp(buf, "i1") == 0: return "bool" if string.strcmp(buf, "i8") == 0: # Unsigned i8 (CUInt8T/CUnsignedChar) via the bare name char would silently change sign if sign == TSpec.TS_SIGN_UNSIGNED: return "uint8" return "char" if string.strcmp(buf, "i16") == 0: if sign == TSpec.TS_SIGN_UNSIGNED: return "uint16" if sign == TSpec.TS_SIGN_SIGNED: return "int16" return None if string.strcmp(buf, "i32") == 0: # Unsigned i32 (CUInt32T/CSizeT/CUIntPtrT) via the bare name int would silently change sign if sign == TSpec.TS_SIGN_UNSIGNED: return "uint32" return "int" if string.strcmp(buf, "i64") == 0: if sign == TSpec.TS_SIGN_UNSIGNED: return "uint64" if sign == TSpec.TS_SIGN_SIGNED: return "int64" return None # ---- Floats: exact bit width (no signedness concept, unambiguous) ---- if string.strcmp(buf, "half") == 0: return "t.CFloat16T" if string.strcmp(buf, "float") == 0: return "float" if string.strcmp(buf, "double") == 0: return "double" if string.strcmp(buf, "fp128") == 0: return "t.CFloat128T" return None def infer_scalar_type_arg_from_llvm_ty(self, pool: memhub.MemBuddy*, ty: mlirlite.LLVMType*, sign: int) -> str: """Map an LLVM scalar type to a generic type-argument name by exact bit width; None for non-scalars.""" if pool is None or ty is None: return None arena: TSpec.TypeSpecArena* = self.Gen.TypeGenBase.ensure_spec_arena() if arena is None: return None return self._infer_scalar_type_name_from_spec(pool, TSpec.SpecFromLLVMType(arena, ty), sign) def _clone_funcdef_for_spec(self, pool: memhub.MemBuddy*, fd: ast.FunctionDef*, spec_base: str, tp_names: list[str]*, type_args: list[str]*) -> ast.FunctionDef*: """Clone a FunctionDef for generic function specialization, substituting concrete types.""" if fd is None: return None new_args: ast.AST* = self._clone_arguments(pool, fd.args, tp_names, type_args) new_returns: ast.AST* = self._clone_annotation(pool, fd.returns, tp_names, type_args) new_fd: ast.FunctionDef* = ast.FunctionDef(pool, spec_base, new_args, fd.decorator_list, new_returns, 0) if fd.children is not None: body: list[ast.AST*]* = fd.children bn: sizet = len(body) bi: sizet for bi in range(bn): stmt: ast.AST* = body.get(bi) if stmt is not None: new_fd.append(stmt) return new_fd def _ensure_generic_func_declare(self, trans: HT.Translator*, pool: memhub.MemBuddy*, spec_name: str, template_key: str, tp_names: list[str]*, type_args: list[str]*) -> mlirlite.Function*: """Ensure the current module declares a specialized function; None on failure.""" if trans is None or pool is None or spec_name is None: return None mod: mlirlite.LLVMModule* = trans.Module if mod is None: return None existing: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, spec_name) if existing is not None: return existing template_fd: ast.FunctionDef* = self._find_generic_func_template(template_key) if template_fd is None: return None template_sha1: str = self._find_generic_func_template_sha1(template_key) if template_sha1 is None: return None spec_base: str = self._mangle_generic_class_name(pool, template_fd.name, type_args, trans._from_imports) if spec_base is None: return None spec_fd: ast.FunctionDef* = self._clone_funcdef_for_spec(pool, template_fd, spec_base, tp_names, type_args) if spec_fd is None: return None i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool) saved_ms: str = trans.ModuleSha1 saved_imp: str = trans._imported_modules saved_gtpn: list[str]* = trans.GenericTypeParamNames saved_gta: list[str]* = trans.GenericTypeArgs trans.ModuleSha1 = template_sha1 self._merge_template_imports(trans, template_sha1) trans.GenericTypeParamNames = tp_names trans.GenericTypeArgs = type_args self.Gen.TypeGenBase.set_generic_context(tp_names, type_args) saved_cur: str = self.Gen.TypeGenResolve.get_current_file() tpl_file: str = self._find_generic_func_template_file(template_key) if tpl_file is not None: self.Gen.TypeGenResolve.set_current_file(tpl_file) ret_ty: mlirlite.LLVMType* = None if spec_fd.returns is not None: # Value-reference markers are parameter-only: `-> &T` fails fast on a generic too. self.Gen.TypeGenResolve.reject_ref_marker_position(spec_fd.returns, trans._imported_modules, "a function return type") ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, spec_fd.returns, trans._imported_modules, trans._from_imports, trans, 0) if ret_ty is None: ret_ty = i32_ty func: mlirlite.Function* = mlirlite.create_declare(pool, mod, spec_name, ret_ty) if func is not None and spec_fd.args is not None and spec_fd.args.kind() == ast.ASTKind.Arguments: ags: ast.Arguments* = ast.Arguments*(spec_fd.args) if ags.args is not None: alist: list[ast.AST*]* = ags.args an: sizet = len(alist) ai: sizet for ai in range(an): pa: ast.AST* = alist.get(ai) if pa is None or pa.kind() != ast.ASTKind.Arg: continue arg: ast.Arg* = ast.Arg*(pa) if arg.arg is None: continue param_ty: mlirlite.LLVMType* = i32_ty if arg.annotation is not None: resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, trans._imported_modules, trans._from_imports, trans, 0) if resolved is not None: match resolved: case mlirlite.LLVMType.Struct(_, _, _): param_ty = mlirlite.Ptr(pool, resolved) case mlirlite.LLVMType.Array(aelem_ty, _): param_ty = mlirlite.Ptr(pool, aelem_ty) case _: param_ty = resolved pname: str = pool.alloc(32) if pname is not None: viperlib.snprintf(pname, 32, "%%%s", arg.arg) mlirlite.add_param(pool, func, param_ty, pname) # Register value-reference (&T / &temp T) parameter modes for the specialization. mode_buf_g: str = pool.alloc(an + 1) if mode_buf_g is not None: mode_cnt_g: int = 0 has_ref_g: int = 0 for ai_g in range(an): pa_g: ast.AST* = alist.get(ai_g) if pa_g is None or pa_g.kind() != ast.ASTKind.Arg: continue arg_g: ast.Arg* = ast.Arg*(pa_g) if arg_g.arg is None: continue mm_g: int = 0 if arg_g.annotation is not None: mm_g = self.Gen.TypeGenResolve.ref_param_mode(arg_g.annotation, trans._imported_modules) if mm_g == 1: mode_buf_g[mode_cnt_g] = char(49) elif mm_g == 2: mode_buf_g[mode_cnt_g] = char(50) else: mode_buf_g[mode_cnt_g] = char(48) if mm_g != 0: has_ref_g = 1 mode_cnt_g += 1 mode_buf_g[mode_cnt_g] = char(0) if has_ref_g != 0: self.Gen.CallGenBase._register_auto_addr_modes(spec_name, mode_buf_g, mode_cnt_g) if tpl_file is not None: self.Gen.TypeGenResolve.set_current_file(saved_cur) trans.ModuleSha1 = saved_ms trans._imported_modules = saved_imp trans.GenericTypeParamNames = saved_gtpn trans.GenericTypeArgs = saved_gta self.Gen.TypeGenBase.clear_generic_context() return func def _specialize_generic_function(self, trans: HT.Translator*, template_key: str, type_args: list[str]*) -> str: """Specialize a generic function and return its mangled name.""" if trans is None or template_key is None or type_args is None: return None self.GenericSpecDepth += 1 if self.GenericSpecDepth > GENERIC_SPEC_MAX_DEPTH: self.Gen.TypeGenErr.fatal_error(None, "generic function specialization recursion too deep (suspected generic loop)", "InternalCompilerError") pool: memhub.MemBuddy* = trans.Pool template_fd: ast.FunctionDef* = self._find_generic_func_template(template_key) if template_fd is None: self.GenericSpecDepth -= 1 return None template_sha1: str = self._find_generic_func_template_sha1(template_key) if template_sha1 is None: self.GenericSpecDepth -= 1 return None # Defensive: any unresolved type-parameter name (e.g. "T") left in the collected type_args # is replaced with the concrete type name (nested specialization: generic fn calling generic fn). ta_cnt_g: sizet = len(type_args) ta_i_g: sizet for ta_i_g in range(ta_cnt_g): ta_raw_g: str = type_args.get(ta_i_g) if ta_raw_g is not None: ta_rsv_g: str = self.Gen.TypeGenBase.resolve_generic_type_name(ta_raw_g) if ta_rsv_g is not None and string.strcmp(ta_raw_g, ta_rsv_g) != 0: type_args.set(ta_i_g, ta_rsv_g) tp_names: list[str]* = self._find_generic_func_tp_params(pool, template_key) if tp_names is None: tp_names = template_fd.type_params if tp_names is None: self.GenericSpecDepth -= 1 return None if len(tp_names) != len(type_args): err_tp: str = pool.alloc(256) if err_tp is not None: viperlib.snprintf(err_tp, 256, "generic function '%s' type argument count mismatch (expected %d, given %d)", template_fd.name, len(tp_names), len(type_args)) self.Gen.TypeGenErr.fatal_error(None, err_tp, "TypeError") self.GenericSpecDepth -= 1 return None # Generic functions with CExport/CExtern/State markers are unsupported (specialized names bypass bare-name export) if template_fd.returns is not None: if self.Gen.TypeGenErr.has_decorator_marker(template_fd.returns, "CExport", 0) != 0 or self.Gen.TypeGenErr.has_decorator_marker(template_fd.returns, "CExtern", 0) != 0 or self.Gen.TypeGenErr.has_decorator_marker(template_fd.returns, "State", 0) != 0: self.Gen.TypeGenErr.fatal_error(template_fd, "generic functions do not yet support t.CExport/t.CExtern/t.State storage markers", "TypeError") # Generate the specialized name (the single generator normalizes each arg so bare/prefixed forms converge) spec_base: str = self._mangle_generic_class_name(pool, template_fd.name, type_args, trans._from_imports) if spec_base is None: self.GenericSpecDepth -= 1 return None spec_name: str = self.Gen.FuncGenBase._mangle_name_with_sha1(template_sha1, spec_base) # spec_key = template key + normalized spec base, matching spec_name one-to-one so bare/prefixed # spellings cannot yield two cache keys; the template key (SHA1) keeps it distinct from class keys. spec_key: str = pool.alloc(string.strlen(template_key) + string.strlen(spec_base) + 2) if spec_key is None: self.GenericSpecDepth -= 1 return None spec_key[0] = '\0' string.strcat(spec_key, template_key) string.strcat(spec_key, "#") string.strcat(spec_key, spec_base) cached: str = self._find_cached_spec(spec_key) if cached is not None: self._ensure_generic_func_declare(trans, pool, spec_name, template_key, tp_names, type_args) self.GenericSpecDepth -= 1 return cached # An existing same-name define in this module (multi-pass/repeat) is reused, not re-cloned; # a declare only continues to emit (translate_function_def reuses it and fills the body). existing_fn: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(trans.Module, spec_name) if existing_fn is not None and existing_fn.IsDeclared == 0: self._cache_spec(pool, spec_key, spec_name) self.GenericSpecDepth -= 1 return spec_name # Clone + translate with context to emit the define spec_fd: ast.FunctionDef* = self._clone_funcdef_for_spec(pool, template_fd, spec_base, tp_names, type_args) if spec_fd is None: self.GenericSpecDepth -= 1 return None # Pre-register in the cache to prevent recursive specialization self._cache_spec(pool, spec_key, spec_name) saved_ms: str = trans.ModuleSha1 saved_imp: str = trans._imported_modules saved_gtpn: list[str]* = trans.GenericTypeParamNames saved_gta: list[str]* = trans.GenericTypeArgs # The class-definition window must be saved/restored together with ModuleSha1 (specialization # translation refreshes it per template module sha1 + spec name; see _specialize_generic_class). saved_def_mod: str = self.Gen.StructGen.CurDefModuleSha1 saved_def_cls: str = self.Gen.StructGen.CurDefClassName # Switch ModuleSha1 to the template-defining module: specialized symbol names are {templateSHA1}.f[args], # consistent cross-module; also merge the template module's imports so its cross-module calls resolve. trans.ModuleSha1 = template_sha1 self._merge_template_imports(trans, template_sha1) trans.GenericTypeParamNames = tp_names trans.GenericTypeArgs = type_args self.Gen.TypeGenBase.set_generic_context(tp_names, type_args) # Error reporting: the specialized body's node.lineno is a template-file line, so temporarily # switch CurrentFile to the template file and push a trace frame (restored after translation). saved_cur: str = self.Gen.TypeGenResolve.get_current_file() tpl_file: str = self._find_generic_func_template_file(template_key) if tpl_file is not None: self.Gen.TypeGenResolve.set_current_file(tpl_file) self.Gen.TypeGenBase.push_trace(tpl_file, template_fd.lineno) self.Gen.FuncGenDef.translate_function_def(trans, spec_fd) if tpl_file is not None: self.Gen.TypeGenBase.pop_trace() self.Gen.TypeGenResolve.set_current_file(saved_cur) trans.ModuleSha1 = saved_ms trans._imported_modules = saved_imp self.Gen.StructGen.CurDefModuleSha1 = saved_def_mod self.Gen.StructGen.CurDefClassName = saved_def_cls trans.GenericTypeParamNames = saved_gtpn trans.GenericTypeArgs = saved_gta self.Gen.TypeGenBase.clear_generic_context() self.GenericSpecDepth -= 1 return spec_name def _find_generic_template_tp_params(self, pool: memhub.MemBuddy*, class_name: str) -> list[str]*: """Rebuild a generic class template's type-param name list; None when absent.""" if self.GenericClassNames is None or class_name is None or self.GenericClassTpParams is None: return None names_local: list[str]* = self.GenericClassNames cn: sizet = len(names_local) i: sizet for i in range(cn): nm: str = names_local.get(i) if nm is not None and string.strcmp(nm, class_name) == 0: if i >= GENERIC_TEMPLATE_MAX: return None buf_addr: sizet = self.GenericClassTpParams[i] if buf_addr == 0: return None buf_tp: str = rawptr(buf_addr) # Split by spaces into list[str]. list[str](pool, 8)'s second arg is elem_size (pointer # 8 bytes), not capacity; passing 4 corrupts get(i) offsets and append overwrites. tp_list: list[str]* = list[str](pool, 8) if tp_list is None: return None seg_start: sizet = 0 blen: sizet = string.strlen(buf_tp) for pos in range(blen + 1): if buf_tp[pos] == ' ' or buf_tp[pos] == '\0': seg_len: sizet = pos - seg_start if seg_len > 0: seg: str = pool.alloc(seg_len + 1) if seg is not None: string.strncpy(seg, buf_tp + seg_start, seg_len) seg[seg_len] = '\0' tp_list.append(seg) seg_start = pos + 1 return tp_list return None def _find_generic_template(self, class_name: str) -> ast.ClassDef*: """Find a generic class template; return the ClassDef node or None.""" if self.GenericClassNames is None or class_name is None: return None # Assign the list to a local to trigger type inference (avoids field __len__ failure) names_local: list[str]* = self.GenericClassNames nodes_local: list[ast.ClassDef*] | t.CPtr = self.GenericClassNodes cn: sizet = len(names_local) i: sizet for i in range(cn): nm: str = names_local.get(i) if nm is not None and string.strcmp(nm, class_name) == 0: node: ast.ClassDef* = nodes_local.get(i) return node return None def _find_generic_template_sha1(self, class_name: str) -> str: """Find a generic class template's source module SHA1; None when absent.""" if self.GenericClassNames is None or class_name is None: return None # Assign the list to a local to trigger type inference names_local: list[str]* = self.GenericClassNames sha1s_local: list[str]* = self.GenericClassSha1s cn: sizet = len(names_local) i: sizet for i in range(cn): nm: str = names_local.get(i) if nm is not None and string.strcmp(nm, class_name) == 0: sha1_val: str = sha1s_local.get(i) return sha1_val return None def _find_generic_template_file(self, class_name: str) -> str: """Find a generic class template's source file path; None when absent.""" if self.GenericClassNames is None or class_name is None: return None names_local: list[str]* = self.GenericClassNames files_local: list[str]* = self.GenericClassFiles cn: sizet = len(names_local) i: sizet for i in range(cn): nm: str = names_local.get(i) if nm is not None and string.strcmp(nm, class_name) == 0: f_val: str = files_local.get(i) return f_val return None def _mangle_generic_class_name(self, pool: memhub.MemBuddy*, class_name: str, type_args: list[str]*, from_imports: str = None) -> str: """Build the specialized class name (e.g. list + [int] -> list[int]) in a new buffer.""" if class_name is None or type_args is None or pool is None: return class_name n: sizet = len(type_args) if n == 0: return class_name # Normalize each arg (idempotent): normalize into a new list first, then use it for length and concatenation norm_args: list[str]* = list[str](pool, 8) if norm_args is None: return class_name i: sizet for i in range(n): ta_raw: str = type_args.get(i) if ta_raw is None: continue ta_norm: str = self.Gen.TypeGenResolve._norm_type_arg_name(pool, ta_raw, from_imports) if ta_norm is None: ta_norm = ta_raw norm_args.append(ta_norm) n = len(norm_args) if n == 0: return class_name # Compute total length: class_name + "[" + arg0 + "," + arg1 + ... + "]" + NUL total_len: sizet = string.strlen(class_name) + 2 # "[" and "]" for i in range(n): if i > 0: total_len += 1 # "," ta: str = norm_args.get(i) if ta is not None: total_len += string.strlen(ta) total_len += 1 # NUL # Allocate a new buffer mangled: str = pool.alloc(total_len) if mangled is None: return class_name mangled[0] = '\0' string.strcat(mangled, class_name) string.strcat(mangled, "[") for i in range(n): if i > 0: string.strcat(mangled, ",") ta: str = norm_args.get(i) if ta is not None: string.strcat(mangled, ta) string.strcat(mangled, "]") return mangled def _clone_annotation(self, pool: memhub.MemBuddy*, node: ast.AST*, tp_names: list[str]*, type_args: list[str]*) -> ast.AST*: """Clone a type annotation expression, replacing type parameter names with concrete types.""" if node is None: return None k: int = node.kind() # Name: may be the type parameter T if k == ast.ASTKind.Name: nm: ast.Name* = ast.Name*(node) if nm.id is not None: n: sizet = len(tp_names) if tp_names is not None else 0 i: sizet for i in range(n): tpn: str = tp_names.get(i) if tpn is not None and string.strcmp(nm.id, tpn) == 0: ta: str = type_args.get(i) # A type arg may be a composite type (e.g. "AST*"); parse it with _parse_type_string parsed_node: ast.AST* = self.Gen.TypeGenResolve._parse_type_string(pool, ta, nm.ctx) return parsed_node return ast.Name(pool, nm.id, nm.ctx) return None # Attribute: t.CPtr etc. if k == ast.ASTKind.Attribute: at: ast.Attribute* = ast.Attribute*(node) new_value: ast.AST* = self._clone_annotation(pool, at.value, tp_names, type_args) return ast.Attribute(pool, new_value, at.attr, at.ctx) # BinOp: T* etc. if k == ast.ASTKind.BinOp: bop: ast.BinOp* = ast.BinOp*(node) new_left: ast.AST* = self._clone_annotation(pool, bop.left, tp_names, type_args) new_right: ast.AST* = self._clone_annotation(pool, bop.right, tp_names, type_args) return ast.BinOp(pool, new_left, bop.op, new_right) # Subscript: list[int] etc. if k == ast.ASTKind.Subscript: sub: ast.Subscript* = ast.Subscript*(node) new_value2: ast.AST* = self._clone_annotation(pool, sub.value, tp_names, type_args) new_slice: ast.AST* = self._clone_annotation(pool, sub.slice, tp_names, type_args) return ast.Subscript(pool, new_value2, new_slice, sub.ctx) # Tuple: (T, int) etc. if k == ast.ASTKind.Tuple: tup: ast.Tuple* = ast.Tuple*(node) new_elts: list[ast.AST*]* = list[ast.AST*](pool, 8) if tup.elts is not None: tup_elts: list[ast.AST*]* = tup.elts en: sizet = len(tup_elts) ei: sizet for ei in range(en): el: ast.AST* = tup_elts.get(ei) new_el: ast.AST* = self._clone_annotation(pool, el, tp_names, type_args) if new_el is not None: new_elts.append(new_el) return ast.Tuple(pool, new_elts, tup.ctx) # Constant: default values etc. if k == ast.ASTKind.Constant: cnst: ast.Constant* = ast.Constant*(node) return ast.Constant(pool, cnst.const_kind, cnst.int_val, cnst.float_val, cnst.str_val, cnst.lineno, cnst.col_offset) # Other: reference directly (no copy) return node def _clone_arguments(self, pool: memhub.MemBuddy*, args: ast.AST*, tp_names: list[str]*, type_args: list[str]*) -> ast.AST*: """Clone an Arguments node, replacing type parameters inside arg annotations.""" if args is None: return None if args.kind() != ast.ASTKind.Arguments: return args old_args: ast.Arguments* = ast.Arguments*(args) # Copy the args list and replace each Arg's annotation new_arg_list: list[ast.AST*]* = list[ast.AST*](pool, 8) if old_args.args is not None: old_arg_list: list[ast.AST*]* = old_args.args an: sizet = len(old_arg_list) ai: sizet for ai in range(an): arg_node: ast.AST* = old_arg_list.get(ai) if arg_node is None: continue if arg_node.kind() == ast.ASTKind.Arg: ag: ast.Arg* = ast.Arg*(arg_node) new_anno: ast.AST* = self._clone_annotation(pool, ag.annotation, tp_names, type_args) new_arg: ast.Arg* = ast.Arg(pool, ag.arg, new_anno) new_arg_list.append(new_arg) else: new_arg_list.append(arg_node) return ast.Arguments(pool, new_arg_list, old_args.vararg, old_args.kwarg, old_args.defaults, old_args.kw_defaults) def _clone_classdef_for_spec(self, pool: memhub.MemBuddy*, cd: ast.ClassDef*, spec_name: str, tp_names: list[str]*, type_args: list[str]*) -> ast.ClassDef*: """Clone a ClassDef node for generic specialization, substituting concrete types.""" # Create the new ClassDef (type_params=None means non-generic) new_cd: ast.ClassDef* = ast.ClassDef(pool, spec_name, cd.bases, cd.keywords, cd.decorator_list, None) # Copy children if cd.children is not None: cd_children: list[ast.AST*]* = cd.children cn: sizet = len(cd_children) ci: sizet for ci in range(cn): child: ast.AST* = cd_children.get(ci) if child is None: continue ck: int = child.kind() if ck == ast.ASTKind.AnnAssign: aa: ast.AnnAssign* = ast.AnnAssign*(child) new_target: ast.AST* = aa.target new_anno2: ast.AST* = self._clone_annotation(pool, aa.annotation, tp_names, type_args) new_value3: ast.AST* = aa.value new_aa: ast.AnnAssign* = ast.AnnAssign(pool, new_target, new_anno2, new_value3, aa.simple) new_cd.append(new_aa) elif ck == ast.ASTKind.FunctionDef: fd: ast.FunctionDef* = ast.FunctionDef*(child) new_args2: ast.AST* = self._clone_arguments(pool, fd.args, tp_names, type_args) new_returns2: ast.AST* = self._clone_annotation(pool, fd.returns, tp_names, type_args) new_fd: ast.FunctionDef* = ast.FunctionDef(pool, fd.name, new_args2, fd.decorator_list, new_returns2, 0) # The method body directly references the original nodes if fd.children is not None: fd_children: list[ast.AST*]* = fd.children bcn: sizet = len(fd_children) bci: sizet for bci in range(bcn): body_stmt: ast.AST* = fd_children.get(bci) if body_stmt is not None: new_fd.append(body_stmt) new_cd.append(new_fd) else: new_cd.append(child) return new_cd def _find_cached_spec(self, spec_key: str) -> str: """Find a cached specialization name; None when absent.""" if self.SpecKeys is None or spec_key is None: return None i: int for i in range(self.SpecCount): key_addr: sizet = self.SpecKeys[i] if key_addr != 0: k: str = rawptr(key_addr) if k is not None and string.strcmp(k, spec_key) == 0: name_addr: sizet = self.SpecNames[i] if name_addr != 0: return rawptr(name_addr) return None def _cache_spec(self, pool: memhub.MemBuddy*, spec_key: str, spec_name: str): """Cache a specialization result as a cross-file-safe stdlib deep copy.""" if spec_key is None or spec_name is None: return if self.SpecKeys is None: self.SpecKeys = CompilerFactory.factory.PermPool.alloc(SPEC_CACHE_MAX * 8) self.SpecNames = CompilerFactory.factory.PermPool.alloc(SPEC_CACHE_MAX * 8) if self.SpecKeys is None or self.SpecNames is None: return string.memset(self.SpecKeys, 0, SPEC_CACHE_MAX * 8) string.memset(self.SpecNames, 0, SPEC_CACHE_MAX * 8) self.SpecCount = 0 if self.SpecCount >= SPEC_CACHE_MAX: return # Deep-copy strings into the stdlib region key_len: sizet = string.strlen(spec_key) name_len: sizet = string.strlen(spec_name) key_copy: str = CompilerFactory.factory.PermPool.alloc(key_len + 1) name_copy: str = CompilerFactory.factory.PermPool.alloc(name_len + 1) if key_copy is None or name_copy is None: return string.strcpy(key_copy, spec_key) string.strcpy(name_copy, spec_name) self.SpecKeys[self.SpecCount] = sizet(key_copy) self.SpecNames[self.SpecCount] = sizet(name_copy) self.SpecCount += 1 def _merge_template_imports(self, trans: HT.Translator*, template_sha1: str) -> None: """Merge the template module's imports into trans._imported_modules.""" if trans is None or template_sha1 is None: return tdeps: str = StubMergerStore.LookupIncludesDeps(template_sha1) if tdeps is None or tdeps[0] == '\0': return cur: str = trans._imported_modules if cur is None or cur[0] == '\0': trans._imported_modules = tdeps return cur_len: sizet = string.strlen(cur) td_len: sizet = string.strlen(tdeps) merged: str = trans.Pool.alloc(cur_len + td_len + 2) if merged is None: return string.strcpy(merged, cur) merged[cur_len] = ' ' string.strcpy(merged + cur_len + 1, tdeps) trans._imported_modules = merged def _record_spec_trigger(self, pool: memhub.MemBuddy*, spec_name: str, trigger_sha1: str) -> int: """Record the triggering file SHA1 of a specialized generic class for later reset.""" if spec_name is None or trigger_sha1 is None: return 0 entry_rt: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(spec_name) if entry_rt is None: return 0 if string.strcmp(entry_rt.Name, spec_name) != 0: return 0 ts_len_r: sizet = string.strlen(trigger_sha1) ts_buf_r: str = CompilerFactory.factory.PermPool.alloc(ts_len_r + 1) if ts_buf_r is None: return 0 string.strcpy(ts_buf_r, trigger_sha1) entry_rt.SpecTriggerSha1 = ts_buf_r return 1 def _tp_names_for_spec(self, pool: memhub.MemBuddy*, class_name: str, template_cd: ast.ClassDef*) -> list[str]*: """Return type-param names for specialization (stdlib table first, else template node).""" tp: list[str]* = self._find_generic_template_tp_params(pool, class_name) if tp is not None and len(tp) > 0: return tp if template_cd is not None and template_cd.type_params is not None and len(template_cd.type_params) > 0: return template_cd.type_params self.Gen.TypeGenErr.fatal_error(None, f"generic class template '{class_name}' is missing type param names (GenericClassTpParams slot is empty, " f"and the template node has no type_params) -- refusing to specialize with an empty generic context", "InternalCompilerError") return None def _specialize_generic_class(self, trans: HT.Translator*, class_name: str, type_args: list[str]*) -> str: """Specialize a generic class and return the specialized class name.""" if trans is None or class_name is None or type_args is None: return None # Generic-loop guard: increment recursion depth and abort once the limit is exceeded (prevents # F-bounded etc. from looping forever). Every return path must decrement the depth first. self.GenericSpecDepth += 1 # Specialization trigger sha1: trans.ModuleSha1 is the triggering file on entry but is switched # to the template sha1 below; used to reset the spec class's MethodsEmitted on invalidation. spec_trigger_sha1: str = trans.ModuleSha1 if self.GenericSpecDepth > GENERIC_SPEC_MAX_DEPTH: err_spec: str = trans.Pool.alloc(256) if err_spec is not None: viperlib.snprintf(err_spec, 256, "generic specialization recursion too deep (suspected generic loop), class: %s depth=%d", class_name, self.GenericSpecDepth) self.Gen.TypeGenErr.fatal_error(None, err_spec, "InternalCompilerError") self.Gen.TypeGenErr.fatal_error(None, "generic specialization recursion too deep (suspected generic loop)", "InternalCompilerError") # Defensive: unresolved type-parameter names (e.g. "T") left in type_args are replaced with the # concrete type name to avoid ndarray[T] double-recursion crashes; in-place edits are side-effect free. ta_cnt_g: sizet = len(type_args) ta_i_g: sizet for ta_i_g in range(ta_cnt_g): ta_raw_g: str = type_args.get(ta_i_g) if ta_raw_g is not None: ta_rsv_g: str = self.Gen.TypeGenBase.resolve_generic_type_name(ta_raw_g) if ta_rsv_g is not None and string.strcmp(ta_raw_g, ta_rsv_g) != 0: type_args.set(ta_i_g, ta_rsv_g) pool: memhub.MemBuddy* = trans.Pool # 1. Find the generic template template_cd: ast.ClassDef* = self._find_generic_template(class_name) if template_cd is None: self.GenericSpecDepth -= 1 return None # 2. Generate the specialized name (the single generator normalizes args: bare/prefixed converge) _from_imports: str = None if trans is not None: _from_imports = trans._from_imports spec_name: str = self._mangle_generic_class_name(pool, class_name, type_args, _from_imports) if spec_name is None: self.GenericSpecDepth -= 1 return None # Phase B back-fill of the specialized class's method bodies (unified entry, before cache/existing # checks): Phase 1a only registered the struct, so Phase B re-entering emits the defines. if trans._declare_only == 0: pre_existing: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(spec_name) if pre_existing is not None and pre_existing.MethodsEmitted == 0: # Re-entrancy guard: set MethodsEmitted=1 first so method/return-type annotations cannot # re-trigger specialization (infinite recursion -> OOM); _translate_oop_methods must get reemit=1. pre_existing.MethodsEmitted = 1 # Take type_params from the stdlib deep-copy table (the template node's pool list may be invalid across phases) tp_names_pre: list[str]* = self._tp_names_for_spec(pool, class_name, template_cd) spec_cd_pre: ast.ClassDef* = self._clone_classdef_for_spec(pool, template_cd, spec_name, tp_names_pre, type_args) if spec_cd_pre is not None: trans.GenericTypeParamNames = tp_names_pre trans.GenericTypeArgs = type_args self.Gen.TypeGenBase.set_generic_context(tp_names_pre, type_args) saved_ms_pre: str = trans.ModuleSha1 saved_imp_pre: str = trans._imported_modules template_sha1_pre: str = self._find_generic_template_sha1(class_name) if template_sha1_pre is not None: trans.ModuleSha1 = template_sha1_pre self._merge_template_imports(trans, template_sha1_pre) # Error reporting: the back-filled body is a cloned template, so switch CurrentFile to # the template file and push a trace frame (restored after translation). saved_cur_pre: str = self.Gen.TypeGenResolve.get_current_file() tpl_file_pre: str = self._find_generic_template_file(class_name) if tpl_file_pre is not None: self.Gen.TypeGenResolve.set_current_file(tpl_file_pre) self.Gen.TypeGenBase.push_trace(tpl_file_pre, template_cd.lineno) self.Gen.ClassGenVTable._translate_oop_methods(trans, spec_cd_pre, pre_existing.Ty, spec_name, 0, 1) if tpl_file_pre is not None: self.Gen.TypeGenBase.pop_trace() self.Gen.TypeGenResolve.set_current_file(saved_cur_pre) trans.ModuleSha1 = saved_ms_pre trans._imported_modules = saved_imp_pre trans.GenericTypeParamNames = None trans.GenericTypeArgs = None self.Gen.TypeGenBase.clear_generic_context() # 3. Check the cache (spec_key includes all type args, avoiding multi-param cache collisions) # Allocate a new buffer to avoid in-place strcat overflow sk_n: sizet = len(type_args) sk_total: sizet = string.strlen(class_name) + 2 # "<" and ">" sk_i: sizet for sk_i in range(sk_n): if sk_i > 0: sk_total += 1 # "," sk_ta: str = type_args.get(sk_i) if sk_ta is not None: sk_total += string.strlen(sk_ta) sk_total += 1 # NUL spec_key: str = pool.alloc(sk_total) if spec_key is None: self.GenericSpecDepth -= 1 return None spec_key[0] = '\0' string.strcat(spec_key, class_name) string.strcat(spec_key, "<") for sk_i in range(sk_n): if sk_i > 0: string.strcat(spec_key, ",") sk_ta: str = type_args.get(sk_i) if sk_ta is not None: string.strcat(spec_key, sk_ta) string.strcat(spec_key, ">") cached: str = self._find_cached_spec(spec_key) if cached is not None: self._record_spec_trigger(pool, spec_name, spec_trigger_sha1) self.GenericSpecDepth -= 1 return cached # 4. Check whether it is already registered (Phase B duplicate specialization) existing: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(spec_name) if existing is not None: self._cache_spec(pool, spec_key, spec_name) self._record_spec_trigger(pool, spec_name, spec_trigger_sha1) self.GenericSpecDepth -= 1 return spec_name # Pre-register in the cache (prevents recursion) self._cache_spec(pool, spec_key, spec_name) # 5. Clone the ClassDef node # Take type_params from the stdlib deep-copy table (the template node's pool list may be invalid across phases) tp_names: list[str]* = self._tp_names_for_spec(pool, class_name, template_cd) spec_cd: ast.ClassDef* = self._clone_classdef_for_spec(pool, template_cd, spec_name, tp_names, type_args) if spec_cd is None: self.GenericSpecDepth -= 1 return None # 6. Set the type_map context (used for T replacement in method bodies) trans.GenericTypeParamNames = tp_names trans.GenericTypeArgs = type_args self.Gen.TypeGenBase.set_generic_context(tp_names, type_args) # 6.5 Context self-check (fail-fast, right after setting): the type-param name list and type args # must correspond one-to-one, each resolving to a concrete type; otherwise T fails with NameError. tp_n_c: sizet = len(tp_names) if tp_names is not None else 0 ta_n_c: sizet = len(type_args) if type_args is not None else 0 if tp_n_c == 0 or ta_n_c != tp_n_c: self.Gen.TypeGenErr.fatal_error(None, f"generic class '{class_name}' type argument count ({ta_n_c}) does not match the template type param count ({tp_n_c}), " f"cannot build the specialization context (spec key {spec_key})", "InternalCompilerError") probe_tp: str = tp_names.get(0) if probe_tp is not None: if string.strcmp(self.Gen.TypeGenBase.resolve_generic_type_name(probe_tp), probe_tp) == 0: self.Gen.TypeGenErr.fatal_error(None, f"cannot install the specialization context of generic class '{class_name}': type param '{probe_tp}' does not resolve to a concrete type" f"(spec key {spec_key}) -- type args missing/empty", "InternalCompilerError") # 7. Translate the specialized class: switch trans.ModuleSha1 to the template-defining module so # struct type names and method symbols use its SHA1 prefix; also merge its imports. saved_module_sha1: str = trans.ModuleSha1 saved_imported: str = trans._imported_modules # Save/restore the class-definition window (CurDefModuleSha1/CurDefClassName) alongside ModuleSha1: # translate_class_def overwrites it, and restoring only ModuleSha1 splits one spec into two names. saved_def_mod: str = self.Gen.StructGen.CurDefModuleSha1 saved_def_cls: str = self.Gen.StructGen.CurDefClassName template_sha1: str = self._find_generic_template_sha1(class_name) if template_sha1 is not None: trans.ModuleSha1 = template_sha1 self._merge_template_imports(trans, template_sha1) # Error reporting: spec_cd is cloned from the template, so method node.lineno lines belong to the # template file; switch CurrentFile to it and push a trace frame (restored after translation). saved_cur_full: str = self.Gen.TypeGenResolve.get_current_file() tpl_file_full: str = self._find_generic_template_file(class_name) if tpl_file_full is not None: self.Gen.TypeGenResolve.set_current_file(tpl_file_full) self.Gen.TypeGenBase.push_trace(tpl_file_full, template_cd.lineno) self.Gen.ClassGenDef.translate_class_def(trans, spec_cd) if tpl_file_full is not None: self.Gen.TypeGenBase.pop_trace() self.Gen.TypeGenResolve.set_current_file(saved_cur_full) # Restore the original ModuleSha1 / imports / class-definition window (the window does not shift; see above) trans.ModuleSha1 = saved_module_sha1 trans._imported_modules = saved_imported self.Gen.StructGen.CurDefModuleSha1 = saved_def_mod self.Gen.StructGen.CurDefClassName = saved_def_cls # 8. Clear the type_map context trans.GenericTypeParamNames = None trans.GenericTypeArgs = None self.Gen.TypeGenBase.clear_generic_context() # Specialization field-name integrity check (fail-fast: a missing field name means a corrupted # field table, and silently continuing would make later GEPs use wrong indices). if string.strcmp(class_name, "GSListNode") == 0: result_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(spec_name) if result_entry is not None: for ri in range(result_entry.FieldCount): rfe: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(result_entry, ri) if rfe is not None: rfn: str = self.Gen.StructGen.get_field_name_ptr(rfe) if rfn is None: VLogger.error("SPEC field name missing (field table corrupted)", "SPEC") self.Gen.TypeGenErr.fatal_error(None, "SPEC field name missing (field table corrupted)", "TypeError") self._record_spec_trigger(pool, spec_name, spec_trigger_sha1) self.GenericSpecDepth -= 1 return spec_name