Files
ViperC/App/lib/core/Gen/AssignGenMixin.vp
T
TermiNexus cd55a7e214 refactor: rawptr[T] -> T*(R14)+ 修 REnum 前向占位未就地填充
R14:rawptr[X] -> X*(56 文件 / 834 行改动);保留 6 处 rawptr[t.CPtr](存纯裸指针的规范例外)。 修复:class X(t.REnum) 的变体字段写成自引用 X* 时,字段解析在变体扫描期经 ensure_forward_class 建了一个 opaque 前向占位;末尾 register_struct 按 Ty 指针去重会再建第二个同名 entry,而 find_struct 取首个(占位,0 字段)→ X.__sizeof__() == 0,报「已注册但大小为 0(字段链表未填充)」。改为注册前若命中同模块前向占位则就地填充(与 ensure_forward_class 文档口径一致)。TPC 无此问题,属 TPV 侧缺口。 验证:自举三重重编 SUCCESS;全量 77/77 顺序回归通过(修复前卡在 LLvmLiteTest)。
2026-10-05 17:38:28 +08:00

2418 lines
157 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import t, c
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdio
import stdlib
import viperlib
import w32.fileio as fileio
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HT
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Generator as Generator
import lib.core.CompilerFactory as CompilerFactory
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.core.Gen.GenApi as GenApi
# AssignGenMixin - assignment translation mixin
novirtual class AssignGenMixin:
Gen: Generator.Generator*
"""Assignment translation mixin."""
RecoverFound: int
# Check whether an annotation is t.CDefine (module-level function)
def is_cdefine_annotation(self, annot: ast.AST*) -> int:
"""Return 1 if the annotation is t.CDefine, else 0."""
if annot is None: return 0
k: int = annot.kind()
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(annot)
if at.attr is not None and string.strcmp(at.attr, "CDefine") == 0: return 1
return 0
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(annot)
if self.is_cdefine_annotation(bop.left) != 0: return 1
if self.is_cdefine_annotation(bop.right) != 0: return 1
return 0
# extract_cdefine_int_value - extract an integer constant from AnnAssign.value
def extract_cdefine_int_value(self, val_node: ast.AST*) -> int64:
"""Extract an integer constant from an AnnAssign value node."""
if val_node is None: return 0
k: int = val_node.kind()
# Case 1: Constant(INT) - e.g. 0x0002
if k == ast.ASTKind.Constant:
cn: ast.Constant* = ast.Constant*(val_node)
# True/False boolean constant -> 1/0 (bool treated as int in CDefine context)
if cn.const_kind == ast.CONST_BOOL: return 1 if cn.int_val != 0 else 0
if cn.const_kind != ast.CONST_INT:
# Non-INT constant: fail fast
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine 值提取失败:无法从表达式解析编译期常量", "TypeError")
return cn.int_val
# Case 2: BinOp - e.g. FOREGROUND_RED | FOREGROUND_GREEN, (1 << N)
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(val_node)
left_val: int64 = self.extract_cdefine_int_value(bop.left)
right_val: int64 = self.extract_cdefine_int_value(bop.right)
if bop.op == ast.OpKind.BitOr: return left_val | right_val
if bop.op == ast.OpKind.BitAnd: return left_val & right_val
if bop.op == ast.OpKind.BitXor: return left_val ^ right_val
if bop.op == ast.OpKind.LShift: return left_val << right_val
if bop.op == ast.OpKind.RShift: return left_val >> right_val
if bop.op == ast.OpKind.Add: return left_val + right_val
if bop.op == ast.OpKind.Sub: return left_val - right_val
if bop.op == ast.OpKind.Mult: return left_val * right_val
if bop.op == ast.OpKind.Div: return left_val // right_val
if bop.op == ast.OpKind.FloorDiv: return left_val // right_val
if bop.op == ast.OpKind.Mod: return left_val % right_val
# Unknown BinOp operator: fail fast
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine 值提取失败:无法从表达式解析编译期常量", "TypeError")
# Case 3: Name - a registered CDefine constant or True/False literal
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(val_node)
if nm.id is not None:
# True/False literal -> 1/0 (bool treated as int in CDefine context)
if string.strcmp(nm.id, "True") == 0: return 1
if string.strcmp(nm.id, "False") == 0: return 0
looked_up: int64 = self.Gen.TypeGenCDef.lookup_cdefine_constant(nm.id)
if self.Gen.TypeGenCDef.is_cdefine_found() != 0: return looked_up
# Unregistered reference: fail fast
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine 值提取失败:无法从表达式解析编译期常量", "TypeError")
# Case 4: Call - e.g. t.CUnsignedLong(-11)
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(val_node)
# Bind the property to a list[...]* local before calling len() (else GEP base breaks)
cl_args_list: list[ast.AST*]* = cl.args
if cl_args_list is not None and len(cl_args_list) > 0:
first_arg: ast.AST* = cl_args_list.get(0)
arg_val: int64 = self.extract_cdefine_int_value(first_arg)
return arg_val
# Call with no args: fail fast
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine 值提取失败:无法从表达式解析编译期常量", "TypeError")
# Case 5: UnaryOp - e.g. -11
if k == ast.ASTKind.UnaryOp:
uop: ast.UnaryOp* = ast.UnaryOp*(val_node)
operand_val: int64 = self.extract_cdefine_int_value(uop.operand)
if uop.op == ast.OpKind.USub: return -operand_val
if uop.op == ast.OpKind.UAdd: return operand_val
if uop.op == ast.OpKind.Invert: return ~operand_val
# Unknown UnaryOp operator: fail fast
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine 值提取失败:无法从表达式解析编译期常量", "TypeError")
# Unknown node kind: fail fast
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine 值提取失败:无法从表达式解析编译期常量", "TypeError")
# After fatal_error add an explicit return so the block is properly terminated
return 0
# define / t.CDefine integer constant storable and width checks
# Auto-derived => 64-bit pattern; explicit type T => T's width range.
def define_min_width(self, value: int64) -> int:
"""Return the minimal storable width (8/16/32/64) for an integer value."""
if value < 0:
if value >= -128: return 8
if value >= -32768: return 16
# -2^31: use an arithmetic equivalent to avoid an i32-wrapping literal
if value >= -2147483647 - 1: return 32
return 64
if value <= 255: return 8
if value <= 65535: return 16
if value <= 2147483647: return 32
# 2^31..2^32-1: fits an unsigned 32-bit bit pattern
if uint64(uint32(value)) == value: return 32
return 64
def _is_cdefine_marker_node(self, node: ast.AST*) -> int:
"""Return 1 if the node is a t.CDefine marker node."""
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(node)
if at.attr is not None and string.strcmp(at.attr, "CDefine") == 0: return 1
return 0
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is not None and string.strcmp(nm.id, "CDefine") == 0: return 1
return 0
def _define_explicit_annot(self, annot: ast.AST*) -> ast.AST*:
"""Return the explicit type T of a `T | t.CDefine` annotation, or None."""
if annot is None: return None
if annot.kind() != ast.ASTKind.BinOp: return None
bop: ast.BinOp* = ast.BinOp*(annot)
if bop.left is None: return None
if self._is_cdefine_marker_node(bop.left) != 0: return None
return bop.left
def _annot_int_bits(self, pool: memhub.MemBuddy*, annot: ast.AST*) -> int:
"""Return the bit width of an integer annotation, or 0 if unknown."""
if annot is None: return 0
k: int = annot.kind()
nm: str = None
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(annot)
nm = at.attr
elif k == ast.ASTKind.Name:
n2: ast.Name* = ast.Name*(annot)
nm = n2.id
if nm is None: return 0
if string.strcmp(nm, "int") == 0: return 32
if string.strcmp(nm, "bool") == 0: return 8
# Fixed-width lowercase aliases from stdint/builtins (not t module names)
if string.strcmp(nm, "int8") == 0 or string.strcmp(nm, "uint8") == 0: return 8
if string.strcmp(nm, "int16") == 0 or string.strcmp(nm, "uint16") == 0: return 16
if string.strcmp(nm, "int32") == 0 or string.strcmp(nm, "uint32") == 0: return 32
if string.strcmp(nm, "int64") == 0 or string.strcmp(nm, "uint64") == 0: return 64
if string.strcmp(nm, "sizet") == 0 or string.strcmp(nm, "size_t") == 0: return 64
ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.map_t_type(pool, nm)
if ty is None: return 0
match ty:
case mlirlite.LLVMType.Int(bits):
if bits == 1: return 8
if bits == 8 or bits == 16 or bits == 32 or bits == 64: return bits
return 0
case _:
return 0
def _define_annot_bits(self, pool: memhub.MemBuddy*, annot: ast.AST*) -> int:
"""Return the bit width of a CDefine declaration's explicit type T."""
return self._annot_int_bits(pool, self._define_explicit_annot(annot))
def _cdefine_int_literal_text(self, name: str) -> str:
"""Return the raw value text of a CDefine line, or None if not found."""
if name is None: return None
fp: str = self.Gen.TypeGenResolve.get_current_file()
if fp is None: return None
f: fileio.File* = fileio.File(fp, fileio.MODE.R)
if f.closed: return None
BUF_SZ: sizet = 1048576
buf: bytes = stdlib.malloc(BUF_SZ)
if buf is None:
f.close()
return None
nrd: LONG = f.read_all(buf, BUF_SZ)
f.close()
if nrd <= 0:
stdlib.free(buf)
return None
if nrd < BUF_SZ: buf[nrd] = 0
name_len: sizet = string.strlen(name)
total_len: sizet = string.strlen(buf)
pos: sizet = 0
result: str = None
while pos < total_len:
line_start: sizet = pos
while pos < total_len and buf[pos] != '\n': pos += 1
line_len: sizet = pos - line_start
if pos < total_len: pos += 1
if line_len < name_len + 1: continue
ok: int = 1
ki: sizet
for ki in range(name_len):
if buf[line_start + ki] != name[ki]:
ok = 0
break
if ok == 0: continue
sep: sizet = line_start + name_len
while sep < line_start + line_len and buf[sep] == ' ': sep += 1
if sep >= line_start + line_len: continue
if buf[sep] != ':' and buf[sep] != '=': continue
eq: sizet = line_start
while eq < line_start + line_len and buf[eq] != '=': eq += 1
if eq >= line_start + line_len: continue
eq += 1
while eq < line_start + line_len and buf[eq] == ' ': eq += 1
vstart: sizet = eq
while eq < line_start + line_len and buf[eq] != ' ' and buf[eq] != '\t' and buf[eq] != '#' and buf[eq] != '\r': eq += 1
if eq == vstart: continue
txt: str = stdlib.malloc(eq - vstart + 1)
if txt is None: continue
vi: sizet
for vi in range(eq - vstart): txt[vi] = buf[vstart + vi]
txt[eq - vstart] = 0
result = txt
break
stdlib.free(buf)
return result
def _int_text_exceeds_64(self, text: str) -> int:
"""Return 1 if the literal text exceeds the 64-bit range, else 0."""
if text is None: return 0
tl: sizet = string.strlen(text)
if tl == 0: return 0
neg: int = 0
i: sizet = 0
if text[0] == '-':
neg = 1
i = 1
elif text[0] == '+': i = 1
if i >= tl: return 0
if i + 1 < tl and text[i] == '0' and (text[i + 1] == 'x' or text[i + 1] == 'X'):
# Hex: significant digits after removing '_' and leading zeros (>16 => overflow)
sig: sizet = 0
j2: sizet = i + 2
leading: int = 1
while j2 < tl:
c2: char = text[j2]
j2 += 1
if c2 == '_': continue
if not ((c2 >= '0' and c2 <= '9') or (c2 >= 'a' and c2 <= 'f') or (c2 >= 'A' and c2 <= 'F')):
return 0
if leading != 0 and c2 == '0': continue
leading = 0
sig += 1
if sig > 16: return 1
return 0
# Decimal: locate the first significant digit
s: sizet = i
while s < tl and (text[s] == '0' or text[s] == '_'): s += 1
digits2: int = 0
j3: sizet = s
while j3 < tl:
c3: char = text[j3]
j3 += 1
if c3 == '_': continue
if c3 < '0' or c3 > '9': return 0
digits2 += 1
if digits2 == 0: return 0
max_text: str = "18446744073709551615"
mlen: sizet = 20
if neg != 0:
max_text = "9223372036854775808"
mlen = 19
if digits2 > mlen: return 1
if digits2 < mlen: return 0
# Equal length: lexicographic digit compare (same as numeric order)
d: sizet = 0
k2: sizet = s
while k2 < tl and d < mlen:
c4: char = text[k2]
k2 += 1
if c4 == '_': continue
mc: char = max_text[d]
if c4 > mc: return 1
if c4 < mc: return 0
d += 1
return 0
def require_cdefine_storable(self, pool: memhub.MemBuddy*, node: ast.AST*, name: str, value: int64, annot: ast.AST*) -> None:
"""Fail fast if a CDefine value is not storable in its declared type."""
text: str = self._cdefine_int_literal_text(name)
bits: int = self._define_annot_bits(pool, annot)
wmin: int = self.define_min_width(value)
if self._int_text_exceeds_64(text) != 0:
self.Gen.TypeGenErr.fatal_error(node, f"[TPV] define 常量数值不可存储: 常量 '{name}' 的初始值 {text} 超出 64 位" f"(本语言最宽的整数类型为 64 位,按有符号/无符号两种解释的可存储范围为 " f"-9223372036854775808..18446744073709551615,不存在更宽的整数类型 ⇒ 需要扩容)。" f"禁止静默截断/降级,已终止编译。可用整数类型: int8/uint8、" f"int16/uint16、int32/uint32、int64/uint64" f"(或 int8/uint8/…/uint64、char、sizet)。" f"修法: 把值改到 64 位范围内,或为该数字引入更大的整数类型(扩容)。", "TypeError")
return
if bits == 0: return
# Build the 32-bit bounds from overflow-safe literals (see define_min_width)
m31: int64 = 2147483647
lo: int64 = -m31 - 1
hi: int64 = m31 * 2 + 1
if bits == 8:
lo = -128
hi = 255
elif bits == 16:
lo = -32768
hi = 65535
elif bits == 64: return
if value < lo or value > hi:
self.Gen.TypeGenErr.fatal_error(node, f"[TPV] define 常量数值不可存储: 常量 '{name}' 的初始值 {value} 不可存储。" f"最小可容纳宽度 W_min: {wmin} 位;声明类型位宽 {bits} 位," f"该声明可存储范围: {lo}..{hi}。" f"禁止静默截断/降级,已终止编译。" f"修法: 把显式类型改为更宽的类型(如 int32 / int64 / uint64)," f"或修正该字面量。", "TypeError")
def require_cdefine_use_width(self, pool: memhub.MemBuddy*, trans: HT.Translator*, target_annot: ast.AST*, value_node: ast.AST*) -> None:
"""Fail fast when a define constant is wider than its use-site type."""
if value_node is None or target_annot is None: return
if value_node.kind() != ast.ASTKind.Name: return
rn: ast.Name* = ast.Name*(value_node)
if rn.id is None: return
if trans is not None:
if self.Gen.VarGen.lookup_var(trans.SymTab, rn.id) is not None: return
cdef_val: int64 = self.Gen.TypeGenCDef.lookup_cdefine_constant(rn.id)
if self.Gen.TypeGenCDef.is_cdefine_found() == 0: return
bits: int = self._annot_int_bits(pool, target_annot)
if bits == 0: return
wmin: int = self.define_min_width(cdef_val)
if bits >= wmin: return
self.Gen.TypeGenErr.fatal_error(value_node, f"[TPV] define 常量使用处宽度不足: 常量 '{rn.id}' 的实际值 {cdef_val} " f"需要至少 {wmin} 位(最小可容纳宽度 W_min)," f"但使用处(变量声明注解)只提供 {bits} 位。" f"禁止静默截断/降级,已终止编译。" f"修法: 把变量声明注解加宽到至少 {wmin} 位(如 int{wmin}T / CUInt{wmin}T)," f"或改用更小的常量值。", "TypeError")
# _register_cdefine_from_value - register a CDefine constant (float or int)
def _register_cdefine_from_value(self, pool: memhub.MemBuddy*, name: str, val_node: ast.AST*, annot: ast.AST* = None) -> None:
"""Register a CDefine constant (double for floats, else integer)."""
if pool is None or name is None: return
# Call-wrapped constant (e.g. float(3.14)): use its first argument as the value
if val_node is not None and val_node.kind() == ast.ASTKind.Call:
cl_cd: ast.Call* = ast.Call*(val_node)
if cl_cd is not None and cl_cd.args is not None:
cl_args_cd: list[ast.AST*]* = cl_cd.args
if len(cl_args_cd) > 0:
first_arg_cd: ast.AST* = cl_args_cd.get(0)
if first_arg_cd is not None and first_arg_cd.kind() == ast.ASTKind.Constant: val_node = first_arg_cd
# Negative float literal: unwrap UnaryOp(USub, Constant(float)); value recovered from source
if val_node is not None and val_node.kind() == ast.ASTKind.UnaryOp:
uu_cd: ast.UnaryOp* = ast.UnaryOp*(val_node)
if uu_cd is not None and uu_cd.op == ast.OpKind.USub and uu_cd.operand is not None and uu_cd.operand.kind() == ast.ASTKind.Constant:
ic_cd: ast.Constant* = ast.Constant*(uu_cd.operand)
if ic_cd is not None and ic_cd.const_kind == ast.CONST_FLOAT: val_node = uu_cd.operand
if val_node is not None and val_node.kind() == ast.ASTKind.Constant:
cn_fp: ast.Constant* = ast.Constant*(val_node)
if cn_fp.const_kind == ast.CONST_STR:
# String CDefine: register as a string constant (reader returns a global string)
self.Gen.TypeGenCDef.register_cdefine_string(pool, name, cn_fp.str_val)
return
if cn_fp.const_kind == ast.CONST_FLOAT:
# Recover the float value from the source file (AST float_val may be overwritten)
use_val: double = cn_fp.float_val
rec_d: double = 0.0
if self._recover_cdefine_float(name, t.CPtr(&rec_d)) != 0: use_val = rec_d
else:
# Source recovery failed: fail fast to avoid registering a corrupted float
self.Gen.TypeGenErr.fatal_error(val_node, "CDefine 浮点值源文件恢复失败", "InternalCompilerError")
return
self.Gen.TypeGenCDef.register_cdefine_double(pool, name, use_val)
return
if val_node is None:
# CDefine declaration without a value node: fail fast
self.Gen.TypeGenErr.fatal_error(None, f"CDefine 值提取失败:'{name}' 缺少初始值", "TypeError")
return
val_fp: int64 = self.extract_cdefine_int_value(val_node)
# Check storability before registering (source text >64-bit or explicit T too narrow => fail)
self.require_cdefine_storable(pool, val_node, name, val_fp, annot)
self.Gen.TypeGenCDef.register_cdefine_constant(pool, name, val_fp)
# Recover a CDefine float value by re-reading its source line with atof
def _recover_found(self) -> int:
"""Return whether the last _recover_cdefine_float succeeded."""
return self.RecoverFound
def _recover_float_literal(self, name: str, out: double*, require_cdefine: int) -> int:
"""Recover a float literal from the module source into out[0]; 1 on success."""
self.RecoverFound = 0
fp: str = self.Gen.TypeGenResolve.get_current_file()
if fp is None or name is None or out is None: return 0
f: fileio.File* = fileio.File(fp, fileio.MODE.R)
if f.closed: return 0
BUF_SZ: sizet = 1048576
buf: bytes = stdlib.malloc(BUF_SZ)
if buf is None:
f.close()
return 0
nrd: LONG = f.read_all(buf, BUF_SZ)
f.close()
if nrd <= 0:
stdlib.free(buf)
return 0
if nrd < BUF_SZ: buf[nrd] = 0
name_len: sizet = string.strlen(name)
total_len: sizet = string.strlen(buf)
pos: sizet = 0
while pos < total_len:
line_start: sizet = pos
while pos < total_len and buf[pos] != '\n': pos += 1
line_len: sizet = pos - line_start
if pos < total_len: pos += 1
# define NAME: T = V form: the name is preceded by the define keyword
ns: sizet = line_start
is_define_line: int = 0
if line_len >= 8:
if buf[ns] == 'd' and buf[ns + 1] == 'e' and buf[ns + 2] == 'f' and buf[ns + 3] == 'i' and buf[ns + 4] == 'n' and buf[ns + 5] == 'e':
nb: sizet = ns + 6
while nb < line_start + line_len and (buf[nb] == ' ' or buf[nb] == '\t'): nb += 1
# Keyword only when followed by whitespace (avoid matching defineX)
if nb > ns + 6:
is_define_line = 1
ns = nb
# Match the name at line start (or after define); it must be followed by ':' or '='
if ns + name_len + 1 > line_start + line_len: continue
match_ok: int = 1
ki: sizet
for ki in range(name_len):
if buf[ns + ki] != name[ki]:
match_ok = 0
break
if match_ok == 0: continue
sep: sizet = ns + name_len
while sep < line_start + line_len and buf[sep] == ' ': sep += 1
if sep >= line_start + line_len: continue
if buf[sep] != ':' and buf[sep] != '=': continue
# CDefine path requires t.CDefine in the line, or the define keyword form
if require_cdefine != 0 and is_define_line == 0:
has_cd: int = 0
ci: sizet = line_start + name_len + 1
while ci + 8 <= line_start + line_len:
if (buf[ci] == 't' and buf[ci + 1] == '.' and buf[ci + 2] == 'C' and buf[ci + 3] == 'D' and buf[ci + 4] == 'e' and buf[ci + 5] == 'f' and buf[ci + 6] == 'i' and buf[ci + 7] == 'n' and buf[ci + 8] == 'e'):
has_cd = 1
break
ci += 1
if has_cd == 0: continue
# Find '='
eq: sizet = line_start
while eq < line_start + line_len and buf[eq] != '=': eq += 1
if eq >= line_start + line_len: continue
eq += 1
while eq < line_start + line_len and buf[eq] == ' ': eq += 1
# Call-wrapped float constant: skip the float( prefix and start at the first numeric char
if eq < line_start + line_len:
c0_rf: char = buf[eq]
if not ((c0_rf >= '0' and c0_rf <= '9') or c0_rf == '-' or c0_rf == '+' or c0_rf == '.'):
pfx_rf: sizet = eq
while pfx_rf < line_start + line_len:
cp_rf: char = buf[pfx_rf]
if (cp_rf >= '0' and cp_rf <= '9') or cp_rf == '.' or cp_rf == '-' or cp_rf == '+':
eq = pfx_rf
break
pfx_rf += 1
val_start: sizet = eq
while eq < line_start + line_len and buf[eq] != ' ' and buf[eq] != '\t' and buf[eq] != '#' and buf[eq] != '\r' and buf[eq] != '\n': eq += 1
if eq == val_start: continue
val_txt: str = stdlib.malloc(eq - val_start + 1)
if val_txt is None:
stdlib.free(buf)
return 0
vi: sizet
for vi in range(eq - val_start): val_txt[vi] = buf[val_start + vi]
val_txt[eq - val_start] = 0
stdlib.free(buf)
# Use atof_out (an out param) to avoid the cross-module double return bug
if string.atof_out(val_txt, out) != 0:
self.RecoverFound = 1
return 1
return 0
stdlib.free(buf)
return 0
def _recover_cdefine_float(self, name: str, out: double*) -> int:
"""Recover a CDefine float value (requiring a t.CDefine line)."""
return self._recover_float_literal(name, out, 1)
# _annotation_contains_name - recursively check whether an annotation contains a name
def _annotation_contains_name(self, annot: ast.AST*, name: str) -> int:
"""Return 1 if the annotation recursively contains the given name."""
if annot is None or name is None: return 0
k: int = annot.kind()
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(annot)
if at.attr is not None and string.strcmp(at.attr, name) == 0: return 1
return 0
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(annot)
if nm.id is not None and string.strcmp(nm.id, name) == 0: return 1
return 0
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(annot)
if self._annotation_contains_name(bop.left, name) != 0: return 1
if self._annotation_contains_name(bop.right, name) != 0: return 1
return 0
# _is_ptr_element_annotation - check whether an annotation is bytes* or str*
# bytes*/str* => 8-byte element stride (i8**); char*/int8* => 1-byte stride.
def _is_ptr_element_annotation(self, annot: ast.AST*) -> int:
"""Return 1 if the annotation is a bytes* or str* form."""
if annot is None: return 0
k: int = annot.kind()
if k != ast.ASTKind.BinOp: return 0
bop: ast.BinOp* = ast.BinOp*(annot)
if bop.op != ast.OpKind.BitOr: return 0
if self._annotation_contains_name(annot, "str") != 0: return 1
if self._annotation_contains_name(annot, "bytes") != 0: return 1
# Generic type param / nested union | t.CPtr: scalars use their own size, pointers use 8-byte slots
if self._is_generic_tp_or_nested(bop.left) != 0 or self._is_generic_tp_or_nested(bop.right) != 0:
gp_nm: str = None
if bop.left is not None and bop.left.kind() == ast.ASTKind.Name:
gp_left_nm: ast.Name* = ast.Name*(bop.left)
gp_nm = gp_left_nm.id
elif bop.right is not None and bop.right.kind() == ast.ASTKind.Name:
gp_right_nm: ast.Name* = ast.Name*(bop.right)
gp_nm = gp_right_nm.id
if gp_nm is not None:
# Resolve the generic param via the global gen (direct field access breaks static binding)
gp_resolved: str = self.Gen.TypeGenBase.resolve_generic_type_name(gp_nm)
if gp_resolved is not None and string.strcmp(gp_resolved, gp_nm) != 0:
# Generic param resolved to a scalar => use scalar size, not an 8-byte pointer slot
if self._is_scalar_ty_name(gp_resolved) != 0: return 0
return 1
# 64-bit scalar | CPtr: element is 8 bytes, so use an 8-byte stride (else GEP truncation)
if self._is_64bit_scalar_ty(bop.left) != 0 or self._is_64bit_scalar_ty(bop.right) != 0: return 1
return 0
def _stg_ty_equal(self, pool: memhub.MemBuddy*, ta: mlirlite.LLVMType*, tb: mlirlite.LLVMType*) -> int:
"""Return 1 if the two types are equal by printed content."""
if ta is None or tb is None: return 0
# Fast path: identical instance is equal
if uint64(ta) == uint64(tb): return 1
buf_a: str = pool.alloc(512)
buf_b: str = pool.alloc(512)
if buf_a is None or buf_b is None: return 0
mlirlite.TypePrint(buf_a, 512, ta, pool)
mlirlite.TypePrint(buf_b, 512, tb, pool)
if buf_a[0] == 0 or buf_b[0] == 0: return 0
return 1 if string.strcmp(buf_a, buf_b) == 0 else 0
def _is_addrlike_call(self, node: ast.AST*) -> int:
"""Return 1 if the rhs is an explicit c.Addr/c.Deref-style pointer call."""
if node is None or node.kind() != ast.ASTKind.Call: return 0
cl: ast.Call* = ast.Call*(node)
if cl.func is None or cl.func.kind() != ast.ASTKind.Attribute: return 0
at: ast.Attribute* = ast.Attribute*(cl.func)
if at.value is None or at.value.kind() != ast.ASTKind.Name: return 0
vn: ast.Name* = ast.Name*(at.value)
if vn.id is None or string.strcmp(vn.id, "c") != 0: return 0
if at.attr is None: return 0
if string.strcmp(at.attr, "Addr") == 0: return 1
if string.strcmp(at.attr, "Deref") == 0: return 1
if string.strcmp(at.attr, "Cast") == 0: return 1
if string.strcmp(at.attr, "Ptr") == 0: return 1
return 0
def _is_64bit_scalar_ty(self, node: ast.AST*) -> int:
"""Return 1 for a 64-bit scalar type (CUInt64T/CInt64T/CSizeT)."""
if node is None: return 0
ty_nm: str = None
nk: int = node.kind()
if nk == ast.ASTKind.Name:
nm64: ast.Name* = ast.Name*(node)
ty_nm = nm64.id
elif nk == ast.ASTKind.Attribute:
at64: ast.Attribute* = ast.Attribute*(node)
ty_nm = at64.attr
if ty_nm is None: return 0
if string.strcmp(ty_nm, "CUInt64T") == 0: return 1
if string.strcmp(ty_nm, "CInt64T") == 0: return 1
if string.strcmp(ty_nm, "CSizeT") == 0: return 1
return 0
def _is_scalar_ty_name(self, ty_name: str) -> int:
"""Return 1 if the type name is a known pure scalar (whitelist)."""
if ty_name is None: return 0
# Strip the t./c. namespace prefix so int and bare aliases are judged the same
if ty_name[0] == 't' and ty_name[1] == '.': ty_name += 2
elif ty_name[0] == 'c' and ty_name[1] == '.': ty_name += 2
if string.strchr(ty_name, '|') is not None: return 0
if string.strchr(ty_name, '*') is not None: return 0
if string.strcmp(ty_name, "str") == 0: return 0
if string.strcmp(ty_name, "bytes") == 0: return 0
if string.strcmp(ty_name, "CPtr") == 0: return 0
if string.strstr(ty_name, "Ptr") is not None: return 0
# Whitelist: known pure scalars (base names + stdint uppercase aliases)
if string.strcmp(ty_name, "int") == 0: return 1
if string.strcmp(ty_name, "char") == 0: return 1
# Python builtin scalar names (float/double/bool) pass through as scalar types
if string.strcmp(ty_name, "float") == 0: return 1
if string.strcmp(ty_name, "double") == 0: return 1
if string.strcmp(ty_name, "bool") == 0: return 1
if string.strcmp(ty_name, "CInt") == 0: return 1
if string.strcmp(ty_name, "CUnsignedInt") == 0: return 1
if string.strcmp(ty_name, "CUnsigned") == 0: return 1
if string.strcmp(ty_name, "CChar") == 0: return 1
if string.strcmp(ty_name, "CUnsignedChar") == 0: return 1
if string.strcmp(ty_name, "CSignedChar") == 0: return 1
if string.strcmp(ty_name, "CShort") == 0: return 1
if string.strcmp(ty_name, "CUnsignedShort") == 0: return 1
if string.strcmp(ty_name, "CLong") == 0: return 1
if string.strcmp(ty_name, "CUnsignedLong") == 0: return 1
if string.strcmp(ty_name, "CLongLong") == 0: return 1
if string.strcmp(ty_name, "CUnsignedLongLong") == 0: return 1
if string.strcmp(ty_name, "CSizeT") == 0: return 1
if string.strcmp(ty_name, "CBool") == 0: return 1
# Character width family: widths 8/16/32 are not 8-byte pointer slots
if string.strcmp(ty_name, "CWCharT") == 0: return 1
if string.strcmp(ty_name, "CChar8T") == 0: return 1
if string.strcmp(ty_name, "CChar16T") == 0: return 1
if string.strcmp(ty_name, "CChar32T") == 0: return 1
if string.strcmp(ty_name, "BOOL") == 0: return 1
if string.strcmp(ty_name, "BYTE") == 0: return 1
if string.strcmp(ty_name, "UCHAR") == 0: return 1
if string.strcmp(ty_name, "CHAR") == 0: return 1
if string.strcmp(ty_name, "SHORT") == 0: return 1
if string.strcmp(ty_name, "USHORT") == 0: return 1
if string.strcmp(ty_name, "LONG") == 0: return 1
if string.strcmp(ty_name, "ULONG") == 0: return 1
if string.strcmp(ty_name, "LONGLONG") == 0: return 1
if string.strcmp(ty_name, "ULONGLONG") == 0: return 1
if string.strcmp(ty_name, "INT") == 0: return 1
if string.strcmp(ty_name, "UINT") == 0: return 1
if string.strcmp(ty_name, "WORD") == 0: return 1
if string.strcmp(ty_name, "DWORD") == 0: return 1
if string.strcmp(ty_name, "QWORD") == 0: return 1
# Lowercase bare C type aliases from builtins (same table)
if string.strcmp(ty_name, "uchar") == 0: return 1
if string.strcmp(ty_name, "short") == 0: return 1
if string.strcmp(ty_name, "ushort") == 0: return 1
if string.strcmp(ty_name, "uint") == 0: return 1
if string.strcmp(ty_name, "long") == 0: return 1
if string.strcmp(ty_name, "ulong") == 0: return 1
if string.strcmp(ty_name, "longlong") == 0: return 1
if string.strcmp(ty_name, "ulonglong") == 0: return 1
# Fixed-width int/float (C*T forms and bare aliases, prefix-matched)
if string.strstr(ty_name, "Int8") is not None: return 1
if string.strstr(ty_name, "Int16") is not None: return 1
if string.strstr(ty_name, "Int32") is not None: return 1
if string.strstr(ty_name, "Int64") is not None: return 1
if string.strstr(ty_name, "UInt8") is not None: return 1
if string.strstr(ty_name, "UInt16") is not None: return 1
if string.strstr(ty_name, "UInt32") is not None: return 1
if string.strstr(ty_name, "UInt64") is not None: return 1
if string.strstr(ty_name, "Float") is not None: return 1
if string.strstr(ty_name, "FLOAT") is not None: return 1
if string.strstr(ty_name, "Double") is not None: return 1
if string.strstr(ty_name, "DOUBLE") is not None: return 1
# Unknown class name => not a scalar, use an 8-byte pointer slot
return 0
def _is_generic_tp_or_nested(self, node: ast.AST*) -> int:
"""Return 1 for a generic type-param name or nested union (BinOp)."""
if node is None: return 0
nk: int = node.kind()
if nk == ast.ASTKind.BinOp: return 1
if nk == ast.ASTKind.Name:
nm_tp: ast.Name* = ast.Name*(node)
if nm_tp.id is None: return 0
tp_len: sizet = string.strlen(nm_tp.id)
if tp_len == 1:
if nm_tp.id[0] >= 65 and nm_tp.id[0] <= 90: return 1
return 0
# _init_global_array_from_list - initialize a global array from a list literal
def _init_global_array_from_list(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, array_alloca: mlirlite.Value*, list_node: ast.AST*, trans: HT.Translator*, owner_ctx: str = None) -> int:
"""Initialize a global array from a list literal via per-element GEP+store."""
if builder is None or array_alloca is None or list_node is None: return 1
# Get the array type (pointee of [N x elem_ty]*)
arr_ty: mlirlite.LLVMType* = None
if array_alloca.Ty is not None: arr_ty = array_alloca.Ty.Pointee
if arr_ty is None: return 1
# Match the array type to get the element type and declared length
elem_ty: mlirlite.LLVMType* = None
arr_count: int = 0
match arr_ty:
case mlirlite.LLVMType.Array(et, cnt):
elem_ty = et
arr_count = cnt
if elem_ty is None: return 1
# Get the list elements
lst: ast.List* = ast.List*(list_node)
if lst is None or lst.elts is None: return 1
elts: list[ast.AST*]* = lst.elts
elts_count: sizet = len(elts)
# Special case: a single string literal initializes a char array byte by byte
if self.Gen.ExprGenBase.get_llvm_type_bits(elem_ty) == 8 and elts_count == 1 and arr_count >= 1:
e0_s: ast.AST* = elts.get(0)
if e0_s is not None and e0_s.kind() == ast.ASTKind.Constant:
c0_s: ast.Constant* = ast.Constant*(e0_s)
if c0_s.const_kind == ast.CONST_STR:
sv_s: str = ast.constant_get_str_val(c0_s)
if sv_s is not None:
n_s: sizet = string.strlen(sv_s)
if sizet(arr_count) < n_s:
self.Gen.TypeGenErr.fatal_error(list_node, f"字符串字面量长度 {n_s} 超过字符数组声明长度 {arr_count},不允许越界写入", "OverflowError")
for bi_s in range(n_s):
bidx_s: mlirlite.Value* = mlirlite.const_int32(pool, bi_s)
bp_s: mlirlite.Value* = mlirlite.build_gep_array(builder, arr_ty, elem_ty, array_alloca, bidx_s)
if bp_s is None: break
bvv: mlirlite.Value* = mlirlite.const_int32(pool, sv_s[bi_s])
bsv: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, bvv, elem_ty, e0_s)
mlirlite.build_store(builder, bsv, bp_s)
return 0
# Element count exceeds declared length: fail fast (avoid out-of-range GEP writes)
if arr_count > 0 and sizet(arr_count) < elts_count:
self.Gen.TypeGenErr.fatal_error(list_node, f"数组初始化元素数 {elts_count} 超过数组声明长度 {arr_count},不允许越界写入", "OverflowError")
# Emit GEP + store for each element
for i in range(elts_count):
elem_node: ast.AST* = elts.get(i)
# Explicit null check (repo convention: is None); an empty slot means an element count mismatch.
# When the slot is null, fail fast with class/field/index context.
if elem_node is None:
ctx_dbg: str = ""
if owner_ctx is not None: ctx_dbg = owner_ctx
src_line: int = 0
src_col: int = 0
if list_node is not None:
src_line = list_node.lineno
src_col = list_node.col_offset
self.Gen.TypeGenErr.fatal_error(list_node, f"全局数组初始化遇到空 AST 元素:{ctx_dbg}第 {i} 个元素为裸空指针(0)," f"但列表元素计数为 {elts_count}(元素与计数不一致)。" f"上层某处产生了空 AST 元素(列表字面量节点 {src_line}:{src_col} 的第 {i} 槽未被填充)," f"拒绝静默跳过/补零,已终止编译。", "ValueError")
return 1
# Object validity check (nonzero vptr): a zero vptr makes any virtual call read address 0
elem_vptr: uint64 = *((uint64*)(elem_node))
if elem_vptr == 0:
ctx_vt: str = ""
if owner_ctx is not None: ctx_vt = owner_ctx
vt_line: int = 0
vt_col: int = 0
if list_node is not None:
vt_line = list_node.lineno
vt_col = list_node.col_offset
self.Gen.TypeGenErr.fatal_error(list_node, f"全局数组初始化遇到【对象无效】的 AST 元素(vptr 为 0):{ctx_vt}" f"第 {i} 个元素指针非 0(地址 {uint64(elem_node)})但其对象首字(vtable 指针)为 0," f"对它的任何虚调用(kind()/dump()/…) 都会在 `load [0+slot]` 处读地址 0。" f"列表字面量节点位置 {vt_line}:{vt_col},元素计数 {elts_count}。" f"上层某处产生了「未初始化 vptr 的 AST 节点」(典型源:深拷贝/整块拷贝只搬字段未搬 vptr)," f"拒绝继续,已终止编译。", "ValueError", vt_col)
return 1
idx_val: mlirlite.Value* = mlirlite.const_int32(pool, i)
elem_ptr: mlirlite.Value* = mlirlite.build_gep_array(builder, arr_ty, elem_ty, array_alloca, idx_val)
if elem_ptr is None: continue
# Nested array: element type is an array and the node is a list literal => recurse
inner_arr: int = 0
if self.Gen.ExprGenBase.is_array_type(elem_ty) != 0:
# Null check for nested element: use is not None (not integer 0)
if elem_node is not None and elem_node.kind() == ast.ASTKind.List: inner_arr = 1
if inner_arr != 0:
self._init_global_array_from_list(builder, pool, mod, elem_ptr, elem_node, trans, owner_ctx)
continue
elem_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, elem_node, None, 0, trans)
if elem_val is None:
# Element translation failed: fail fast to expose the upstream cause
self.Gen.TypeGenErr.fatal_error(elem_node, "全局数组初始化元素翻译失败", "TypeError")
# Type coercion (e.g. i32 -> i8); Pointee is statically i64 so compare with != 0
store_val: mlirlite.Value* = elem_val
if elem_ptr.Ty is not None and elem_ptr.Ty.Pointee != 0: store_val = self.Gen.ExprGenBase.coerce_to_type(builder, elem_val, elem_ptr.Ty.Pointee, elem_node)
mlirlite.build_store(builder, store_val, elem_ptr)
return 0
# _apply_aug_op - apply the binary op of an augmented assignment (float, pointer, integer)
def _apply_aug_op(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, op: int, lhs: mlirlite.Value*, rhs: mlirlite.Value*, node: ast.AST* = None, trans: HT.Translator* = None, lhs_node: ast.AST* = None) -> mlirlite.Value*:
"""Apply the binary op of an augmented assignment (float/pointer/int)."""
# Power aug-assign: base types use llvm.pow via ExprGenOps.emit_pow
if op == ast.OpKind.Pow:
if trans is None: return None
return self.Gen.ExprGenOps.emit_pow(pool, builder, trans.Module, lhs, rhs, node, trans)
lhs_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(lhs.Ty)
rhs_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(rhs.Ty)
lhs_fbits: int = self.Gen.ExprGenBase.get_llvm_float_bits(lhs.Ty)
rhs_fbits: int = self.Gen.ExprGenBase.get_llvm_float_bits(rhs.Ty)
# Float op when either operand is float (check before pointer arithmetic)
if lhs_fbits != 0 or rhs_fbits != 0:
# Pick the target float type (the wider one)
target_fbits: int = lhs_fbits
if rhs_fbits > target_fbits: target_fbits = rhs_fbits
target_float_ty: mlirlite.LLVMType* = mlirlite.Double(pool)
if target_fbits == 32: target_float_ty = mlirlite.Float(pool)
# Coerce both operands to the target float type
lhs = self.Gen.ExprGenBase.coerce_to_type(builder, lhs, target_float_ty, node)
rhs = self.Gen.ExprGenBase.coerce_to_type(builder, rhs, target_float_ty, node)
if op == ast.OpKind.Add: return mlirlite.build_fadd(builder, lhs, rhs)
if op == ast.OpKind.Sub: return mlirlite.build_fsub(builder, lhs, rhs)
if op == ast.OpKind.Mult: return mlirlite.build_fmul(builder, lhs, rhs)
if op == ast.OpKind.Div: return mlirlite.build_fdiv(builder, lhs, rhs)
if op == ast.OpKind.Mod: return mlirlite.build_frem(builder, lhs, rhs)
return None
# Pointer arithmetic: ptr += int / ptr -= int -> getelementptr (scaled by element size)
if lhs_bits == 0 and rhs_bits != 0:
if op == ast.OpKind.Add or op == ast.OpKind.Sub: return self.Gen.ExprGenBase.translate_ptr_arith(pool, builder, lhs, rhs, op)
return None
# Double-pointer arithmetic: delegate the check to ExprGenOps.fail_if_double_ptr_binop
self.Gen.ExprGenOps.fail_if_double_ptr_binop(pool, op, lhs, rhs, node, 1)
# Integer op: type promotion
if lhs_bits > rhs_bits and rhs_bits > 0: rhs = self.Gen.ExprGenBase.coerce_to_type(builder, rhs, lhs.Ty, node, trans)
elif rhs_bits > lhs_bits and lhs_bits > 0: lhs = self.Gen.ExprGenBase.coerce_to_type(builder, lhs, rhs.Ty, node, trans)
# Unsigned operands use udiv/urem; signed use sdiv/srem
aug_unsigned: int = 0
if lhs_node is not None:
if self.Gen.ExprGenOps._node_type_unsigned(pool, lhs_node, trans) != 0: aug_unsigned = 1
if op == ast.OpKind.Add: return mlirlite.build_add(builder, lhs, rhs)
elif op == ast.OpKind.Sub: return mlirlite.build_sub(builder, lhs, rhs)
elif op == ast.OpKind.Mult: return mlirlite.build_mul(builder, lhs, rhs)
elif op == ast.OpKind.Div:
if aug_unsigned != 0: return mlirlite.build_udiv(builder, lhs, rhs)
return mlirlite.build_sdiv(builder, lhs, rhs)
elif op == ast.OpKind.FloorDiv:
if aug_unsigned != 0: return mlirlite.build_udiv(builder, lhs, rhs)
return mlirlite.build_sdiv(builder, lhs, rhs)
elif op == ast.OpKind.Mod:
if aug_unsigned != 0: return mlirlite.build_urem(builder, lhs, rhs)
return mlirlite.build_srem(builder, lhs, rhs)
elif op == ast.OpKind.BitAnd: return mlirlite.build_and(builder, lhs, rhs)
elif op == ast.OpKind.BitOr: return mlirlite.build_or(builder, lhs, rhs)
elif op == ast.OpKind.BitXor: return mlirlite.build_xor(builder, lhs, rhs)
elif op == ast.OpKind.LShift: return mlirlite.build_shl(builder, lhs, rhs)
elif op == ast.OpKind.RShift:
# Unsigned >> uses lshr, signed >> uses ashr (i8/i16 default unsigned)
if lhs_bits == 8 or lhs_bits == 16: return mlirlite.build_lshr(builder, lhs, rhs)
if lhs_node is not None and trans is not None:
if self.Gen.ExprGenOps._node_type_unsigned(pool, lhs_node, trans) != 0: return mlirlite.build_lshr(builder, lhs, rhs)
return mlirlite.build_ashr(builder, lhs, rhs)
return None
# translate_augassign - translate augmented assignment statements
def const_assign_msg(self, kind: str, name: str, src_name: str) -> str:
"""Build the const-assignment error message used by const_assign_fail."""
decl: str = "(注解含 t.CConst,声明处来源名未记录)"
if src_name is not None: decl = f"t.CConst | {src_name}"
return GenApi.escape_fstring_copy(f"不能对 const 变量 '{name}' {kind}" f"(声明处类型(含 const):{decl}" f";修法:删掉该声明里的 const 描述符 / t.CConst,或改为声明时一次赋值、" f"之后只用非 const 的副本/中间量;const 对象在其作用域内禁止赋值、" f"增量赋值与元素赋值,且不会降级为警告)")
def const_assign_fail(self, node: ast.AST*, kind: str, name: str, src_name: str) -> int:
"""Fail fast when a const target is written."""
self.Gen.TypeGenErr.fatal_error(node, self.const_assign_msg(kind, name, src_name), "TypeError")
return 0
def translate_augassign(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate an augmented assignment statement (x += 1)."""
if node is None: return 0
aug: ast.AugAssign* = ast.AugAssign*(node)
if aug is None: return 0
target: ast.AST* = aug.target
if target is None: return 0
tk: int = target.kind()
# Attribute target: self.field += 1 (get_attribute_ptr -> load -> binop -> store)
if tk == ast.ASTKind.Attribute:
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
# const field: fail fast on the field write
at_ca: ast.Attribute* = ast.Attribute*(target)
if at_ca is not None and at_ca.attr is not None:
if self.Gen.ExprGenAttr.attr_field_is_const(pool, trans, target) != 0:
self.const_assign_fail(target, "字段赋值", at_ca.attr, self.Gen.ExprGenAttr.attr_field_decl_src(pool, trans, target))
# 1. Get the field pointer
field_ptr: mlirlite.Value* = self.Gen.ExprGenAttr.get_attribute_ptr(builder, pool, mod, target, trans)
if field_ptr is None:
# fail-fast: attribute pointer resolution failed
self.Gen.TypeGenErr.fatal_error(target, "attribute ptr is None", "TypeError")
# 2. Determine the field type and load the current value
target_ty: mlirlite.LLVMType* = None
if field_ptr.Ty is not None and field_ptr.Ty.Pointee != 0: target_ty = field_ptr.Ty.Pointee
if target_ty is None:
# fail-fast: field type missing, no i32 fallback
self.Gen.TypeGenErr.fatal_error(target, "字段类型信息缺失", "TypeError")
# t.CVolatile field: both load and store carry the volatile qualifier
vol_fa: int = self.Gen.ExprGenAttr.attr_field_is_volatile(pool, trans, target)
# rawptr[t.CVolatile|T] base: field load/store also carry volatile
at_aug: ast.Attribute* = ast.Attribute*(target)
if at_aug is not None:
if self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, at_aug.value) != 0: vol_fa = 1
cur_val: mlirlite.Value* = mlirlite.build_load(builder, target_ty, field_ptr, vol_fa)
if cur_val is None:
# fail-fast: attribute current value load failed
self.Gen.TypeGenErr.fatal_error(target, "无法加载 attribute 当前值", "TypeError")
# 3. Translate the RHS value
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aug.value, None, 0, trans)
if rhs_val is None:
# fail-fast: RHS translation failed
self.Gen.TypeGenErr.fatal_error(target, "rhs 翻译失败", "TypeError")
# 4. Apply the binary op
result: mlirlite.Value* = self._apply_aug_op(pool, builder, aug.op, cur_val, rhs_val, node, trans, target)
if result is None:
# fail-fast: attribute binary op failed
self.Gen.TypeGenErr.fatal_error(target, f"attribute 二元运算失败 op={aug.op}", "TypeError")
# 5. Coerce the type and store
result = self.Gen.ExprGenBase.coerce_to_type(builder, result, target_ty, node)
if result is None:
# fail-fast: type coercion failed
self.Gen.TypeGenErr.fatal_error(target, "类型对齐失败", "TypeError")
mlirlite.build_store(builder, result, field_ptr, vol_fa)
return 0
# Subscript target: self.state[i] += x (get_subscript_ptr -> load -> binop -> store)
if tk == ast.ASTKind.Subscript:
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
# const element target: fail fast when the base variable or field is const
sub_ca: ast.Subscript* = ast.Subscript*(target)
if sub_ca is not None and sub_ca.value is not None:
if sub_ca.value.kind() == ast.ASTKind.Name:
nm_ca: ast.Name* = ast.Name*(sub_ca.value)
if nm_ca.id is not None:
ve_ca: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_ca.id)
if self.Gen.VarGen.var_is_const(ve_ca) != 0:
self.const_assign_fail(target, "的元素赋值", nm_ca.id, self.Gen.VarGen.var_annot_class_name(ve_ca))
elif sub_ca.value.kind() == ast.ASTKind.Attribute:
at_ca2: ast.Attribute* = ast.Attribute*(sub_ca.value)
if at_ca2.attr is not None:
if self.Gen.ExprGenAttr.attr_field_is_const(pool, trans, sub_ca.value) != 0:
self.const_assign_fail(target, "的元素赋值", at_ca2.attr, self.Gen.ExprGenAttr.attr_field_decl_src(pool, trans, sub_ca.value))
# 1. Get the element pointer
elem_ptr: mlirlite.Value* = self.Gen.ExprGenName.get_subscript_ptr(builder, pool, mod, target, trans)
if elem_ptr is None:
# fail-fast: subscript pointer resolution failed
self.Gen.TypeGenErr.fatal_error(target, "subscript ptr is None", "TypeError")
# 2. Determine the element type and load the current value
target_ty: mlirlite.LLVMType* = None
if elem_ptr.Ty is not None and elem_ptr.Ty.Pointee != 0: target_ty = elem_ptr.Ty.Pointee
if target_ty is None:
# fail-fast: element type missing, no i32 fallback
self.Gen.TypeGenErr.fatal_error(target, "元素类型信息缺失", "TypeError")
# rawptr[t.CVolatile|T] base: p[i] load/store carry volatile when the base is resolvable
vol_sa: int = 0
sub_aug: ast.Subscript* = ast.Subscript*(target)
if sub_aug is not None: vol_sa = self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, sub_aug.value)
cur_val: mlirlite.Value* = mlirlite.build_load(builder, target_ty, elem_ptr, vol_sa)
if cur_val is None:
# fail-fast: element current value load failed
self.Gen.TypeGenErr.fatal_error(target, "无法加载 subscript 元素当前值", "TypeError")
# 3. Translate the RHS value
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aug.value, None, 0, trans)
if rhs_val is None:
# fail-fast: RHS translation failed
self.Gen.TypeGenErr.fatal_error(target, "rhs 翻译失败", "TypeError")
# 4. Apply the binary op
result: mlirlite.Value* = self._apply_aug_op(pool, builder, aug.op, cur_val, rhs_val, node, trans, target)
if result is None:
# fail-fast: subscript binary op failed
self.Gen.TypeGenErr.fatal_error(target, f"subscript 二元运算失败 op={aug.op}", "TypeError")
# 5. Coerce the type and store
result = self.Gen.ExprGenBase.coerce_to_type(builder, result, target_ty, node)
if result is None:
# fail-fast: type coercion failed
self.Gen.TypeGenErr.fatal_error(target, "类型对齐失败", "TypeError")
mlirlite.build_store(builder, result, elem_ptr, vol_sa)
return 0
# UnaryOp target: *p += v (deref augmented assignment: load, op, store back)
if tk == ast.ASTKind.UnaryOp:
uo_ag: ast.UnaryOp* = ast.UnaryOp*(target)
if uo_ag is None or uo_ag.operand is None: self.Gen.TypeGenErr.fatal_error(target, "增量赋值目标单目运算缺少操作数", "TypeError")
if uo_ag.op != ast.OpKind.Deref:
self.Gen.TypeGenErr.fatal_error(target, "增量赋值目标只支持单目 '*' 解引用(&x / -x / ~x 不是合法左值)", "SyntaxError")
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
ptr_ag: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, uo_ag.operand, None, 0, trans)
if ptr_ag is None or ptr_ag.Ty is None or self.Gen.ExprGenBase.is_ptr_type(ptr_ag.Ty) == 0:
self.Gen.TypeGenErr.fatal_error(target, "增量赋值 '*' 的操作数必须是指针类型(类型推断退化)", "TypeError")
ty_ag: mlirlite.LLVMType* = ptr_ag.Ty.Pointee
if ty_ag is None:
self.Gen.TypeGenErr.fatal_error(target, "解引用增量赋值的目标类型缺失(pointee 为空)", "TypeError")
old_ag: mlirlite.Value* = mlirlite.build_load(builder, ty_ag, ptr_ag, 0)
if old_ag is None: self.Gen.TypeGenErr.fatal_error(target, "无法加载解引用当前值", "TypeError")
rhs_ag: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aug.value, None, 0, trans)
if rhs_ag is None: self.Gen.TypeGenErr.fatal_error(target, "rhs 翻译失败", "TypeError")
res_ag: mlirlite.Value* = self._apply_aug_op(pool, builder, aug.op, old_ag, rhs_ag, node, trans, target)
if res_ag is None: self.Gen.TypeGenErr.fatal_error(target, f"解引用二元运算失败 op={aug.op}", "TypeError")
res_ag = self.Gen.ExprGenBase.coerce_to_type(builder, res_ag, ty_ag, node, trans)
if res_ag is None: self.Gen.TypeGenErr.fatal_error(target, "类型对齐失败", "TypeError")
mlirlite.build_store(builder, res_ag, ptr_ag, 0)
return 0
if tk != ast.ASTKind.Name:
# fail-fast: unsupported AugAssign target kind
self.Gen.TypeGenErr.fatal_error(target, "AugAssign 目标类型不支持,仅支持 Name/Attribute/Subscript/*解引用", "TypeError")
nm: ast.Name* = ast.Name*(target)
if nm is None or nm.id is None: return 0
# const variable: fail fast immediately
ve_aug: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
if self.Gen.VarGen.var_is_const(ve_aug) != 0:
self.const_assign_fail(target, "增量赋值", nm.id, self.Gen.VarGen.var_annot_class_name(ve_aug))
# REPL session variable aug-assign: read slot, apply op, write slot and result area
if trans.ReplCtx is not None and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0:
if self.Gen.VarGen.repl_try_augassign(trans, nm.id, aug.op, aug.value, node) != 0: return 0
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
# 1. Determine scope: 0=local, 1=global, 2=nonlocal
scope_type: int = 0
if GenApi.is_global_name(trans, nm.id) != 0: scope_type = 1
elif GenApi.is_nonlocal_name(trans, nm.id) != 0: scope_type = 2
# t.CVolatile variable: both load and store carry the volatile qualifier
vol_aa: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
# 2. Load the current value
cur_val: mlirlite.Value* = None
target_alloca: mlirlite.Value* = None
target_ty: mlirlite.LLVMType* = None
if scope_type == 1:
# global variable
target_alloca = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id)
if target_alloca is not None:
if target_alloca.Ty is None:
# fail-fast: variable type missing, no i32 fallback
self.Gen.TypeGenErr.fatal_error(target, "变量类型信息缺失", "TypeError")
target_ty = target_alloca.Ty.Pointee
cur_val = mlirlite.build_load(builder, target_ty, target_alloca, vol_aa)
elif scope_type == 2:
# nonlocal variable (via closure env)
cur_val = self.Gen.StmtGenDecl.load_nonlocal_var(trans, nm.id)
if cur_val is not None: target_ty = cur_val.Ty
else:
# Ordinary local variable
target_alloca = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if target_alloca is not None:
if target_alloca.Ty is None:
# fail-fast: variable type missing, no i32 fallback
self.Gen.TypeGenErr.fatal_error(target, "变量类型信息缺失", "TypeError")
target_ty = target_alloca.Ty.Pointee
cur_val = mlirlite.build_load(builder, target_ty, target_alloca, vol_aa)
if cur_val is None:
# fail-fast: target variable current value load failed
self.Gen.TypeGenErr.fatal_error(target, f"无法加载目标变量 '{nm.id}' 的当前值", "TypeError")
# 3. Translate the RHS value
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aug.value, None, 0, trans)
if rhs_val is None:
# fail-fast: RHS translation failed
self.Gen.TypeGenErr.fatal_error(target, "rhs 翻译失败", "TypeError")
# 3b. Operator overload: object += -> __iadd__, fall back to __add__ (locals only)
if scope_type == 0 and target_alloca is not None:
aug_dup: str = self._aug_op_to_dunder(aug.op)
if aug_dup is not None:
ovl_self_aug: mlirlite.Value* = target_alloca
# Pointer storage slot (Ptr(Ptr(Struct))): load the object pointer as self
if target_alloca.Ty is not None and target_alloca.Ty.Pointee != 0 and self.Gen.ExprGenBase.is_ptr_type(target_alloca.Ty.Pointee) != 0:
load_self_aug: mlirlite.Value* = mlirlite.build_load(builder, target_alloca.Ty.Pointee, target_alloca)
if load_self_aug is not None: ovl_self_aug = load_self_aug
# Try the aug dunder only when the target is a struct (class instance)
if self.Gen.StructGen.get_struct_type_from_value(ovl_self_aug) is not None:
aug_extra: sizet* = pool.alloc(8)
if aug_extra is not None:
aug_extra[0] = sizet(rhs_val)
aug_ovl: mlirlite.Value* = self.Gen.ExprGenOps._try_named_dunder(pool, builder, mod, ovl_self_aug, aug_dup, trans, aug_extra, 1)
if aug_ovl is None:
aug_fb: str = self._op_to_dunder(aug.op)
if aug_fb is not None: aug_ovl = self.Gen.ExprGenOps._try_named_dunder(pool, builder, mod, ovl_self_aug, aug_fb, trans, aug_extra, 1)
if aug_ovl is not None:
aug_res: mlirlite.Value* = aug_ovl
if target_ty is not None and aug_res.Ty is not None and self.Gen.ExprGenBase._same_agg_ty(pool, aug_res.Ty, target_ty) == 0:
# Target is a struct value but the result is a pointer -> load the struct value
if self.Gen.ExprGenBase.is_ptr_type(target_ty) == 0 and self.Gen.ExprGenBase.is_ptr_type(aug_res.Ty) != 0 and aug_res.Ty.Pointee != 0:
aug_res = mlirlite.build_load(builder, aug_res.Ty.Pointee, aug_res)
else:
coerced_aug: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, aug_res, target_ty, node, trans)
if coerced_aug is not None: aug_res = coerced_aug
if aug_res is None or target_ty is None or self.Gen.ExprGenBase._same_agg_ty(pool, aug_res.Ty, target_ty) == 0:
# fail-fast: overload return type mismatch, do not corrupt memory silently
self.Gen.TypeGenErr.fatal_error(node, "增量赋值运算符重载返回值类型与目标变量不匹配", "TypeError")
mlirlite.build_store(builder, aug_res, target_alloca, vol_aa)
return 0
# 4. Coerce the type and apply the binary op
result: mlirlite.Value* = self._apply_aug_op(pool, builder, aug.op, cur_val, rhs_val, node, trans, target)
if result is None:
# fail-fast: binary op failed
self.Gen.TypeGenErr.fatal_error(target, f"对变量 '{nm.id}' 应用二元运算失败 op={aug.op}", "TypeError")
# 5. Store the result
result = self.Gen.ExprGenBase.coerce_to_type(builder, result, target_ty, node)
if result is None:
# fail-fast: type coercion failed
self.Gen.TypeGenErr.fatal_error(target, "类型对齐失败", "TypeError")
if scope_type == 1:
# global variable
if target_alloca is not None: mlirlite.build_store(builder, result, target_alloca, vol_aa)
elif scope_type == 2:
# nonlocal variable
nl_ptr: mlirlite.Value* = self.Gen.StmtGenDecl.get_nonlocal_var_ptr(trans, nm.id)
if nl_ptr is not None: mlirlite.build_store(builder, result, nl_ptr)
else:
# Ordinary local variable
if target_alloca is not None: mlirlite.build_store(builder, result, target_alloca, vol_aa)
return 0
# is_ctypedef_annotation - check whether an annotation is t.CTypedef
def is_ctypedef_annotation(self, annot: ast.AST*) -> int:
"""Return 1 if the annotation is t.CTypedef, else 0."""
return self.Gen.TypeGenCType._is_ctypedef_annotation(annot)
# _is_single_param_carray_annot - check for single-param t.CArray[elem_ty]
def _is_single_param_carray_annot(self, annot: ast.AST*) -> int:
"""Return 1 for a single-param or None-length t.CArray annotation."""
if annot is None or annot.kind() != ast.ASTKind.Subscript: return 0
sub: ast.Subscript* = ast.Subscript*(annot)
if sub.value is None or sub.slice is None: return 0
if sub.value.kind() == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(sub.value)
if at.attr is None or string.strcmp(at.attr, "CArray") != 0: return 0
if at.value is None or at.value.kind() != ast.ASTKind.Name: return 0
mv: ast.Name* = ast.Name*(at.value)
if mv.id is None or string.strcmp(mv.id, "t") != 0: return 0
elif sub.value.kind() == ast.ASTKind.Name:
# Bare CArray / builtin rawarray / builtin array are all equivalent to t.CArray
bn: ast.Name* = ast.Name*(sub.value)
if bn.id is None or (string.strcmp(bn.id, "CArray") != 0 and string.strcmp(bn.id, "rawarray") != 0 and string.strcmp(bn.id, "array") != 0): return 0
else: return 0
# Single-param form: slice is not a Tuple (e.g. t.CArray[u64])
if sub.slice.kind() != ast.ASTKind.Tuple: return 1
# Two-param form with count=None is equivalent to single-param
tup: ast.Tuple* = ast.Tuple*(sub.slice)
if tup.elts is None or len(tup.elts) < 2: return 0
cnt_node: ast.AST* = tup.elts.get(1)
if cnt_node is not None and cnt_node.kind() == ast.ASTKind.Constant:
cn0: ast.Constant* = ast.Constant*(cnt_node)
if cn0.const_kind == ast.CONST_NONE: return 1
return 0
# _infer_static_array_ty_from_literal - single-param CArray + list literal -> [len x elem_ty]
def _infer_static_array_ty_from_literal(self, pool: memhub.MemBuddy*, annot: ast.AST*, value: ast.AST*, resolved_ty: mlirlite.LLVMType*) -> mlirlite.LLVMType*:
"""Return Array(elem_ty, len) for a single-param CArray with a list literal."""
if value is not None and value.kind() == ast.ASTKind.List and resolved_ty is not None:
if self._is_single_param_carray_annot(annot) != 0:
elem_ty_arr: mlirlite.LLVMType* = resolved_ty.Pointee
lst_arr: ast.List* = ast.List*(value)
cnt_arr: int = 0
if lst_arr.elts is not None: cnt_arr = len(lst_arr.elts)
# Special case: a single string literal means length strlen+1 (include the NUL)
if elem_ty_arr is not None and cnt_arr == 1 and lst_arr.elts is not None:
e0_arr: ast.AST* = lst_arr.elts.get(0)
if e0_arr is not None and e0_arr.kind() == ast.ASTKind.Constant:
c0_arr: ast.Constant* = ast.Constant*(e0_arr)
if c0_arr.const_kind == ast.CONST_STR:
sv0_arr: str = ast.constant_get_str_val(c0_arr)
if sv0_arr is not None and self.Gen.ExprGenBase.get_llvm_type_bits(elem_ty_arr) == 8: return mlirlite.Array(pool, elem_ty_arr, int(string.strlen(sv0_arr)) + 1)
if elem_ty_arr is not None and cnt_arr > 0: return mlirlite.Array(pool, elem_ty_arr, cnt_arr)
return resolved_ty
# _build_ann_fail_msg - build a detailed annotation-resolution-failed error
def _build_ann_fail_msg(self, pool: memhub.MemBuddy*, annot: ast.AST*, nm_id: str) -> str:
"""Build a detailed annotation-resolution-failure message (heap buffer)."""
ann_txt_x: str = None
if annot is not None: ann_txt_x = self.Gen.TypeGenResolve._get_type_name_from_annotation(pool, annot)
if ann_txt_x is None: ann_txt_x = "?"
btd_nm_x: str = self.Gen.TypeGenCType.LastBuiltinsTypedefName
btd_rs_x: str = self.Gen.TypeGenCType.LastBuiltinsTypedefResult
msg_len: sizet = 640
msg_buf: str = stdlib.malloc(msg_len)
if msg_buf is None: return ann_txt_x
if btd_nm_x is not None and btd_rs_x is not None: viperlib.snprintf(msg_buf, msg_len, "变量类型注解解析失败,变量名: %s,注解: %s。builtins 最后匹配: %s 展开为 %s,仍无法解析", nm_id, ann_txt_x, btd_nm_x, btd_rs_x)
elif btd_nm_x is not None: viperlib.snprintf(msg_buf, msg_len, "变量类型注解解析失败,变量名: %s,注解: %s。builtins 最后匹配: %s 未命中(builtins.vp 无该 typedef 或扫描未运行)", nm_id, ann_txt_x, btd_nm_x)
else: viperlib.snprintf(msg_buf, msg_len, "变量类型注解解析失败,变量名: %s,注解: %s。builtins 最后匹配: 未触发", nm_id, ann_txt_x)
return msg_buf
# translate_annassign_prescan - pre-create alloca for annotated vars
def translate_annassign_prescan(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Pre-create allocas for AnnAssign variables at the entry block."""
if node is None: return 0
k: int = node.kind()
if k != ast.ASTKind.AnnAssign: return 0
aa: ast.AnnAssign* = ast.AnnAssign*(node)
if aa is None or aa.target is None: return 0
target: ast.AST* = aa.target
if target.kind() != ast.ASTKind.Name: return 0
nm: ast.Name* = ast.Name*(target)
if nm.id is None: return 0
# CDefine annotation: register the constant instead of creating an alloca
if self.is_cdefine_annotation(aa.annotation) != 0:
pool_ps: memhub.MemBuddy* = trans.Pool
self._register_cdefine_from_value(pool_ps, nm.id, aa.value)
return 0
# CTypedef annotation: register the alias only, no storage (avoids duplicate symbols)
if self.is_ctypedef_annotation(aa.annotation) != 0: return 0
# REPL fragment entry: AnnAssign goes to the session slot; skip pre-creating a local alloca
if trans.ReplCtx is not None and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0: return 0
# global/nonlocal variables need no local alloca
if GenApi.is_global_name(trans, nm.id) != 0: return 0
if GenApi.is_nonlocal_name(trans, nm.id) != 0: return 0
# Module-level globals are already registered; only skip the local alloca at module scope
if self.Gen.VarGen.is_module_scope(trans.SymTab) != 0:
if self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id) is not None: return 0
# Check whether it already exists
existing: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, nm.id)
if existing is not None: return 0
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
# Infer the type from the annotation (no hard-coded i32)
var_ty: mlirlite.LLVMType* = None
annot: ast.AST* = aa.annotation
if annot is not None:
# Specialize generics before resolving so list[X] does not degrade to opaque list*
self._prespecialize_generic_annotation(trans, pool, annot)
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, annot, trans._imported_modules, trans._from_imports, trans, 0)
if resolved is not None:
var_ty = resolved
# Single-param CArray + list literal -> static array; keep PreScan and Handle types in sync
var_ty = self._infer_static_array_ty_from_literal(pool, annot, aa.value, var_ty)
# [D31 fail-fast] generic annotation degraded to a bare base class
self._fail_on_degenerated_generic_annotation(aa, nm.id, annot, var_ty)
else:
# fail-fast: annotation resolution failed (no silent i32 fallback)
self.Gen.TypeGenErr.fatal_error(aa, self._build_ann_fail_msg(pool, annot, nm.id), "TypeError")
else:
# fail-fast: AnnAssign must have an annotation
self.Gen.TypeGenErr.fatal_error(aa, f"AnnAssign 缺少类型注解,变量名: {nm.id}", "SyntaxError")
# Ensure the full cross-module struct definition is available (for alloca)
self.Gen.StructGen.ensure_struct_def_in_module(pool, trans.Module, var_ty)
alloca: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, var_ty)
if alloca is not None:
if self.Gen.VarGen.define_var(trans.SymTab, nm.id, alloca) == 0:
# Store the annotation's class name; pass pool so generic Subscript yields the mangled name
if annot is not None:
cls_nm_ps: str = self.Gen.TypeGenResolve.extract_class_name_from_annotation(annot, trans._imported_modules, pool, trans._from_imports)
if cls_nm_ps is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, nm.id, cls_nm_ps)
# Check for bytes* or str* (i8** semantics, 8-byte stride)
if self._is_ptr_element_annotation(annot) != 0: self.Gen.VarGen.set_var_ptr_element(trans.SymTab, nm.id)
# const object (annotation has t.CConst and the type is not a pointer): no writes in scope
if self.Gen.TypeGenErr.is_const_object_marker(annot, var_ty) != 0: self.Gen.VarGen.set_var_const(trans.SymTab, nm.id)
# t.CVolatile: mark the variable at declaration; its load/store carry volatile
if self.Gen.TypeGenErr.has_decorator_marker(annot, "CVolatile", 0) != 0: self.Gen.VarGen.set_var_volatile(trans.SymTab, nm.id)
# rawptr[t.CVolatile | T]: dereference load/store carry volatile
if self.Gen.TypeGenErr.has_pointee_volatile_marker(annot) != 0: self.Gen.VarGen.set_var_pointee_volatile(trans.SymTab, nm.id)
return 0
return 0
# _prespecialize_generic_annotation - register the generic specialization before resolving
def _prespecialize_generic_annotation(self, trans: HT.Translator*, pool: memhub.MemBuddy*, annot: ast.AST*) -> int:
"""Pre-specialize a generic annotation; return 1 if attempted."""
if annot is None or trans is None: return 0
# Take the generic Subscript side: the Subscript itself or the Subscript of A*
spec_annot: ast.Subscript* = None
if annot.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(annot)
elif annot.kind() == ast.ASTKind.BinOp:
bop_ps: ast.BinOp* = ast.BinOp*(annot)
if bop_ps.op == ast.OpKind.BitOr:
if bop_ps.left is not None and bop_ps.left.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(bop_ps.left)
elif bop_ps.right is not None and bop_ps.right.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(bop_ps.right)
if spec_annot is None: return 0
sv_ps: ast.AST* = spec_annot.value
if sv_ps is None or sv_ps.kind() != ast.ASTKind.Name: return 0
sub_nm: ast.Name* = ast.Name*(sv_ps)
if sub_nm.id is None: return 0
template_cd: ast.ClassDef* = self.Gen.ClassGenGeneric._find_generic_template(sub_nm.id)
if template_cd is None: return 0
type_args_ps: list[str]* = self.Gen.CallGenCtor._extract_type_args_from_slice(pool, spec_annot.slice, trans._from_imports)
if type_args_ps is None or len(type_args_ps) == 0: return 0
self.Gen.ClassGenGeneric._specialize_generic_class(trans, sub_nm.id, type_args_ps)
return 1
# translate_annassign - translate an AnnAssign statement; returns new variable count
def _fail_on_degenerated_generic_annotation(self, aa: ast.AnnAssign*, var_name: str, annot: ast.AST*, var_ty: mlirlite.LLVMType*) -> int:
"""Fail fast when a generic annotation degrades to a bare base pointer."""
if aa is None or annot is None or var_ty is None: return 0
# Take the generic Subscript side: the Subscript itself or the Subscript of A*
ann_sub: ast.Subscript* = None
if annot.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(annot)
elif annot.kind() == ast.ASTKind.BinOp:
bop_ff: ast.BinOp* = ast.BinOp*(annot)
if bop_ff.op == ast.OpKind.BitOr:
if bop_ff.left is not None and bop_ff.left.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(bop_ff.left)
elif bop_ff.right is not None and bop_ff.right.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(bop_ff.right)
if ann_sub is None or ann_sub.value is None or ann_sub.value.kind() != ast.ASTKind.Name: return 0
base_nm_ff: ast.Name* = ast.Name*(ann_sub.value)
if base_nm_ff.id is None: return 0
# Only treat it as degraded when the base is a real generic template
if self.Gen.ClassGenGeneric._find_generic_template(base_nm_ff.id) is None: return 0
if var_ty.Pointee == 0: return 0
vt_nm_ff: str = mlirlite.get_struct_name(var_ty.Pointee)
if vt_nm_ff is None: return 0
vt_pf_ff: int = GenApi.sha1_prefix_len(vt_nm_ff)
vt_short_ff: str = vt_nm_ff + vt_pf_ff if vt_pf_ff != 0 else vt_nm_ff
if string.strchr(vt_short_ff, '[') is not None:
return 0 # With type args: not degraded
self.Gen.TypeGenErr.fatal_error(aa, f"变量 '{var_name}' 的泛型注解 '{base_nm_ff.id}[...]' 解析成了裸基类 " f"'{vt_nm_ff}*'(丢失类型实参);后续 .get()/.set() 会按错误元素类型" f"特化(如 list[int].get 返回 i32)→ inttoptr 截断指针崩溃。" f"禁止静默继续——请检查该注解在 resolve_annotation_type 中的退化点。", "TypeError")
return 0
def translate_annassign(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate an AnnAssign statement; return the new variable count."""
aa: ast.AnnAssign* = ast.AnnAssign*(node)
if aa is None: return 0
target: ast.AST* = aa.target
if target is None or target.kind() != ast.ASTKind.Name: return 0
nm: ast.Name* = ast.Name*(target)
if nm.id is None: return 0
# CDefine annotation: compile-time constant, no runtime code; register it (idempotent)
if self.is_cdefine_annotation(aa.annotation) != 0:
pool_cd: memhub.MemBuddy* = trans.Pool
self._register_cdefine_from_value(pool_cd, nm.id, aa.value, aa.annotation)
return 0
# CTypedef annotation: register the alias on demand; no storage (avoids duplicate symbols)
if self.is_ctypedef_annotation(aa.annotation) != 0:
pool_ct: memhub.MemBuddy* = trans.Pool
ctd_str: str = self.Gen.TypeGenResolve._get_type_name_from_annotation(pool_ct, aa.value)
if ctd_str is not None: self.Gen.TypeGenCType.register_ctypedef(pool_ct, nm.id, ctd_str)
return 0
# Use-site width check for define/CDefine constants (a: T = C): T must be >= W_min
self.require_cdefine_use_width(trans.Pool, trans, aa.annotation, aa.value)
# REPL session variable: annotated declaration at fragment entry goes to the session slot
if trans.ReplCtx is not None and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0:
rp_e: GenTypes.ReplVarEntry* = self.Gen.VarGen.repl_ensure_entry(trans, nm.id, aa)
if rp_e is not None:
self.Gen.VarGen.repl_try_annassign(trans, nm.id, aa.annotation, aa.value, aa)
return 0
# Duplicate-declaration check (fail fast): re-annotating a module-level name inside a function.
# Legal alternatives: a global declaration plus unannotated assignment, or a differently named local.
if nm.id is not None and trans is not None and self.Gen.VarGen.is_module_scope(trans.SymTab) == 0 and self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id) is not None:
dup_decl: int = 0
if GenApi.is_global_name(trans, nm.id) != 0 or GenApi.is_nonlocal_name(trans, nm.id) != 0: dup_decl = 1
if dup_decl != 0:
self.Gen.TypeGenErr.fatal_error(aa, f"重复声明:'{nm.id}' 已是模块级变量,此处声明 global/nonlocal 之后" f"又用类型注解重新声明。请删除本行的类型注解,直接赋值即可修改全局", "TypeError")
else:
self.Gen.TypeGenErr.fatal_error(aa, f"重复声明:'{nm.id}' 已是模块级变量,函数内未声明 global 时不得再用" f"类型注解声明同名变量(会把全局遮蔽为局部)。如需独立局部变量请换名;" f"如需修改全局请在函数内加 global 声明并删除本行注解", "TypeError")
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
# Determine the type (inferred from the annotation)
var_ty: mlirlite.LLVMType* = None
annot: ast.AST* = aa.annotation
if annot is not None:
# Same as PreScan: specialize generics first so list[X] does not degrade to bare list*
self._prespecialize_generic_annotation(trans, pool, annot)
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, annot, trans._imported_modules, trans._from_imports, trans, 0)
if resolved is not None: var_ty = resolved
else:
# fail-fast: annotation resolution failed (no silent i32 fallback)
self.Gen.TypeGenErr.fatal_error(aa, self._build_ann_fail_msg(pool, annot, nm.id), "TypeError")
else:
# fail-fast: AnnAssign must have an annotation
self.Gen.TypeGenErr.fatal_error(aa, f"AnnAssign 缺少类型注解,变量名: {nm.id}", "SyntaxError")
# void cannot be a variable type (only void* and opaque-struct typedef)
if var_ty is not None and self.Gen.StmtGenDecl._is_void_type(var_ty) != 0:
self.Gen.TypeGenErr.fatal_error(aa, f"不能声明 void 类型的变量 '{nm.id}'(void 只能用于 void* 与 typedef 别名)", "TypeError")
# t.Callable function pointer: register the return-type signature
callable_ret: mlirlite.LLVMType* = self.Gen.TypeGenErr.try_extract_callable_ret(pool, annot, trans._imported_modules, trans._from_imports, trans)
if callable_ret is not None:
# Nonzero return already reported inside register_func_ptr_sig; this branch is defensive
if self.Gen.CallGenLeafKit.register_func_ptr_sig(pool, nm.id, callable_ret, aa) != 0: return 0
# Value-position volatile call prefix: mark the result slot volatile
if aa.value is not None and aa.value.kind() == ast.ASTKind.Call and (ast.Call*(aa.value)).IsVolatile != 0 and nm.id is not None:
if self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id) is not None: self.Gen.VarGen.set_var_volatile(trans.SymTab, nm.id)
# global variable: write to the module-scope global
if GenApi.is_global_name(trans, nm.id) != 0:
mod_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id)
if mod_alloca is not None and aa.value is not None:
# P1-A context width propagation: pass the target type so integer literals materialize at target width
aa_glob_ty: mlirlite.LLVMType* = None
if mod_alloca.Ty is not None: aa_glob_ty = mod_alloca.Ty.Pointee
# Set CurAssignAnnot/CurAssignNode before translating the RHS (kept in sync across all four branches)
trans.CurAssignAnnot = aa.annotation
trans.CurAssignNode = aa.value
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aa.value, None, 0, trans, aa_glob_ty, "模块级全局")
if rhs_val is not None:
# Object global with static storage: bind storage and copy the object
stg_ref_aa: mlirlite.Value* = None
if nm.id is not None:
stg_key_aa: str = pool.alloc(sizet(string.strlen(nm.id)) + 8)
if stg_key_aa is not None:
viperlib.snprintf(stg_key_aa, sizet(string.strlen(nm.id)) + 8, "%s.__stg", nm.id)
stg_ref_aa = self.Gen.VarGen.lookup_module_var(trans.SymTab, stg_key_aa)
if self._is_addrlike_call(aa.value) == 0 and stg_ref_aa is not None and rhs_val.Ty is not None and stg_ref_aa.Ty is not None and stg_ref_aa.Ty.Pointee != 0:
stg_ty_aa: mlirlite.LLVMType* = stg_ref_aa.Ty.Pointee
rhs_inner_aa: mlirlite.LLVMType* = None
match rhs_val.Ty:
case mlirlite.LLVMType.Ptr(pointee_aa):
rhs_inner_aa = pointee_aa
case _:
pass
if rhs_inner_aa is not None and rhs_inner_aa.kind() == mlirlite.LLVMType.Struct and self._stg_ty_equal(pool, stg_ty_aa, rhs_inner_aa) != 0:
mlirlite.build_store(builder, stg_ref_aa, mod_alloca)
stg_obj_val_aa: mlirlite.Value* = mlirlite.build_load(builder, stg_ty_aa, rhs_val)
if stg_obj_val_aa is not None: mlirlite.build_store(builder, stg_obj_val_aa, stg_ref_aa)
# Clear the assignment context before returning (avoid leaking into later statements)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
target_ty: mlirlite.LLVMType* = None
if mod_alloca.Ty is not None: target_ty = mod_alloca.Ty.Pointee
if target_ty is not None:
# str/bytes is not a generic pointer: bare str/bytes annotation with an aggregate-pointer RHS
self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val, aa.annotation, node)
rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans)
# t.CVolatile global: the initializing store carries the volatile qualifier
vol_gaa: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
mlirlite.build_store(builder, rhs_val, mod_alloca, vol_gaa)
# Clear the assignment context (all four branches must stay in sync)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
# nonlocal variable: write through the closure env
if GenApi.is_nonlocal_name(trans, nm.id) != 0:
if aa.value is not None:
# P1-A: fetch the nonlocal slot pointer first so its type can be the target type
nl_ptr: mlirlite.Value* = self.Gen.StmtGenDecl.get_nonlocal_var_ptr(trans, nm.id)
# Infer the nonlocal type from nl_ptr instead of forcing i32
nl_target_ty: mlirlite.LLVMType* = None
if nl_ptr is not None and nl_ptr.Ty is not None: nl_target_ty = nl_ptr.Ty.Pointee
# Set CurAssignAnnot/CurAssignNode before translating the RHS (sync across all four branches)
trans.CurAssignAnnot = aa.annotation
trans.CurAssignNode = aa.value
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aa.value, None, 0, trans, nl_target_ty, "闭包变量(nonlocal)")
if rhs_val is not None:
if nl_ptr is not None:
if nl_target_ty is None:
# fail-fast: nl_ptr type lost, cannot store safely
VLogger.error(f"nonlocal 变量类型信息丢失,无法写入: {nm.id}", "HandlesAnnAssign")
# Clear the assignment context before returning (avoid leaking into later statements)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val, aa.annotation, node)
rhs_coerced: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, nl_target_ty, node)
mlirlite.build_store(builder, rhs_coerced, nl_ptr)
# Clear the assignment context (all four branches must stay in sync)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
# Module-level global: only module code modifies it; in a function without global, create a local
if self.Gen.VarGen.is_module_scope(trans.SymTab) != 0: mod_glob: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id)
else: mod_glob = None
if mod_glob is not None:
if aa.value is not None:
# List literal: initialize the array with per-element GEP + store
if aa.value.kind() == ast.ASTKind.List: self._init_global_array_from_list(builder, pool, mod, mod_glob, aa.value, trans)
else:
# P1-A: use the module-level global type as the target type
mg_val_ty: mlirlite.LLVMType* = None
if mod_glob.Ty is not None: mg_val_ty = mod_glob.Ty.Pointee
# Set CurAssignAnnot/CurAssignNode before translating the RHS (sync across all four branches)
trans.CurAssignAnnot = aa.annotation
trans.CurAssignNode = aa.value
rhs_val_mg: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aa.value, None, 0, trans, mg_val_ty, "模块级全局")
if rhs_val_mg is not None:
# Object global with static storage: bind storage and copy the object
stg_ref_mg: mlirlite.Value* = None
if nm.id is not None:
stg_key_mg: str = pool.alloc(sizet(string.strlen(nm.id)) + 8)
if stg_key_mg is not None:
viperlib.snprintf(stg_key_mg, sizet(string.strlen(nm.id)) + 8, "%s.__stg", nm.id)
stg_ref_mg = self.Gen.VarGen.lookup_module_var(trans.SymTab, stg_key_mg)
if self._is_addrlike_call(aa.value) == 0 and stg_ref_mg is not None and rhs_val_mg.Ty is not None and stg_ref_mg.Ty is not None and stg_ref_mg.Ty.Pointee != 0:
stg_ty_mg: mlirlite.LLVMType* = stg_ref_mg.Ty.Pointee
rhs_inner_mg: mlirlite.LLVMType* = None
match rhs_val_mg.Ty:
case mlirlite.LLVMType.Ptr(pointee_mg):
rhs_inner_mg = pointee_mg
case _:
pass
if rhs_inner_mg is not None and rhs_inner_mg.kind() == mlirlite.LLVMType.Struct and self._stg_ty_equal(pool, stg_ty_mg, rhs_inner_mg) != 0:
mlirlite.build_store(builder, stg_ref_mg, mod_glob)
stg_obj_val_mg: mlirlite.Value* = mlirlite.build_load(builder, stg_ty_mg, rhs_val_mg)
if stg_obj_val_mg is not None: mlirlite.build_store(builder, stg_obj_val_mg, stg_ref_mg)
# Clear the assignment context before returning (avoid leaking into later statements)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
target_ty_mg: mlirlite.LLVMType* = None
if mod_glob.Ty is not None: target_ty_mg = mod_glob.Ty.Pointee
if target_ty_mg is not None:
# str/bytes is not a generic pointer: bare str/bytes annotation with an aggregate-pointer RHS
self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val_mg, aa.annotation, node)
rhs_val_mg = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val_mg, target_ty_mg, node)
# t.CVolatile module-level global: the initializing store carries volatile
vol_mga: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
mlirlite.build_store(builder, rhs_val_mg, mod_glob, vol_mga)
# Clear the assignment context (all four branches must stay in sync)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
return 0
# Single-param CArray + list literal -> static array; match PreScan so the alloca is reused
var_ty = self._infer_static_array_ty_from_literal(pool, annot, aa.value, var_ty)
# [D31 fail-fast] generic annotation degraded to a bare base class
self._fail_on_degenerated_generic_annotation(aa, nm.id, annot, var_ty)
# Ordinary local variable: create the alloca
alloca: mlirlite.Value* = self.Gen.VarGen.get_or_create_sym(trans.SymTab, pool, builder, nm.id, var_ty)
if alloca is None:
# fail-fast: storage creation failed
self.Gen.TypeGenErr.fatal_error(aa, f"无法为变量 {nm.id} 创建存储", "TypeError")
# A reused alloca type must match the resolved annotation type (compare struct names)
if alloca is not None and alloca.Ty is not None and var_ty is not None:
av_pt_d: mlirlite.LLVMType* = alloca.Ty.Pointee
if av_pt_d is not None and self.Gen.ExprGenBase.is_ptr_type(av_pt_d) != 0 and self.Gen.ExprGenBase.is_ptr_type(var_ty) != 0:
av_in: mlirlite.LLVMType* = av_pt_d.Pointee
vt_in: mlirlite.LLVMType* = var_ty.Pointee
if av_in is not None and vt_in is not None:
av_nm_d: str = mlirlite.get_struct_name(av_in)
vt_nm_d: str = mlirlite.get_struct_name(vt_in)
if av_nm_d is not None and vt_nm_d is not None and string.strcmp(av_nm_d, vt_nm_d) != 0:
# Fail fast only when the reused pointee is a bare generic base (lost type args)
av_pf: int = GenApi.sha1_prefix_len(av_nm_d)
av_short: str = av_nm_d + av_pf if av_pf != 0 else av_nm_d
if string.strchr(av_short, '[') is None:
av_tpl: ast.ClassDef* = self.Gen.ClassGenGeneric._find_generic_template(av_short)
if av_tpl is not None:
self.Gen.TypeGenErr.fatal_error(aa, f"变量 '{nm.id}' 复用到的存储类型为裸泛型基类 '{av_nm_d}*'" f"(丢了类型实参),与注解解析类型 '{vt_nm_d}*' 不一致;" f"禁止静默混用——方法调用会按错误元素类型特化并截断指针", "TypeError")
# FakeDuck: a str variable gets a shadow struct {var}__meta__ for per-instance fields
fd_str_ty: mlirlite.LLVMType* = None
fd_meta_alloca: mlirlite.Value* = None
fd_meta_name: str = None
if annot is not None and annot.kind() == ast.ASTKind.Name:
fd_annot: ast.Name* = ast.Name*(annot)
if fd_annot is not None and fd_annot.id is not None:
if string.strcmp(fd_annot.id, "str") == 0: fd_str_ty = self.Gen.StructGen.get_struct_type("_str")
if fd_str_ty is not None:
# Ensure the full _str definition is available (opaque types cannot be alloca'd)
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, fd_str_ty)
# Shadow variable name must use a pool buffer (f-string stack buffers get overwritten)
fd_meta_name = pool.alloc(256)
if fd_meta_name is not None: viperlib.snprintf(fd_meta_name, 256, "%s__meta__", nm.id)
fd_meta_alloca = self.Gen.VarGen.get_or_create_sym(trans.SymTab, pool, builder, fd_meta_name, fd_str_ty)
if fd_meta_alloca is None:
# fail-fast: shadow storage creation failed
self.Gen.TypeGenErr.fatal_error(aa, f"无法为 str 影子变量 {fd_meta_name} 创建存储", "TypeError")
# Store the annotation's class name; pass pool so generic Subscript yields the mangled name
if annot is not None:
cls_nm_hd: str = self.Gen.TypeGenResolve.extract_class_name_from_annotation(annot, trans._imported_modules, pool, trans._from_imports)
if cls_nm_hd is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, nm.id, cls_nm_hd)
# Check for bytes* or str* (i8** semantics, 8-byte stride)
if self._is_ptr_element_annotation(annot) != 0: self.Gen.VarGen.set_var_ptr_element(trans.SymTab, nm.id)
new_vars: int = 0
existing: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, nm.id)
if existing is None: new_vars = 1
# If there is an initial value, store it
if aa.value is not None:
# Coroutine: record the variable name assigned from an async call (for frame-type lookup)
if aa.value.kind() == ast.ASTKind.Call:
val_cl: ast.Call* = ast.Call*(aa.value)
if val_cl.func is not None and val_cl.func.kind() == ast.ASTKind.Name:
val_nm: ast.Name* = ast.Name*(val_cl.func)
if val_nm.id is not None:
if self.Gen.AsyncGen.is_async_func(trans, val_nm.id) != 0: self.Gen.AsyncGen.register_coro_var(trans, nm.id, val_nm.id)
# c.Addr(async_call): interpret the tt: Coroutine = c.Addr(task()) form in place
elif val_cl.func is not None and val_cl.func.kind() == ast.ASTKind.Attribute:
val_at: ast.Attribute* = ast.Attribute*(val_cl.func)
if val_at.attr is not None and string.strcmp(val_at.attr, "Addr") == 0 and val_cl.args is not None and len(val_cl.args) >= 1:
icall: ast.AST* = val_cl.args.get(0)
if icall is not None and icall.kind() == ast.ASTKind.Call:
icl2: ast.Call* = ast.Call*(icall)
if icl2.func is not None and icl2.func.kind() == ast.ASTKind.Name:
inm2: ast.Name* = ast.Name*(icl2.func)
if inm2.id is not None:
if self.Gen.AsyncGen.is_async_func(trans, inm2.id) != 0: self.Gen.AsyncGen.register_coro_var(trans, nm.id, inm2.id)
# Local array from a list literal: zero it first, then per-element GEP + store (no list[T])
local_arr_ty: mlirlite.LLVMType* = None
if aa.value.kind() == ast.ASTKind.List and alloca.Ty is not None:
pt_l: mlirlite.LLVMType* = alloca.Ty.Pointee
if self.Gen.ExprGenBase.is_array_type(pt_l) != 0: local_arr_ty = pt_l
if local_arr_ty is not None:
zero_arr: mlirlite.Value* = mlirlite.ConstZero(pool, local_arr_ty)
if zero_arr is not None: mlirlite.build_store(builder, zero_arr, alloca)
self._init_global_array_from_list(builder, pool, mod, alloca, aa.value, trans)
else:
# Set CurAssignAnnot/CurAssignNode for bare generic construction (sync across all four branches)
trans.CurAssignAnnot = aa.annotation
trans.CurAssignNode = aa.value
# P1-A: use the local alloca pointee as the target type so integer literals materialize at target width
lv_target_ty: mlirlite.LLVMType* = None
if alloca.Ty is not None: lv_target_ty = alloca.Ty.Pointee
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aa.value, None, 0, trans, lv_target_ty, "局部变量注解")
if rhs_val is not None:
# Fix degraded annotation: rebuild the alloca with the precise RHS struct type
precise_rhs_ty: mlirlite.LLVMType* = None
if rhs_val.Ty is not None:
match rhs_val.Ty:
case mlirlite.LLVMType.Ptr(inner_struct):
match inner_struct:
case mlirlite.LLVMType.Struct(_, _, _):
precise_rhs_ty = rhs_val.Ty
case _:
pass
case _:
pass
if precise_rhs_ty is not None and alloca.Ty is not None:
alloca_pt: mlirlite.LLVMType* = alloca.Ty.Pointee
# Forbid downgrade rebuild: do not rebuild when the RHS pointee is a bare generic base
ann_gen_base: str = None
if annot is not None:
ag_side: ast.AST* = annot
if ag_side.kind() == ast.ASTKind.BinOp:
ag_bop: ast.BinOp* = ast.BinOp*(ag_side)
if ag_bop.op == ast.OpKind.BitOr:
if ag_bop.left is not None and ag_bop.left.kind() == ast.ASTKind.Subscript: ag_side = ag_bop.left
elif ag_bop.right is not None and ag_bop.right.kind() == ast.ASTKind.Subscript: ag_side = ag_bop.right
if ag_side.kind() == ast.ASTKind.Subscript:
ag_sub: ast.Subscript* = ast.Subscript*(ag_side)
if ag_sub.value is not None and ag_sub.value.kind() == ast.ASTKind.Name:
ag_nm: ast.Name* = ast.Name*(ag_sub.value)
if ag_nm.id is not None: ann_gen_base = ag_nm.id
rb_is_bare_generic: int = 0
if alloca_pt is not None and uint64(alloca_pt) != uint64(precise_rhs_ty):
rhs_pt_rb: mlirlite.LLVMType* = precise_rhs_ty.Pointee
if rhs_pt_rb is not None:
rb_nm: str = mlirlite.get_struct_name(rhs_pt_rb)
if rb_nm is not None:
rb_pf: int = GenApi.sha1_prefix_len(rb_nm)
rb_short: str = rb_nm + rb_pf if rb_pf != 0 else rb_nm
if string.strchr(rb_short, '[') is None:
# (1) The bare name hits the generic template registry (list/GSList/...)
if self.Gen.ClassGenGeneric._find_generic_template(rb_short) is not None: rb_is_bare_generic = 1
# (2) Same base name as the annotation => rebuild would downgrade; forbid it
elif ann_gen_base is not None and string.strcmp(rb_short, ann_gen_base) == 0: rb_is_bare_generic = 1
# Forbid narrowing an upcast: annotated base pointer with an RHS derived pointer must not be narrowed
rb_is_upcast: int = 0
if alloca_pt is not None and uint64(alloca_pt) != uint64(precise_rhs_ty) and self.Gen.ExprGenBase.is_ptr_type(alloca_pt) != 0:
ann_pt_rb: mlirlite.LLVMType* = alloca_pt.Pointee
rhs_pt_up: mlirlite.LLVMType* = precise_rhs_ty.Pointee
if ann_pt_rb is not None and rhs_pt_up is not None and uint64(ann_pt_rb) != uint64(rhs_pt_up):
if self.Gen.ExprGenBase._is_inheritance_upcast(pool, rhs_pt_up, ann_pt_rb) != 0: rb_is_upcast = 1
if alloca_pt is not None and uint64(alloca_pt) != uint64(precise_rhs_ty) and rb_is_bare_generic == 0 and rb_is_upcast == 0:
new_alloca: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, precise_rhs_ty)
if new_alloca is not None:
v_entry_rb: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
if v_entry_rb is not None:
v_entry_rb.Alloca = new_alloca
alloca = new_alloca
# Type coercion: convert rhs_val to the alloca pointee (integer literals may need trunc/sext)
target_ty: mlirlite.LLVMType* = None
if alloca.Ty is not None: target_ty = alloca.Ty.Pointee
if target_ty is not None:
# str/bytes is not a generic pointer: bare str/bytes annotation with an aggregate-pointer RHS
self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val, aa.annotation, node)
rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans)
# t.CVolatile local variable: mark the VarEntry and store with volatile
vol_laa: int = 0
if aa.annotation is not None and nm.id is not None:
# const object local variable: mark VarEntry.IsConst (idempotent with prescan)
if self.Gen.TypeGenErr.is_const_object_marker(aa.annotation, target_ty) != 0: self.Gen.VarGen.set_var_const(trans.SymTab, nm.id)
if self.Gen.TypeGenErr.has_decorator_marker(aa.annotation, "CVolatile", 0) != 0:
self.Gen.VarGen.set_var_volatile(trans.SymTab, nm.id)
vol_laa = 1
# rawptr[t.CVolatile | T]: mark PointeeVolatile (this store writes the pointer itself)
if self.Gen.TypeGenErr.has_pointee_volatile_marker(aa.annotation) != 0: self.Gen.VarGen.set_var_pointee_volatile(trans.SymTab, nm.id)
# Value-position volatile call prefix: the result slot is accessed with volatile
if aa.value is not None and aa.value.kind() == ast.ASTKind.Call and (ast.Call*(aa.value)).IsVolatile != 0:
self.Gen.VarGen.set_var_volatile(trans.SymTab, nm.id)
vol_laa = 1
mlirlite.build_store(builder, rhs_val, alloca, vol_laa)
# FakeDuck: initialize the shadow struct __data__ field with the initial string pointer
if fd_str_ty is not None and fd_meta_alloca is not None:
fd_data_fe: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(fd_str_ty, "__data__")
if fd_data_fe is not None:
fd_data_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, fd_str_ty, fd_data_fe.Ty, fd_meta_alloca, fd_data_fe.Index)
if fd_data_ptr is not None: mlirlite.build_store(builder, rhs_val, fd_data_ptr)
# Clear the assignment context after the RHS translation (sync across all four branches)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
# FakeDuck: initialize the __mbuddy__ field to NULL
if fd_str_ty is not None and fd_meta_alloca is not None:
fd_buddy_fe: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(fd_str_ty, "__mbuddy__")
if fd_buddy_fe is not None and fd_buddy_fe.Ty is not None:
fd_null: mlirlite.Value* = mlirlite.ConstNull(pool, fd_buddy_fe.Ty, "null")
fd_buddy_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, fd_str_ty, fd_buddy_fe.Ty, fd_meta_alloca, fd_buddy_fe.Index)
if fd_buddy_ptr is not None and fd_null is not None: mlirlite.build_store(builder, fd_null, fd_buddy_ptr)
# FakeDuck: auto-inject requires fields (e.g. __mbuddy__) for the with context
self.Gen.CallGenFd._inject_requires_fields(trans, pool, builder, mod, fd_meta_alloca, "_str")
return 0
# translate_assign - handle an Assign statement; returns the number of new variables
def translate_assign(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate an Assign statement; return the number of new variables."""
asgn: ast.Assign* = ast.Assign*(node)
if asgn is None:
VLogger.error("cast failed", "ASGN")
return 0
targets: list[ast.AST*]* = asgn.targets
if targets is None:
VLogger.error("targets is None", "ASGN")
return 0
# Read shared state from trans (replaces 11 parameters)
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
mod: mlirlite.LLVMModule* = trans.Module
# const target: fail fast before assignment (Name/Attribute/Subscript)
for ti_c in range(len(targets)):
tgt_c: ast.AST* = targets.get(ti_c)
if tgt_c is None: continue
if tgt_c.kind() == ast.ASTKind.Name:
nm_c: ast.Name* = ast.Name*(tgt_c)
if nm_c.id is None: continue
ve_c: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_c.id)
if self.Gen.VarGen.var_is_const(ve_c) != 0:
self.const_assign_fail(tgt_c, "赋值", nm_c.id, self.Gen.VarGen.var_annot_class_name(ve_c))
elif tgt_c.kind() == ast.ASTKind.Attribute:
at_c: ast.Attribute* = ast.Attribute*(tgt_c)
if at_c.attr is None: continue
if self.Gen.ExprGenAttr.attr_field_is_const(pool, trans, tgt_c) != 0:
self.const_assign_fail(tgt_c, "字段赋值", at_c.attr, self.Gen.ExprGenAttr.attr_field_decl_src(pool, trans, tgt_c))
elif tgt_c.kind() == ast.ASTKind.Subscript:
sub_c: ast.Subscript* = ast.Subscript*(tgt_c)
if sub_c.value is None: continue
if sub_c.value.kind() == ast.ASTKind.Name:
nm_c2: ast.Name* = ast.Name*(sub_c.value)
if nm_c2.id is None: continue
ve_c2: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_c2.id)
if self.Gen.VarGen.var_is_const(ve_c2) != 0:
self.const_assign_fail(tgt_c, "的元素赋值", nm_c2.id, self.Gen.VarGen.var_annot_class_name(ve_c2))
elif sub_c.value.kind() == ast.ASTKind.Attribute:
at_c2: ast.Attribute* = ast.Attribute*(sub_c.value)
if at_c2.attr is None: continue
if self.Gen.ExprGenAttr.attr_field_is_const(pool, trans, sub_c.value) != 0:
self.const_assign_fail(tgt_c, "的元素赋值", at_c2.attr, self.Gen.ExprGenAttr.attr_field_decl_src(pool, trans, sub_c.value))
# f-string escape: results are function-local temporaries; writing them out is dangling
for ti_f in range(len(targets)):
tgt_f: ast.AST* = targets.get(ti_f)
if tgt_f is None: continue
tk_f: int = tgt_f.kind()
if tk_f == ast.ASTKind.Attribute: GenApi.fail_if_fstring_escape(asgn.value, "字段赋值")
elif tk_f == ast.ASTKind.Subscript: GenApi.fail_if_fstring_escape(asgn.value, "元素赋值")
elif tk_f == ast.ASTKind.Name:
nm_f: ast.Name* = ast.Name*(tgt_f)
if nm_f.id is not None and GenApi.is_global_name(trans, nm_f.id) != 0: GenApi.fail_if_fstring_escape(asgn.value, "全局变量赋值")
# Translate the RHS; clear CurAssignAnnot/CurAssignNode (no target type to infer here)
trans.CurAssignAnnot = None
trans.CurAssignNode = None
# P1-A: for a single Name target with a known storage type, pass it as the target type
asg_target_ty: mlirlite.LLVMType* = None
if len(targets) == 1:
asg_t0: ast.AST* = targets.get(0)
if asg_t0 is not None and asg_t0.kind() == ast.ASTKind.Name:
asg_nm0: ast.Name* = ast.Name*(asg_t0)
if asg_nm0.id is not None:
asg_slot: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, asg_nm0.id)
if asg_slot is None: asg_slot = self.Gen.VarGen.lookup_module_var(trans.SymTab, asg_nm0.id)
if asg_slot is not None and asg_slot.Ty is not None: asg_target_ty = asg_slot.Ty.Pointee
elif trans.ReplCtx is not None and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0:
# REPL: a typed session variable also uses its slot type as the target type
asg_re: GenTypes.ReplVarEntry* = self.Gen.VarGen.repl_find_entry(trans.ReplCtx, asg_nm0.id)
if asg_re is not None and asg_re.Kind != GenTypes.REPL_KIND_UNTYPED: asg_target_ty = self.Gen.VarGen.repl_slot_ty(pool, asg_re)
rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, asgn.value, None, 0, trans, asg_target_ty)
if rhs_val is None:
# RHS is None usually means allocation failure; dump both pool statistics
mb_diag: memhub.MemBuddy* = CompilerFactory.factory.Pool
pp_diag: memhub.MemBuddy* = CompilerFactory.factory.PermPool
if mb_diag is not None: mb_diag.dump_stats("主池(Pool)")
if pp_diag is not None: pp_diag.dump_stats("常驻池(PermPool)")
stdio.fflush(None)
# Enhanced error message: file name + lineno + AST node details
if asgn.value is not None:
sha1: str = trans.ModuleSha1
val_kind: int = asgn.value.kind()
val_line: int = asgn.value.lineno
# Look up the file name from sha1 (human readable)
rel_path: str = None
if sha1 is not None: rel_path = StubMergerStore.LookupSha1RelPath(sha1)
# Extract details by AST node kind
if val_kind == ast.ASTKind.Attribute:
at_nv: ast.Attribute* = ast.Attribute*(asgn.value)
attr_nm: str = at_nv.attr
target_nm: str = "?"
if at_nv.value is not None:
if at_nv.value.kind() == ast.ASTKind.Name:
tn_nm: ast.Name* = ast.Name*(at_nv.value)
target_nm = tn_nm.id
if rel_path is not None: VLogger.error(f"赋值右侧为 None: {rel_path}:{val_line} '{target_nm}.{attr_nm}' 属性访问失败", "ASGN")
else: VLogger.error(f"赋值右侧为 None: sha1={sha1}:{val_line} '{target_nm}.{attr_nm}' 属性访问失败", "ASGN")
elif val_kind == ast.ASTKind.Name:
nm_nv: ast.Name* = ast.Name*(asgn.value)
if rel_path is not None: VLogger.error(f"赋值右侧为 None: {rel_path}:{val_line} 变量 '{nm_nv.id}' 未找到", "ASGN")
else: VLogger.error(f"赋值右侧为 None: sha1={sha1}:{val_line} 变量 '{nm_nv.id}' 未找到", "ASGN")
elif val_kind == ast.ASTKind.Call:
cl_nv: ast.Call* = ast.Call*(asgn.value)
func_nm: str = "?"
if cl_nv.func is not None:
if cl_nv.func.kind() == ast.ASTKind.Name:
fn_nm: ast.Name* = ast.Name*(cl_nv.func)
func_nm = fn_nm.id
if rel_path is not None: VLogger.error(f"赋值右侧为 None: {rel_path}:{val_line} 调用 '{func_nm}(...)' 返回 None", "ASGN")
else: VLogger.error(f"赋值右侧为 None: sha1={sha1}:{val_line} 调用 '{func_nm}(...)' 返回 None", "ASGN")
else:
if rel_path is not None: VLogger.error(f"赋值右侧为 None: {rel_path}:{val_line} [kind={val_kind}]")
else: VLogger.error(f"赋值右侧为 None: sha1={sha1}:{val_line} [kind={val_kind}]")
else: VLogger.error("赋值右侧为 None (asgn.value 为空)", "ASGN")
return 0
new_vars: int = 0
tn: sizet = len(targets)
for ti in range(tn):
target: ast.AST* = targets.get(ti)
if target is None: continue
tk: int = target.kind()
# Tuple/List assignment: unpack a struct return value into Name targets
if tk == ast.ASTKind.Tuple or tk == ast.ASTKind.List:
tgt_tup: ast.Tuple* = ast.Tuple*(target)
tgt_elts: list[ast.AST*]* = tgt_tup.elts
# Guard: skip this target when rhs is not a struct (tuple)
if tgt_elts is None or rhs_val is None or rhs_val.Ty is None: continue
match rhs_val.Ty:
case mlirlite.LLVMType.Struct(_, _, _):
tgt_n: sizet = len(tgt_elts)
tmp_s: mlirlite.Value* = mlirlite.build_alloca(builder, rhs_val.Ty)
if tmp_s is not None:
mlirlite.build_store(builder, rhs_val, tmp_s)
fchain: mlirlite.ParamNode* = mlirlite.get_struct_fields(rhs_val.Ty)
fcur: mlirlite.ParamNode* = fchain
fti: sizet
for fti in range(tgt_n):
tgt_elt: ast.AST* = tgt_elts.get(fti)
if tgt_elt is None or fcur is None: break
fty: mlirlite.LLVMType* = mlirlite.paramnode_get_ty(fcur)
fcur = mlirlite.paramnode_get_next(fcur)
if fty is None:
# fail-fast: field type missing
self.Gen.TypeGenErr.fatal_error(target, "元组解包字段类型缺失", "TypeError")
# Guard: skip non-Name elements (cannot unpack)
if tgt_elt.kind() != ast.ASTKind.Name: continue
tgt_nm2: ast.Name* = ast.Name*(tgt_elt)
if tgt_nm2.id is None: continue
fp2: mlirlite.Value* = mlirlite.build_gep_struct(builder, rhs_val.Ty, fty, tmp_s, fti)
if fp2 is None: continue
fv2: mlirlite.Value* = mlirlite.build_load(builder, fty, fp2)
tgt_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, tgt_nm2.id)
if tgt_alloca is None: tgt_alloca = self.Gen.VarGen.get_or_create_sym(trans.SymTab, pool, builder, tgt_nm2.id, fty)
if tgt_alloca is None: continue
sval: mlirlite.Value* = fv2
if tgt_alloca.Ty is not None and tgt_alloca.Ty.Pointee != 0: sval = self.Gen.ExprGenBase.coerce_to_type(builder, fv2, tgt_alloca.Ty.Pointee, node, trans)
mlirlite.build_store(builder, sval, tgt_alloca)
case _:
pass
continue
# Subscript assignment: arr[i] = val / ptr[i] = val / list[i] = val
if tk == ast.ASTKind.Subscript:
# Check for a list[T] Subscript; list subscript assignment uses the inline __setitem__ path
list_obj: mlirlite.Value* = self.Gen.ExprGenName.is_list_subscript(target, trans)
if list_obj is not None:
# list[T]: generate the __setitem__ logic inline
sub_node: ast.Subscript* = ast.Subscript*(target)
if sub_node is not None and sub_node.slice is not None:
list_idx_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub_node.slice, None, 0, trans)
if list_idx_val is None:
# fail-fast: list index translation failed
self.Gen.TypeGenErr.fatal_error(target, "list 下标翻译失败", "TypeError")
self.Gen.ExprGenName.list_setitem_inline(builder, pool, list_obj, list_idx_val, rhs_val, target)
continue
# Ordinary Subscript assignment
elem_ptr: mlirlite.Value* = self.Gen.ExprGenName.get_subscript_ptr(builder, pool, mod, target, trans)
if elem_ptr is not None:
store_val: mlirlite.Value* = rhs_val
# Fail fast when a rawptr[T] element is an object but RHS is a T pointer (whole-object copy)
self._reject_whole_object_copy_into_ptr_slot(pool, trans, target, elem_ptr, rhs_val)
# Guard: coerce only when the element type is complete
if elem_ptr.Ty is not None and elem_ptr.Ty.Pointee != 0: store_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, elem_ptr.Ty.Pointee, node, trans)
# rawptr[t.CVolatile|T] base: p[i] = v stores with volatile when the base is resolvable
vol_sw: int = 0
sub_wr: ast.Subscript* = ast.Subscript*(target)
if sub_wr is not None: vol_sw = self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, sub_wr.value)
mlirlite.build_store(builder, store_val, elem_ptr, vol_sw)
continue
# get_subscript_ptr returned None: try the __setitem__ overload for custom classes
sub_asgn: ast.Subscript* = ast.Subscript*(target)
setitem_done: int = 0
if sub_asgn is not None and sub_asgn.value is not None:
if sub_asgn.value.kind() == ast.ASTKind.Name:
sub_nm: ast.Name* = ast.Name*(sub_asgn.value)
if sub_nm.id is not None:
sub_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, sub_nm.id)
if sub_alloca is not None and sub_alloca.Ty is not None and sub_alloca.Ty.Pointee != 0 and self.Gen.ExprGenBase.is_ptr_type(sub_alloca.Ty) != 0:
sub_pointee: mlirlite.LLVMType* = sub_alloca.Ty.Pointee
cls_nm_set: str = self.Gen.StructGen.get_class_name_by_type(pool, sub_pointee)
obj_val_set: mlirlite.Value* = mlirlite.build_load(builder, sub_pointee, sub_alloca)
# Pointer variable (X*): sub_pointee is Ptr(Struct); load it to inspect the inner struct
if cls_nm_set is None and obj_val_set is not None and self.Gen.ExprGenBase.is_ptr_type(sub_pointee) != 0 and sub_pointee.Pointee != 0:
cls_nm_set = self.Gen.StructGen.get_class_name_by_type(pool, sub_pointee.Pointee)
if cls_nm_set is not None and obj_val_set is not None:
key_val_set: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub_asgn.slice, None, 0, trans)
if key_val_set is not None:
arg_vals_set: sizet* = pool.alloc(16)
if arg_vals_set is not None:
arg_vals_set[0] = sizet(key_val_set)
arg_vals_set[1] = sizet(rhs_val)
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, cls_nm_set, "__setitem__", obj_val_set, arg_vals_set, 2, trans)
setitem_done = 1
elif sub_asgn.value.kind() == ast.ASTKind.Attribute:
# self._ht[key] = val -> get the field pointer then call __setitem__
field_ptr_set: mlirlite.Value* = self.Gen.ExprGenAttr.get_attribute_ptr(builder, pool, mod, sub_asgn.value, trans)
if field_ptr_set is not None and field_ptr_set.Ty is not None and field_ptr_set.Ty.Pointee != 0 and self.Gen.ExprGenBase.is_ptr_type(field_ptr_set.Ty) != 0:
field_pointee_set: mlirlite.LLVMType* = field_ptr_set.Ty.Pointee
cls_nm_attr: str = self.Gen.StructGen.get_class_name_by_type(pool, field_pointee_set)
obj_val_attr: mlirlite.Value* = field_ptr_set
# field_ptr is Ptr(Ptr(Struct)) for an X* field: load to get Ptr(Struct)
if cls_nm_attr is None and self.Gen.ExprGenBase.is_ptr_type(field_pointee_set) != 0 and field_pointee_set.Pointee != 0:
cls_nm_attr = self.Gen.StructGen.get_class_name_by_type(pool, field_pointee_set.Pointee)
if cls_nm_attr is not None: obj_val_attr = mlirlite.build_load(builder, field_pointee_set, field_ptr_set)
if cls_nm_attr is not None and obj_val_attr is not None:
key_val_attr: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub_asgn.slice, None, 0, trans)
if key_val_attr is not None:
arg_vals_attr: sizet* = pool.alloc(16)
if arg_vals_attr is not None:
arg_vals_attr[0] = sizet(key_val_attr)
arg_vals_attr[1] = sizet(rhs_val)
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, cls_nm_attr, "__setitem__", obj_val_attr, arg_vals_attr, 2, trans)
setitem_done = 1
if setitem_done == 0:
# Generic fallback: translate sub.value and try __setitem__ or a direct GEP+store
if sub_asgn is not None and sub_asgn.value is not None:
obj_val_gen: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub_asgn.value, None, 0, trans)
key_val_gen: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sub_asgn.slice, None, 0, trans)
if obj_val_gen is not None and key_val_gen is not None and obj_val_gen.Ty is not None:
# Inline the _get_custom_struct_cls_nm logic (avoid a new dependency)
cls_nm_gen: str = self.Gen.StructGen.get_class_name_by_type(pool, obj_val_gen.Ty)
if cls_nm_gen is None and self.Gen.ExprGenBase.is_ptr_type(obj_val_gen.Ty) != 0 and obj_val_gen.Ty.Pointee != 0:
cls_nm_gen = self.Gen.StructGen.get_class_name_by_type(pool, obj_val_gen.Ty.Pointee)
if cls_nm_gen is not None:
# Custom struct: call __setitem__
arg_vals_gen: sizet* = pool.alloc(16)
if arg_vals_gen is not None:
arg_vals_gen[0] = sizet(key_val_gen)
arg_vals_gen[1] = sizet(rhs_val)
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, cls_nm_gen, "__setitem__", obj_val_gen, arg_vals_gen, 2, trans)
setitem_done = 1
elif self.Gen.ExprGenBase.is_ptr_type(obj_val_gen.Ty) != 0 and obj_val_gen.Ty.Pointee != 0:
# Non-custom struct: direct GEP + store (handles the None from get_subscript_ptr)
elem_gen: mlirlite.LLVMType* = obj_val_gen.Ty.Pointee
store_val_gen: mlirlite.Value* = rhs_val
elem_ptr_gen: mlirlite.Value* = None
match elem_gen:
case mlirlite.LLVMType.Array(arr_elem_gen, _):
elem_ptr_gen = mlirlite.build_gep_array(builder, elem_gen, arr_elem_gen, obj_val_gen, key_val_gen)
case _:
elem_ptr_gen = mlirlite.build_gep(builder, elem_gen, obj_val_gen, key_val_gen)
if elem_ptr_gen is not None and elem_ptr_gen.Ty is not None:
if elem_ptr_gen.Ty.Pointee != 0: store_val_gen = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, elem_ptr_gen.Ty.Pointee, node)
mlirlite.build_store(builder, store_val_gen, elem_ptr_gen)
setitem_done = 1
if setitem_done == 0: self.Gen.TypeGenErr.fatal_error(target, "subscript ptr is None")
continue
# UnaryOp target: *p = val (deref write); only Deref is a valid lvalue, others fail fast
if tk == ast.ASTKind.UnaryOp:
uo_w: ast.UnaryOp* = ast.UnaryOp*(target)
if uo_w is None or uo_w.operand is None: self.Gen.TypeGenErr.fatal_error(target, "赋值目标单目运算缺少操作数", "TypeError")
if uo_w.op != ast.OpKind.Deref:
self.Gen.TypeGenErr.fatal_error(target, "赋值目标只支持单目 '*' 解引用(&x / -x / ~x 不是合法左值)", "SyntaxError")
ptr_w: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, uo_w.operand, None, 0, trans)
if ptr_w is None or ptr_w.Ty is None or self.Gen.ExprGenBase.is_ptr_type(ptr_w.Ty) == 0:
self.Gen.TypeGenErr.fatal_error(target, "单目 '*' 赋值的操作数必须是指针类型(类型推断退化)", "TypeError")
pointee_w: mlirlite.LLVMType* = ptr_w.Ty.Pointee
if pointee_w is None:
self.Gen.TypeGenErr.fatal_error(target, "解引用写入的目标类型缺失(pointee 为空)", "TypeError")
sv_w: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, pointee_w, node, trans)
mlirlite.build_store(builder, sv_w, ptr_w)
continue
# Attribute assignment: obj.field = val
if tk == ast.ASTKind.Attribute:
# Narrow target to ast.Attribute* explicitly (required under the strict pointer rules)
at_tg: ast.Attribute* = ast.Attribute*(target)
# property setter: when attr is a setter, emit the setter call and skip the field store
if self.Gen.CallGenProp._try_property_setter(pool, builder, mod, at_tg, rhs_val, trans) == 1: continue
# FakeDuck: write per-instance str fields through the {var}__meta__ shadow struct
fd_an: ast.Attribute* = at_tg
if fd_an is not None and fd_an.value is not None and fd_an.attr is not None:
if fd_an.value.kind() == ast.ASTKind.Name:
fd_obj_nm: ast.Name* = ast.Name*(fd_an.value)
if fd_obj_nm.id is not None:
# Pool buffer (f-string stack buffers die when the function returns)
fd_meta_w: str = pool.alloc(256)
if fd_meta_w is not None: viperlib.snprintf(fd_meta_w, 256, "%s__meta__", fd_obj_nm.id)
fd_meta_vw: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, fd_meta_w)
if fd_meta_vw is not None:
fd_sty_w: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type("_str")
if fd_sty_w is not None:
fd_fe_w: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(fd_sty_w, fd_an.attr)
if fd_fe_w is not None and fd_fe_w.Ty is not None:
fd_fp_w: mlirlite.Value* = mlirlite.build_gep_struct(builder, fd_sty_w, fd_fe_w.Ty, fd_meta_vw, fd_fe_w.Index)
if fd_fp_w is not None:
sv_fd: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, fd_fe_w.Ty, node)
mlirlite.build_store(builder, sv_fd, fd_fp_w)
continue
# VTABLE write: obj.method = func writes the vtable slot instead of a field
if self.Gen.CallGenVt._write_vtable_attr(builder, pool, mod, target, rhs_val, trans) == 1: continue
field_ptr: mlirlite.Value* = self.Gen.ExprGenAttr.get_attribute_ptr(builder, pool, mod, target, trans)
if field_ptr is not None:
# Get the field type and coerce rhs_val (e.g. i32 -> i64)
store_val: mlirlite.Value* = rhs_val
if field_ptr.Ty is not None and field_ptr.Ty.Pointee != 0: store_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, field_ptr.Ty.Pointee, node, trans)
# t.CVolatile field: store with the volatile qualifier
vol_fw: int = self.Gen.ExprGenAttr.attr_field_is_volatile(pool, trans, target)
# rawptr[t.CVolatile|T] base: p.field = v also stores with volatile
at_wr: ast.Attribute* = ast.Attribute*(target)
if at_wr is not None:
if self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, at_wr.value) != 0: vol_fw = 1
mlirlite.build_store(builder, store_val, field_ptr, vol_fw)
continue
# Guard: field_ptr is None -> build a detailed error message
attr_node: ast.Attribute* = ast.Attribute*(target)
attr_name: str = "(unknown)"
obj_name: str = "(unknown)"
if attr_node is not None:
attr_name = attr_node.attr
if attr_node.value is not None and attr_node.value.kind() == ast.ASTKind.Name:
obj_nm: ast.Name* = ast.Name*(attr_node.value)
obj_name = obj_nm.id
err_buf: str = pool.alloc(256)
if err_buf is not None:
viperlib.snprintf(err_buf, 256, "attribute ptr is None: %s.%s", obj_name, attr_name)
self.Gen.TypeGenErr.fatal_error(target, err_buf)
else: self.Gen.TypeGenErr.fatal_error(target, "attribute ptr is None")
continue
# Name assignment: var = val
if tk == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(target)
# Guard: skip when the target name is missing
if nm.id is None: continue
# global variable: write to the module-scope global
if GenApi.is_global_name(trans, nm.id) != 0:
mod_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id)
if mod_alloca is None:
# fail-fast: global variable not registered
self.Gen.TypeGenErr.fatal_error(target, f"global 变量 '{nm.id}' 未注册,无法赋值", "NameError")
# Object global with static storage: bind to storage and copy the object (else dangling/NULL)
stg_ref_as: mlirlite.Value* = None
if nm.id is not None:
stg_key_as: str = pool.alloc(sizet(string.strlen(nm.id)) + 8)
if stg_key_as is not None:
viperlib.snprintf(stg_key_as, sizet(string.strlen(nm.id)) + 8, "%s.__stg", nm.id)
stg_ref_as = self.Gen.VarGen.lookup_module_var(trans.SymTab, stg_key_as)
if self._is_addrlike_call(asgn.value) == 0 and stg_ref_as is not None and rhs_val is not None and rhs_val.Ty is not None and stg_ref_as.Ty is not None and stg_ref_as.Ty.Pointee != 0:
stg_ty_as: mlirlite.LLVMType* = stg_ref_as.Ty.Pointee
rhs_inner_as: mlirlite.LLVMType* = None
match rhs_val.Ty:
case mlirlite.LLVMType.Ptr(pointee_as):
rhs_inner_as = pointee_as
case _:
pass
if rhs_inner_as is not None and rhs_inner_as.kind() == mlirlite.LLVMType.Struct and self._stg_ty_equal(pool, stg_ty_as, rhs_inner_as) != 0:
# 1) Bind the global reference to storage (drop the temporary alloca)
mlirlite.build_store(builder, stg_ref_as, mod_alloca)
# 2) Copy the constructed temporary into storage
stg_obj_val_as: mlirlite.Value* = mlirlite.build_load(builder, stg_ty_as, rhs_val)
if stg_obj_val_as is not None: mlirlite.build_store(builder, stg_obj_val_as, stg_ref_as)
continue
target_ty: mlirlite.LLVMType* = None
if mod_alloca.Ty is not None: target_ty = mod_alloca.Ty.Pointee
if target_ty is not None: rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans)
# t.CVolatile global: store with the volatile qualifier
vol_gas: int = 0
if nm.id is not None: vol_gas = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
mlirlite.build_store(builder, rhs_val, mod_alloca, vol_gas)
continue
# nonlocal variable: write through the closure env
if GenApi.is_nonlocal_name(trans, nm.id) != 0:
nl_ptr: mlirlite.Value* = self.Gen.StmtGenDecl.get_nonlocal_var_ptr(trans, nm.id)
if nl_ptr is None:
# fail-fast: nonlocal variable pointer resolution failed
self.Gen.TypeGenErr.fatal_error(target, f"nonlocal 变量 '{nm.id}' 指针解析失败", "NameError")
# Infer the nonlocal type from nl_ptr instead of forcing i32
nl_target_ty: mlirlite.LLVMType* = None
if nl_ptr.Ty is not None: nl_target_ty = nl_ptr.Ty.Pointee
if nl_target_ty is None:
# fail-fast: nl_ptr type lost, cannot store safely
self.Gen.TypeGenErr.fatal_error(target, f"nonlocal 变量 '{nm.id}' 类型信息丢失,无法写入", "TypeError")
rhs_coerced: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, nl_target_ty, node, trans)
mlirlite.build_store(builder, rhs_coerced, nl_ptr)
continue
# REPL session variable: a Name target at fragment entry defines/assigns the session slot
if trans.ReplCtx is not None and self.Gen.VarGen.repl_in_frag_entry_scope(trans) != 0:
rp_entry: GenTypes.ReplVarEntry* = self.Gen.VarGen.repl_ensure_entry(trans, nm.id, target)
if rp_entry is not None:
self.Gen.VarGen.repl_try_assign(trans, nm.id, rhs_val, asgn.value, node)
continue
# Block assignment to a module-level global inside a function without a global declaration
if self.Gen.VarGen.is_module_scope(trans.SymTab) == 0:
mod_gv: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, nm.id)
if mod_gv is not None: self.Gen.TypeGenErr.fatal_error(target, f"变量 '{nm.id}' 是模块级全局变量,函数内赋值需用 global 声明(否则创建局部变量遮蔽全局,导致全局保持 null 崩溃)", "SyntaxError")
# Ordinary local variable
alloca: mlirlite.Value* = self.Gen.VarGen.get_or_create_sym(trans.SymTab, pool, builder, nm.id, rhs_val.Ty)
if alloca is not None:
# Unannotated assignment: infer the class name from the RHS expression
cls_inf: str = self._infer_rhs_annot(pool, mod, asgn.value, trans, rhs_val)
if cls_inf is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, nm.id, cls_inf)
# Coerce the value to the alloca type (e.g. double -> float)
store_val: mlirlite.Value* = rhs_val
if alloca.Ty is not None and alloca.Ty.Pointee != 0: store_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, alloca.Ty.Pointee, node, trans)
# t.CVolatile local variable: store with the volatile qualifier
vol_las: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id))
mlirlite.build_store(builder, store_val, alloca, vol_las)
# A lambda assigned to a variable must be registered as a closure name
if asgn.value is not None and asgn.value.kind() == ast.ASTKind.Lambda: self.Gen.CallGenLeafKit.register_closure_name(nm.id)
existing: mlirlite.Value* = self.Gen.VarGen.lookup_current(trans.SymTab, nm.id)
if existing is None: new_vars += 1
else:
# fail-fast: variable storage creation failed
self.Gen.TypeGenErr.fatal_error(target, f"变量 '{nm.id}' 存储创建失败", "TypeError")
return new_vars
# _reject_whole_object_copy_into_ptr_slot - fail fast when a rawptr[T] subscript gets a T pointer RHS
# (the whole object would be copied into an 8-byte pointer slot)
def _reject_whole_object_copy_into_ptr_slot(self, pool: memhub.MemBuddy*, trans: HT.Translator*, target: ast.AST*, elem_ptr: mlirlite.Value*, rhs_val: mlirlite.Value*) -> None:
"""Fail fast when a rawptr[T] subscript gets a T pointer RHS."""
if elem_ptr is None or elem_ptr.Ty is None: return
if rhs_val is None or rhs_val.Ty is None: return
elem_ty: mlirlite.LLVMType* = None
match elem_ptr.Ty:
case mlirlite.LLVMType.Ptr(pointee_wo):
elem_ty = pointee_wo
case _:
return
if elem_ty is None: return
# Only object (struct) elements are checked; scalar and pointer elements return early
if elem_ty.kind() != mlirlite.LLVMType.Struct: return
rhs_inner_wo: mlirlite.LLVMType* = None
match rhs_val.Ty:
case mlirlite.LLVMType.Ptr(pointee_rhs_wo):
rhs_inner_wo = pointee_rhs_wo
case _:
return
if rhs_inner_wo is None: return
if rhs_inner_wo.kind() != mlirlite.LLVMType.Struct: return
# The pointee type must match the element type
if self._stg_ty_equal(pool, elem_ty, rhs_inner_wo) == 0: return
# Array slots (t.CArray[T,N]) are legal; only block when the base is a pointer store
if self.Gen.ExprGenName.subscript_base_store_kind(pool, trans, target) != 0: return
elem_nm_wo: str = mlirlite.get_struct_name(elem_ty)
if elem_nm_wo is None: elem_nm_wo = "<匿名结构体>"
rhs_nm_wo: str = mlirlite.get_struct_name(rhs_inner_wo)
if rhs_nm_wo is None: rhs_nm_wo = elem_nm_wo
elem_sz_wo: int = self.Gen.ClassGenEnum._get_type_size(elem_ty)
ptr_sz_wo: int = 8
over_wo: int = elem_sz_wo - ptr_sz_wo
if over_wo < 0: over_wo = 0
col_s_wo: int = 0
col_e_wo: int = 0
if target is not None:
col_s_wo = target.col_offset + 1
col_e_wo = target.end_col_offset + 1
self.Gen.TypeGenErr.fatal_error(target, f"下标赋值的左值元素是对象 '{elem_nm_wo}'({elem_sz_wo} 字节)," f"右值却是指向 '{rhs_nm_wo}' 的指针:编译器会静默生成整对象拷贝" f"(load {elem_nm_wo} + store {elem_nm_wo},写 {elem_sz_wo} 字节)," f"而不是按指针写 {ptr_sz_wo} 字节。若该地址实际按 8 字节槽存放指针" f"(如 rawptr[T] 缓冲/空闲链表),每写一个元素就越界 {over_wo} 字节," f"直接写穿相邻块。修法:把指针槽的基座类型写成指针元素形态" f"(`rawptr[T]*` 或 `rawptr[rawptr[T]]`)后再赋值;" f"或把基座显式转为 t.CPtr(无类型指针)后按 8 字节指针语义写入。", "TypeError", col_s_wo, col_e_wo)
# _infer_rhs_annot - infer a variable's annotation class name from the RHS expression
def _infer_rhs_annot(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator*, rhs_val: mlirlite.Value* = None) -> str:
"""Infer a variable's annotation class name from the RHS, or None."""
if node is None or trans is None: return None
k: int = node.kind()
# Call: class instantiation or method call
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(node)
if cl is None or cl.func is None: return None
if cl.func.kind() == ast.ASTKind.Name:
# Class instantiation: only accept a registered struct name
cand: str = (ast.Name*(cl.func)).id
if cand is None: return None
se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(cand)
if se is not None: return cand
return None
if cl.func.kind() == ast.ASTKind.Attribute:
# Method call: resolve the exact return class from the vpi annotation cache
ca: ast.Attribute* = ast.Attribute*(cl.func)
if ca is None or ca.attr is None or ca.value is None: return None
obj_cls: str = self._infer_rhs_annot(pool, mod, ca.value, trans)
if obj_cls is None:
# Module-level function call: resolve the return class from the module's vpi cache
if ca.value.kind() == ast.ASTKind.Name:
mod_nm_rhs: ast.Name* = ast.Name*(ca.value)
if mod_nm_rhs.id is not None:
mod_sha1_rhs: str = self.Gen.CallGenBase._lookup_module_sha1(mod_nm_rhs.id)
if mod_sha1_rhs is not None:
temp_dir_rhs: str = None
if self.Gen.CallGenBase.TempDir is not None: temp_dir_rhs = self.Gen.CallGenBase.TempDir
if temp_dir_rhs is not None: VpiReader._ensure_vpi_ret_annot_loaded(mod_sha1_rhs, temp_dir_rhs)
annot_rhs: str = VpiReader._lookup_vpi_ret_annot(mod_sha1_rhs, None, ca.attr)
if annot_rhs is not None:
ret_cls_rhs: str = self._extract_annot_class_name(pool, annot_rhs)
if ret_cls_rhs is not None: return ret_cls_rhs
return None
if self._cls_has_dunder(pool, mod, obj_cls, ca.attr) == 0: return None
# SHA1 of the module defining the method (struct entry, else current module)
cls_sha1: str = self.Gen.StructGen.get_struct_sha1(obj_cls)
if cls_sha1 is None and trans.ModuleSha1 is not None: cls_sha1 = trans.ModuleSha1
ret_cls: str = None
if cls_sha1 is not None:
temp_dir: str = None
if self.Gen.CallGenBase.TempDir is not None: temp_dir = self.Gen.CallGenBase.TempDir
if temp_dir is not None: VpiReader._ensure_vpi_ret_annot_loaded(cls_sha1, temp_dir)
annot_ret: str = VpiReader._lookup_vpi_ret_annot(cls_sha1, obj_cls, ca.attr)
if annot_ret is not None: ret_cls = self._extract_annot_class_name(pool, annot_ret)
return ret_cls
return None
# Name: the source variable annotation
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm is None or nm.id is None: return None
ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
if ve is None: return None
return self.Gen.VarGen.var_annot_class_name(ve)
# BinOp/UnaryOp: operator overload returns an object of the same class
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if bop is None or bop.left is None: return None
dunder: str = self._op_to_dunder(bop.op)
if dunder is None: return None
lhs_cls: str = self._infer_rhs_annot(pool, mod, bop.left, trans)
if lhs_cls is None or self._cls_has_dunder(pool, mod, lhs_cls, dunder) == 0: return None
return lhs_cls
if k == ast.ASTKind.UnaryOp:
uop: ast.UnaryOp* = ast.UnaryOp*(node)
if uop is None or uop.operand is None: return None
dunder: str = self._op_to_dunder(uop.op)
if dunder is None: return None
opr_cls: str = self._infer_rhs_annot(pool, mod, uop.operand, trans)
if opr_cls is None or self._cls_has_dunder(pool, mod, opr_cls, dunder) == 0: return None
return opr_cls
# Attribute: field annotation class name (recursive attribute chain)
if k == ast.ASTKind.Attribute: return self.Gen.ExprGenAttr._resolve_attr_field_class(pool, trans, node)
return None
# _extract_annot_class_name - extract a registered struct class name from a return annotation text
def _extract_annot_class_name(self, pool: memhub.MemBuddy*, annot: str) -> str:
"""Extract a registered struct class name from a return annotation."""
if pool is None or annot is None: return None
alen: sizet = string.strlen(annot)
if alen == 0: return None
seg: str = pool.alloc(256)
if seg is None: return None
start: sizet = 0
i: sizet
for i in range(alen):
if annot[i] == '|':
seglen: sizet = i - start
# Segment [start, i): trim spaces/quotes then look up the registry
s: sizet = start
e: sizet = i
while s < e and (annot[s] == ' ' or annot[s] == '\'' or annot[s] == '"'): s += 1
# Also strip a trailing '*'; otherwise find_struct misses and chained calls degrade
while e > s and (annot[e - 1] == ' ' or annot[e - 1] == '\'' or annot[e - 1] == '"' or annot[e - 1] == '*'): e -= 1
seglen = e - s
if seglen > 0 and seglen < 256:
string.strncpy(seg, annot + s, seglen)
seg[seglen] = '\0'
se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(seg)
if se is not None: return seg
start = i + 1
# Last segment (no '|' suffix)
s: sizet = start
e: sizet = alen
while s < e and (annot[s] == ' ' or annot[s] == '\'' or annot[s] == '"'): s += 1
# Also strip a trailing '*'; otherwise find_struct misses and chained calls degrade
while e > s and (annot[e - 1] == ' ' or annot[e - 1] == '\'' or annot[e - 1] == '"' or annot[e - 1] == '*'): e -= 1
seglen: sizet = e - s
if seglen > 0 and seglen < 256:
string.strncpy(seg, annot + s, seglen)
seg[seglen] = '\0'
se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(seg)
if se is not None: return seg
return None
# _op_to_dunder - operator -> dunder method name (local copy to avoid cross-mixin calls)
def _op_to_dunder(self, op: int) -> str:
"""Map a BinOp/UnaryOp operator to its dunder name, or None."""
if op == ast.OpKind.Add: return "__add__"
if op == ast.OpKind.Sub: return "__sub__"
if op == ast.OpKind.Mult: return "__mul__"
if op == ast.OpKind.Div: return "__truediv__"
if op == ast.OpKind.FloorDiv: return "__floordiv__"
if op == ast.OpKind.Mod: return "__mod__"
if op == ast.OpKind.Pow: return "__pow__"
if op == ast.OpKind.BitAnd: return "__and__"
if op == ast.OpKind.BitOr: return "__or__"
if op == ast.OpKind.BitXor: return "__xor__"
if op == ast.OpKind.LShift: return "__lshift__"
if op == ast.OpKind.RShift: return "__rshift__"
if op == ast.OpKind.USub: return "__neg__"
if op == ast.OpKind.UAdd: return "__pos__"
if op == ast.OpKind.Invert: return "__invert__"
return None
# _aug_op_to_dunder - augmented operator -> aug dunder (__iadd__ etc.); None falls back to _op_to_dunder
def _aug_op_to_dunder(self, op: int) -> str:
"""Map an AugAssign operator to its in-place dunder name, or None."""
if op == ast.OpKind.Add: return "__iadd__"
if op == ast.OpKind.Sub: return "__isub__"
if op == ast.OpKind.Mult: return "__imul__"
if op == ast.OpKind.Div: return "__itruediv__"
if op == ast.OpKind.FloorDiv: return "__ifloordiv__"
if op == ast.OpKind.Mod: return "__imod__"
if op == ast.OpKind.Pow: return "__ipow__"
if op == ast.OpKind.BitAnd: return "__iand__"
if op == ast.OpKind.BitOr: return "__ior__"
if op == ast.OpKind.BitXor: return "__ixor__"
if op == ast.OpKind.LShift: return "__ilshift__"
if op == ast.OpKind.RShift: return "__irshift__"
return None
# _cls_has_dunder - check whether a class (including its MRO) has a dunder method
def _cls_has_dunder(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, class_name: str, dunder: str) -> int:
"""Return 1 if the class or its MRO chain has the dunder method."""
if class_name is None or dunder is None or mod is None: return 0
lookup_name: str = pool.alloc(128)
if lookup_name is None: return 0
viperlib.snprintf(lookup_name, 128, "%s.%s", class_name, dunder)
if self.Gen.CallGenBase.find_func_in_module(mod, lookup_name) is not None: return 1
# Search the inheritance chain ancestors
mro_ptr: sizet* = self.Gen.StructGen.get_mro_chain(pool, class_name)
mi: int = 1
while mro_ptr is not None and mro_ptr[mi] != 0:
anc: str = rawptr(mro_ptr[mi])
if anc is not None:
anc_lookup: str = pool.alloc(128)
if anc_lookup is not None:
viperlib.snprintf(anc_lookup, 128, "%s.%s", anc, dunder)
if self.Gen.CallGenBase.find_func_in_module(mod, anc_lookup) is not None: return 1
mi += 1
# Cross-module: a class with a SHA1 (method declared in the stub) counts as having it
if self.Gen.StructGen.get_struct_sha1(class_name) is not None: return 1
return 0
def InitAssignGen(self) -> int:
"""Initialize AssignGen fields (called by Generator at construction)."""
if self is None: return 1
self.RecoverFound = 0
return 0