Files
ViperC/App/lib/core/Gen/Class/ClassGenGeneric.vp
T
TermiNexus 7a9bc5ae75 chore(messages): TransPyV 运行时中文字符串英文化
把 TransPyV/App 下 57 个文件的运行时中文字符串(编译器报错/告警/日志、CLI 帮助与横幅、REPL 提示、Phase1/Phase2 流程日志、Codegen 各 Mixin 的类型与调用报错、Stub/Vpi 诊断等)共 1091 行译为英文。

口径:只改字符串字面量内部文字,行数零变化(+1092/-1092);printf 格式符与转义序列多重集合校验不变(_strcheck 异常 0);译文不引入撇号以免干扰单引号字面量解析。验证:三重重编 SUCCESS(136.20s)+ 全量 77/77 顺序回归通过 + VET 编译 SUCCESS / --search-paths 7 条 exit 0;新产物 help/日志输出已为英文。
2026-10-07 00:03:58 +08:00

1017 lines
59 KiB
Plaintext

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: 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)
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