Files
ViperC/App/lib/core/Gen/ExprGenAttr.vp
T
TermiNexus 601db9e265 fix(gen): from-import 模块名截断后再查 SHA1(非 NUL 结尾退化裸名)
undefined symbol: _op_head 根因:lookup_from_import 返回指向 from_imports
内部的指针(非 NUL 结尾,后随空格分隔的其他条目,如 "ast.tokens
TokenType:ast.tokens ...")。ExprGenName 的 from-import 全局查找直接用
该值 LookupSha1ByModName → 整串查不到 → _mangle_name_with_sha1(None)
退化裸名 → 引用 @_op_head 而非 @<tokens_sha1>._op_head → 链接 undefined
(tokens 定义端已带 SHA1 前缀)。ExprGenAttr 别名解析同病。

修复:ExprGenName 新增 _cut_from_import_mod(截断到空格/冒号/NUL),
两处 from-import 分支截断后再查 SHA1;ExprGenAttr 别名解析内联截断。
对齐 CallGenBase._lookup_sha1_from_modname_raw / ExprGenCdef 既有截断逻辑。
2026-08-20 05:01:21 +08:00

1204 lines
73 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 llvmlite
import memhub
import string
import stdio
import stdlib
import viperlib
import w32.fileio as fileio
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Handles.HandlesBase as HandlesBase
import lib.core.Handles.HandlesTranslator as HT
import lib.core.Handles.HandlesVar as HandlesVar
import lib.core.Handles.HandlesExpr as HandlesExpr
import lib.core.Handles.HandlesExprOps as HandlesExprOps
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.core.Handles.HandlesNonlocal as HandlesNonlocal
import lib.core.Handles.HandlesType as HandlesType
import lib.core.Handles.HandlesStruct as HandlesStruct
import lib.core.Handles.HandlesEnum as HandlesEnum
import lib.core.Handles.HandlesClassDef as HandlesClassDef
import lib.core.Handles.HandlesImports as HandlesImports
import lib.core.Handles.HandlesFunctions as HandlesFunctions
import lib.core.StubMerger as StubMerger
import lib.core.StubMergerStore as StubMergerStore
import lib.core.VLogger as VLogger
import lib.Projectrans.Config as Config
import lib.Projectrans.Utils as Utils
import lib.core.Gen.ExprGenBase as ExprGenBase
import lib.core.Handles.ExprCallCtor as ExprCallCtor
# ============================================================
# ExprGenAttr - ExprGenMixin 拆分(阶段 D):Attribute/属性访问/全局引用
#
# 继承 ExprGenBase(1 级,TPV self 调用仅支持 1 级父类)。
# 跨 Mixin 调用统一走 HandlesExpr 委托(经 _g_gen)。
# @t.NoVTable:静态绑定。
# ============================================================
@t.NoVTable
class ExprGenAttr(ExprGenBase.ExprGenBase):
"""Attribute/属性访问/全局引用"""
# ============================================================
# find_global_in_module - 在模块全局变量链表中按名称查找
#
# 查找策略:
# 1. 精确匹配 (如 "_mbuddy" 匹配 @_mbuddy)
# 2. 后缀匹配 (如 "_mbuddy" 匹配 @"sha1._mbuddy")
# ============================================================
def find_global_in_module(self, mod: llvmlite.LLVMModule | t.CPtr,
name: str) -> llvmlite.GlobalVariable | t.CPtr:
"""在模块全局变量链表中按名称查找全局变量"""
if mod is None or name is None:
return None
name_len: t.CSizeT = string.strlen(name)
cur: llvmlite.GlobalVariable | t.CPtr = mod.GlobalHead
# 第一遍: 精确匹配
while cur is not None:
if cur.Name is not None:
if string.strcmp(cur.Name, name) == 0:
return cur
cur = cur.Next
# 第二遍: 后缀匹配 (.name)
cur = mod.GlobalHead
while cur is not None:
if cur.Name is not None:
cur_len: t.CSizeT = string.strlen(cur.Name)
if cur_len > name_len + 1:
suffix_start: t.CSizeT = cur_len - name_len
if cur.Name[suffix_start - 1] == '.':
match: int = 1
for i in range(name_len):
if cur.Name[suffix_start + i] != name[i]:
match = 0
break
if match == 1:
return cur
cur = cur.Next
return None
# ============================================================
# make_global_ref - 创建全局变量引用 (@name,类型为 ty*)
#
# 用于跨模块全局变量访问: stub 未注入时,翻译阶段生成 @name 引用,
# 链接时由 stub 提供 external global 声明。
# ============================================================
def make_global_ref(self, pool: memhub.MemBuddy | t.CPtr,
name: str,
ty: llvmlite.LLVMType | t.CPtr) -> llvmlite.Value | t.CPtr:
"""创建全局变量引用 (类型为 ty*,名称为 @name)"""
if name is None or ty is None:
return None
ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, ty)
ref_name: str = pool.alloc(96)
if ref_name is None:
return None
# LLVM 无引号标识符不能以数字开头:带 SHA1 前缀的全局名("sha1.name")
# 必命中 needs_quote(数字开头 + '.'),引用必须加引号 @\"sha1.name\",
# 否则 llc "expected instruction opcode"。对齐函数引用路径
# (ExprGenName L252-255 的 needs_quote 判断)。
if HandlesClassDef._vtable_name_needs_quote(name) != 0:
viperlib.snprintf(ref_name, 96, "@\"%s\"", name)
else:
viperlib.snprintf(ref_name, 96, "@%s", name)
return llvmlite.SSAValue(pool, ptr_ty, ref_name)
# ============================================================
# _resolve_module_attribute_global - 解析模块属性访问的全局变量引用
#
# 当 obj 是已导入模块名 (如 sys._mbuddy) 时:
# 1. 在当前模块全局变量链表中查找 (stub 已注入的情况)
# 2. 未找到则创建前向引用 (@attr_name, 类型为 ty 参数)
#
# 参数:
# ty_hint: 类型提示 (写路径用 rhs 类型, 读路径用 i8* 回退)
# 返回: Value 指针 (类型为 ty*),未识别为模块属性返回 None
# ============================================================
def _is_t_type_object_name(self, name: str) -> int:
"""检查 name 是否是 t.py 中的类型对象名(CPtr/CInt/CDouble 等)
这些类型对象在运行时作为全局变量 @CPtr/@CInt 等被引用,
但 t.py 不被编译,需要在使用处生成带 null 初值的定义。
"""
if name is None:
return 0
# t.py 中的类型对象名(PascalCase,首字母大写)
if string.strcmp(name, "CPtr") == 0:
return 1
if string.strcmp(name, "CInt") == 0:
return 1
if string.strcmp(name, "CChar") == 0:
return 1
if string.strcmp(name, "CShort") == 0:
return 1
if string.strcmp(name, "CLong") == 0:
return 1
if string.strcmp(name, "CLongLong") == 0:
return 1
if string.strcmp(name, "CDouble") == 0:
return 1
if string.strcmp(name, "CFloat") == 0:
return 1
if string.strcmp(name, "CBool") == 0:
return 1
if string.strcmp(name, "CVoid") == 0:
return 1
if string.strcmp(name, "CInt8T") == 0:
return 1
if string.strcmp(name, "CInt16T") == 0:
return 1
if string.strcmp(name, "CInt32T") == 0:
return 1
if string.strcmp(name, "CInt64T") == 0:
return 1
if string.strcmp(name, "CUInt8T") == 0:
return 1
if string.strcmp(name, "CUInt16T") == 0:
return 1
if string.strcmp(name, "CUInt32T") == 0:
return 1
if string.strcmp(name, "CUInt64T") == 0:
return 1
if string.strcmp(name, "CUnsigned") == 0:
return 1
if string.strcmp(name, "CUnsignedInt") == 0:
return 1
if string.strcmp(name, "CUnsignedChar") == 0:
return 1
if string.strcmp(name, "CUnsignedShort") == 0:
return 1
if string.strcmp(name, "CUnsignedLong") == 0:
return 1
if string.strcmp(name, "CUnsignedLongLong") == 0:
return 1
if string.strcmp(name, "CSizeT") == 0:
return 1
if string.strcmp(name, "CIntPtrT") == 0:
return 1
if string.strcmp(name, "CUIntPtrT") == 0:
return 1
if string.strcmp(name, "CPtrDiffT") == 0:
return 1
if string.strcmp(name, "CType") == 0:
return 1
if string.strcmp(name, "CDefine") == 0:
return 1
if string.strcmp(name, "CStatic") == 0:
return 1
if string.strcmp(name, "CExport") == 0:
return 1
if string.strcmp(name, "CArray") == 0:
return 1
if string.strcmp(name, "CTypedef") == 0:
return 1
if string.strcmp(name, "CNoVTable") == 0:
return 1
if string.strcmp(name, "CNeedPtr") == 0:
return 1
if string.strcmp(name, "CAutoPtr") == 0:
return 1
return 0
def _attr_module_path_str(self, pool: memhub.MemBuddy | t.CPtr,
node: ast.AST | t.CPtr) -> str:
"""把模块引用 AST 转成模块路径字符串(递归拼接 Attribute 链):
Name('sys') → "sys";Attribute(Attribute(Name('os'),'_win32')) → "os._win32"。
非模块引用形态(Call/Subscript 等)返回 None。"""
if node is None:
return None
k: int = node.kind()
if k == ast.ASTKind.Name:
nm_m: ast.Name | t.CPtr = (ast.Name | t.CPtr)(node)
if nm_m.id is not None:
return nm_m.id
return None
if k == ast.ASTKind.Attribute:
at_m: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(node)
if at_m.attr is None:
return None
base_m: str = self._attr_module_path_str(pool, at_m.value)
if base_m is None:
return None
blen: t.CSizeT = string.strlen(base_m)
alen: t.CSizeT = string.strlen(at_m.attr)
buf_m: str = pool.alloc(blen + alen + 2)
if buf_m is None:
return None
viperlib.snprintf(buf_m, blen + alen + 2, "%s.%s", base_m, at_m.attr)
return buf_m
return None
def _resolve_module_attribute_global(self, pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
trans: HT.Translator | t.CPtr,
at: ast.Attribute | t.CPtr,
ty_hint: llvmlite.LLVMType | t.CPtr) -> llvmlite.Value | t.CPtr:
"""解析模块属性访问,返回全局变量引用 (用于跨模块 global 访问)"""
if at is None or at.value is None or at.attr is None:
return None
if trans is None or trans._imported_modules is None:
return None
# 提取模块路径名:Name → "sys";嵌套 Attribute → "os._win32"
# (此前只支持 Name 模块;SysLibTest main.vp L32 `os._win32._mbuddy = v`
# 的 at.value 是 Attribute → 被跳过 → "属性写入失败" 根因)
mod_path_w: str = self._attr_module_path_str(pool, at.value)
if mod_path_w is None:
return None
# 别名解析:import lib.core.Gen.Generator as GenGenerator → 查 from_imports
# 得原始模块名。否则 _lookup_module_sha1("GenGenerator") 查不到(Sha1Store
# 用原始模块名)→ 退化裸名 → 全局带 SHA1 前缀后 undefined symbol。
# 别名属性访问(GenGenerator._g_gen = gen)依赖此解析才能命中目标模块定义。
# 注意:lookup_from_import 返回指针非 NUL 结尾,须截断到空格/冒号/NUL
# 再查 SHA1(否则整串 "lib.core.Gen.Generator Config:lib... " 查不到)。
if trans._from_imports is not None:
orig_mw: str = HandlesImports.lookup_from_import(
trans._from_imports, mod_path_w, 0)
if orig_mw is not None:
oml_a: t.CSizeT = 0
while orig_mw[oml_a] != ' ' and orig_mw[oml_a] != '\0' and orig_mw[oml_a] != ':':
oml_a += 1
if oml_a > 0:
om_buf_a: str = pool.alloc(oml_a + 1)
if om_buf_a is not None:
string.strncpy(om_buf_a, orig_mw, oml_a)
om_buf_a[oml_a] = '\0'
mod_path_w = om_buf_a
# 检查是否是已导入模块
if HandlesImports.is_module_imported(trans._imported_modules, mod_path_w) == 0:
return None
# 在当前模块查找全局变量:优先按"目标模块 SHA1 前缀 + 属性名"精确匹配,
# 避免多模块同名全局(@<A>._mbuddy / @<B>._mbuddy 同时注入)时裸名后缀
# 匹配返回错误模块的全局(串扰)。目标 sha1 未知时退化裸名(后缀匹配兜底)
gv_q: str = HandlesFunctions._mangle_name_with_sha1(
self._lookup_module_sha1(mod_path_w), at.attr)
gv: llvmlite.GlobalVariable | t.CPtr = self.find_global_in_module(mod, gv_q)
if gv is not None and gv.Ty is not None:
# 用 gv.Name(完整名,带 SHA1 前缀时与定义端一致)构造引用,
# 避免裸名 @_mbuddy 在多模块各自带前缀定义下 undefined symbol
return self.make_global_ref(pool, gv.Name, gv.Ty)
# 未找到: 创建 external global 声明并添加到模块
# 否则只生成 @_argc 引用而无声明,LLVM 报 "use of undefined value '@_argc'"
use_ty: llvmlite.LLVMType | t.CPtr = ty_hint
if use_ty is None:
use_ty = llvmlite.Int8(pool)
# 确保 use_ty 中的跨模块结构体类型有 opaque 声明
llvmlite.module_ensure_opaque_for_type(mod, pool, use_ty)
# 特殊处理: t.py 中的类型对象(CPtr/CInt/CDouble 等)
# t.py 不被编译,但这些类型对象在运行时被引用(如 va_arg(ap, t.CPtr))。
# 创建带 null 初值的 linkonce_odr 全局变量定义,避免链接时 undefined reference。
# LLVM 22+ 不允许 external linkage 全局变量带初值,使用 linkonce_odr 替代。
is_t_type_obj: int = 0
if string.strcmp(mod_path_w, "t") == 0:
is_t_type_obj = self._is_t_type_object_name(at.attr)
# 兜底声明名:t 类型对象(t.py 不编译、无 sha1)保持裸名 linkonce_odr;
# 其他模块属性带目标模块 SHA1 前缀(对齐定义端 mangling,跨模块同名
# 全局独立命名,消除 duplicate symbol / 串扰)
gv_name_ba: str = at.attr
if is_t_type_obj == 0:
gv_name_ba = HandlesFunctions._mangle_name_with_sha1(
self._lookup_module_sha1(mod_path_w), at.attr)
ext_gv: llvmlite.GlobalVariable | t.CPtr = llvmlite.new_global_variable(pool, gv_name_ba, use_ty)
if ext_gv is not None:
if is_t_type_obj != 0:
# t.CPtr 等类型对象: 创建带 null 初值的定义(linkonce_odr)
ext_gv.Linkage = "linkonce_odr"
# i8* null 的初值文本
null_init: bytes = pool.alloc(8)
if null_init is not None:
null_init[0] = 'n'
null_init[1] = 'u'
null_init[2] = 'l'
null_init[3] = 'l'
null_init[4] = '\0'
ext_gv.Initializer = null_init
else:
ext_gv.Initializer = None
else:
# 普通模块属性: external 声明(由其他模块提供定义)
ext_gv.Linkage = "external"
ext_gv.Initializer = None
llvmlite.module_add_global(mod, ext_gv)
return self.make_global_ref(pool, gv_name_ba, use_ty)
def _lookup_module_sha1(self, mod_name: str) -> str:
"""查找模块名对应的 SHA1(委托 StubMergerStore,支持包名回退;
None=未找到,调用方退化为裸名)"""
return StubMergerStore.LookupSha1ByModName(mod_name)
# ============================================================
# 翻译属性访问 Attribute(value, attr, ctx) — obj.field
#
# 优先级:
# 1. 枚举成员访问 (State.Idle → 常量值)
# 2. 模块属性访问 (sys._mbuddy → 加载全局变量)
# 3. 结构体字段访问 (obj.field → GEP + load)
#
# 对于 Name 类型的 obj,直接使用 alloca 指针(不 load 结构体)
# ============================================================
# ============================================================
# _get_cderef_attr_base - c.Deref(ptr).attr 属性访问的 base 指针
#
# c.Deref(ptr) 作为属性访问 base 时(如 c.Deref(p).a),需要 ptr
# 指针本身而非 load 后的结构体值(*p.a → p->a),与 TPC
# _handle_attr_call_result 语义一致。否则 c.Deref 返回结构体值,
# 后续 GEP 在非指针上做属性访问会误报"base=非指针类型"。
#
# 返回:
# ptr 指针值(Ptr(Struct) 或 Ptr(Ptr(Struct)),交由 _deref_if_ptr_ptr 解层级)
# 非 c.Deref 模式返回 None
# ============================================================
def _get_cderef_attr_base(self, builder: llvmlite.IRBuilder | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
at_value: ast.AST | t.CPtr,
trans: HT.Translator | t.CPtr = None) -> llvmlite.Value | t.CPtr:
"""检测 at.value 是否为 c.Deref(ptr) 调用,返回 ptr 指针(不做 load)"""
if at_value is None or at_value.kind() != ast.ASTKind.Call:
return None
cl_cd: ast.Call | t.CPtr = (ast.Call | t.CPtr)(at_value)
if cl_cd.func is None or cl_cd.func.kind() != ast.ASTKind.Attribute:
return None
f_cd: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(cl_cd.func)
if f_cd.attr is None or string.strcmp(f_cd.attr, "Deref") != 0:
return None
if f_cd.value is None or f_cd.value.kind() != ast.ASTKind.Name:
return None
nm_cd: ast.Name | t.CPtr = (ast.Name | t.CPtr)(f_cd.value)
if nm_cd.id is None or string.strcmp(nm_cd.id, "c") != 0:
return None
if cl_cd.args is None or len(cl_cd.args) < 1:
return None
inner_cd: llvmlite.Value | t.CPtr = HandlesExpr.translate_value(
builder, pool, mod, cl_cd.args.get(0), None, 0, trans)
if inner_cd is not None and self.is_ptr_type(inner_cd.Ty) != 0:
return inner_cd
return None
def translate_attribute(self, builder: llvmlite.IRBuilder | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
node: ast.AST | t.CPtr,
trans: HT.Translator | t.CPtr = None) -> llvmlite.Value | t.CPtr:
"""翻译属性访问,返回加载的字段值"""
if node is None or builder is None:
return None
at: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(node)
if at is None or at.value is None or at.attr is None:
# fast-fail: AST 节点字段缺失属解析器 bug,应报告具体缺失字段
diag_buf_an: bytes = stdlib.malloc(256)
if diag_buf_an is not None:
missing_an: str = "?"
if at is not None:
if at.value is None:
missing_an = "value"
elif at.attr is None:
missing_an = "attr"
viperlib.snprintf(diag_buf_an, 256,
"属性访问失败: AST 节点字段缺失 (%s sha1=%s:%d)",
missing_an,
trans.ModuleSha1 if trans is not None and trans.ModuleSha1 is not None else "?",
at.lineno if at is not None and at.lineno is not None else 0)
VLogger.error(diag_buf_an, "ATTR")
return None
# 魔法属性 __doc__:查询 docstring 登记表(预注册阶段从 AST 提取),
# 命中则当前模块内联生成字符串常量返回;未命中(无 docstring 的
# 函数/类/方法)返回 null 指针(i8*)。支持本模块与跨模块符号:
# local_func / LocalClass / LocalClass.method / remote.remote_func /
# remote.RemoteClass / remote.RemoteClass.remote_method
if string.strcmp(at.attr, "__doc__") == 0:
doc_mod_buf: bytes = pool.alloc(64)
doc_sym_buf: bytes = pool.alloc(256)
if doc_mod_buf is None or doc_sym_buf is None:
return None
doc_mod_buf[0] = '\0'
doc_sym_buf[0] = '\0'
if at.value is not None and at.value.kind() == ast.ASTKind.Name:
nm_doc: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at.value)
if nm_doc.id is not None:
HandlesExpr._resolve_doc_symbol_name(trans, nm_doc.id, doc_mod_buf, doc_sym_buf)
elif at.value is not None and at.value.kind() == ast.ASTKind.Attribute:
HandlesExpr._resolve_doc_symbol_attr(trans, (ast.Attribute | t.CPtr)(at.value), doc_mod_buf, doc_sym_buf)
doc_val: str = None
if doc_mod_buf[0] != '\0' and doc_sym_buf[0] != '\0':
doc_val = HandlesExpr.lookup_docstring(doc_mod_buf, doc_sym_buf)
if doc_val is None:
i8_ty_doc: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
if i8_ty_doc is None:
return None
i8_ptr_ty_doc: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty_doc)
if i8_ptr_ty_doc is None:
return None
return llvmlite.ConstNull(pool, i8_ptr_ty_doc, "null")
return HandlesExpr.create_global_string(builder, pool, mod, doc_val, trans)
# 枚举成员访问: EnumName.MemberName → 常量值
# 当 value 是 Name 且 Name.id 是已注册枚举时,查找成员并返回常量
if at.value.kind() == ast.ASTKind.Name:
enum_nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at.value)
if enum_nm.id is not None:
if HandlesEnum.is_enum_class(enum_nm.id) == 1:
member: HandlesEnum.EnumMember | t.CPtr = HandlesEnum.lookup_enum_member(
enum_nm.id, at.attr)
if member is not None:
base_ty: llvmlite.LLVMType | t.CPtr = HandlesEnum.get_enum_base_type(
enum_nm.id)
if base_ty is not None:
# 生成数字字符串作为常量名(LLVM IR 要求常量输出数字值)
name_buf: str = pool.alloc(32)
if name_buf is not None:
viperlib.snprintf(name_buf, 32, "%lld", member.Value)
return llvmlite.ConstInt(pool, base_ty, member.Value, name_buf)
return llvmlite.const_int32(pool, member.Value)
# fast-fail: 枚举成员未找到
VLogger.error(
f"枚举成员未找到: {enum_nm.id}.{at.attr}",
"ATTR")
return None
# 嵌套枚举成员访问: Module.EnumName.MemberName → 常量值
# 当 value 是 Attribute(如 fileio.MODE / w32.fileio.MODE,含三级以上
# 模块路径)且 attr 部分是已注册枚举时。此前限制 inner_at_e.value 必须是
# Name(仅支持两层 Module.EnumName),w32.fileio.MODE.W(三层,value 是
# Attribute)被跳过 → "属性访问失败: .W"(SysLibTest L190 根因)。
# 枚举类名只取最内层 attr(MODE),模块路径形态不影响。
if at.value.kind() == ast.ASTKind.Attribute:
inner_at_e: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(at.value)
if inner_at_e.attr is not None:
if HandlesEnum.is_enum_class(inner_at_e.attr) == 1:
member_e: HandlesEnum.EnumMember | t.CPtr = HandlesEnum.lookup_enum_member(
inner_at_e.attr, at.attr)
if member_e is not None:
base_ty_e: llvmlite.LLVMType | t.CPtr = HandlesEnum.get_enum_base_type(
inner_at_e.attr)
if base_ty_e is not None:
name_buf_e: str = pool.alloc(32)
if name_buf_e is not None:
viperlib.snprintf(name_buf_e, 32, "%lld", member_e.Value)
return llvmlite.ConstInt(pool, base_ty_e, member_e.Value, name_buf_e)
return llvmlite.const_int32(pool, member_e.Value)
# fast-fail: 嵌套枚举成员未找到
dbg_ee: HandlesEnum.EnumEntry | t.CPtr = HandlesEnum.find_enum(
inner_at_e.attr)
dbg_mc: int = -1
if dbg_ee is not None:
dbg_mc = dbg_ee.MemberCount
VLogger.error(
f"嵌套枚举成员未找到: {inner_at_e.attr}.{at.attr} "
f"(enum_members={dbg_mc})", "ATTR")
return None
# FakeDuck: 影子结构体属性读取 (str 变量的 per-instance 字段)
# a.__data__ / a.__mbuddy__ → GEP {a}__meta__[field] 并加载
# 对齐 TPC HandlesExprAttr.py L433-442:对象是 Name 变量且存在
# {var}__meta__ 影子(fd2 创建)时,属性访问改走影子结构体。
if at.value.kind() == ast.ASTKind.Name and trans is not None:
fd_nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at.value)
if fd_nm.id is not None:
# pool 持久缓冲(f-string 栈缓冲在函数返回后失效)
fd_meta_buf: str = pool.alloc(256)
if fd_meta_buf is not None:
viperlib.snprintf(fd_meta_buf, 256, "%s__meta__", fd_nm.id)
fd_meta_v: llvmlite.Value | t.CPtr = HandlesVar.lookup_current(
trans.SymTab, fd_meta_buf)
if fd_meta_v is not None:
fd_sty: llvmlite.LLVMType | t.CPtr = HandlesStruct.get_struct_type(
"_str")
if fd_sty is not None:
fd_fe: HandlesStruct.FieldEntry | t.CPtr = \
HandlesStruct.lookup_field(fd_sty, at.attr)
if fd_fe is not None and fd_fe.Ty is not None:
fd_fp: llvmlite.Value | t.CPtr = llvmlite.build_gep_struct(
builder, fd_sty, fd_fe.Ty, fd_meta_v, fd_fe.Index)
if fd_fp is not None:
return llvmlite.build_load(builder, fd_fe.Ty, fd_fp)
# 模块属性访问 (读路径): sys._mbuddy → 加载全局变量 @_mbuddy
# 当 obj 是已导入模块名且不是普通变量时,查找/创建全局变量引用并加载
if at.value.kind() == ast.ASTKind.Name and trans is not None:
nm_ma: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at.value)
if nm_ma.id is not None:
# 先检查是否是普通变量 (优先级高于模块属性)
ma_is_var: llvmlite.Value | t.CPtr = HandlesVar.lookup_var(
trans.SymTab, nm_ma.id)
if ma_is_var is None:
# 先检查是否是 CDefine 编译期常量 (如 ast.CONST_INT / win32base.INVALID_HANDLE_VALUE)
# 模块属性访问的 CDefine 常量需要跨模块查找源 .vp / .vpi 文件
cdef_ma_val: t.CInt64T = HandlesType.lookup_cdefine_constant(at.attr)
cd_local: int = HandlesType.is_cdefine_found()
if cd_local == 0:
# 确定真实模块名:优先从 _from_imports 解析别名
# from_imports 中条目格式 "local_name:real_module"
# 如 win32base:w32.win32base → 别名 win32base 真实模块 w32.win32base
real_mod_name: str = nm_ma.id
if trans._from_imports is not None:
resolved_mod: str = HandlesImports.lookup_from_import(
trans._from_imports, nm_ma.id, 0)
if resolved_mod is not None:
# 复制模块名到新缓冲区(截断于空格、null、或 ':')
rm_len: t.CSizeT = 0
while resolved_mod[rm_len] != ' ' and resolved_mod[rm_len] != '\0' and resolved_mod[rm_len] != ':':
rm_len += 1
rm_buf: str = pool.alloc(rm_len + 1)
if rm_buf is not None:
for rm_i in range(rm_len):
rm_buf[rm_i] = resolved_mod[rm_i]
rm_buf[rm_len] = '\0'
real_mod_name = rm_buf
# 即使 _from_imports 为 None(如纯 `import ast` 无 from-import),
# 也必须尝试跨模块 CDefine 查找,否则 ast.CONST_INT 会被
# 误解析为全局变量引用 @CONST_INT(未定义符号)。
# 构建临时 from_imports "{attr}:{real_module}" 用于跨模块查找
if real_mod_name is not None:
ma_fi_len: t.CSizeT = string.strlen(at.attr) + string.strlen(real_mod_name) + 2
ma_fi: str = pool.alloc(ma_fi_len)
if ma_fi is not None:
viperlib.snprintf(ma_fi, ma_fi_len, "%s:%s", at.attr, real_mod_name)
cdef_ma_val = HandlesExpr._lookup_cross_module_cdefine(pool, at.attr, ma_fi)
if HandlesType.is_cdefine_found() != 0:
# 跨模块字符串 CDefine(如 pyzlib.ZLIB_VERSION = "1.3.2")→
# 字符串全局常量(test_pyzlib.vp L49 ver = pyzlib.ZLIB_VERSION)
if HandlesType.is_cdefine_str() != 0:
cdef_str_v: str = HandlesType.lookup_cdefine_string(at.attr)
if cdef_str_v is not None:
return HandlesExpr.create_global_string(
builder, pool, mod, cdef_str_v, trans)
# 跨模块浮点 CDefine(如 vipermath.U_M_PI)→ 浮点常量
dchk_ma: double = HandlesType.lookup_cdefine_double(at.attr)
if HandlesType.is_cdefine_double() != 0:
return llvmlite.ConstFloat(pool, llvmlite.Double(pool), dchk_ma)
return HandlesExpr._const_int_for_cdefine(pool, cdef_ma_val)
# 检查 nm_ma.id.at.attr 是否是已导入子模块 (如 w32.win32file)
# 如果是子模块,不应创建全局变量,让嵌套属性访问路径处理
is_submod: int = 0
if trans._imported_modules is not None:
submod_len: t.CSizeT = string.strlen(nm_ma.id) + string.strlen(at.attr) + 2
submod_buf: str = pool.alloc(submod_len)
if submod_buf is not None:
viperlib.snprintf(submod_buf, submod_len, "%s.%s", nm_ma.id, at.attr)
if HandlesImports.is_module_imported(trans._imported_modules, submod_buf) == 1:
is_submod = 1
# also check from_imports: at.attr 别名对应真实模块 w32.win32file
if is_submod == 0 and trans._from_imports is not None:
fi_mod_raw: str = HandlesImports.lookup_from_import(
trans._from_imports, at.attr, 0)
if fi_mod_raw is not None:
# 复制模块名到新缓冲区(截断于空格、null、或 ':')
fm_len: t.CSizeT = 0
while fi_mod_raw[fm_len] != ' ' and fi_mod_raw[fm_len] != '\0' and fi_mod_raw[fm_len] != ':':
fm_len += 1
fi_mod: str = pool.alloc(fm_len + 1)
if fi_mod is not None:
for fm_i in range(fm_len):
fi_mod[fm_i] = fi_mod_raw[fm_i]
fi_mod[fm_len] = '\0'
# 检查 fi_mod 是否以 "nm_ma.id." 开头 (如 w32.win32file 以 w32. 开头)
prefix_len: t.CSizeT = string.strlen(nm_ma.id)
fi_mod_len: t.CSizeT = string.strlen(fi_mod)
if fi_mod_len > prefix_len + 1:
match_prefix: int = 1
for pi in range(prefix_len):
if fi_mod[pi] != nm_ma.id[pi]:
match_prefix = 0
break
if match_prefix == 1 and fi_mod[prefix_len] == '.':
is_submod = 1
if is_submod != 0:
return None # 是子模块,交给嵌套属性访问路径
# 特殊处理: t.ASM_DESCR 不是 CDefine,是内联汇编约束命名空间
# t.ASM_DESCR.XXX 由 HandlesExprCall 的 _asm_resolve_constraint 处理
# 此处返回 None,让上层嵌套属性访问路径处理
if string.strcmp(nm_ma.id, "t") == 0 and string.strcmp(at.attr, "ASM_DESCR") == 0:
return None
# ALL_CAPS 模块属性但非 CDefine:先检查模块是否定义了同名全局
# 变量(如 Phase1.PYI_BUF_SIZE: t.CSizeT = 262144 是 i64 全局、
# 非宏,语义同 C 的 extern size_t PYI_BUF_SIZE)→ 按跨模块全局
# 引用加载。仅当全局变量也不存在时才 fail-fast(防 llc
# "use of undefined value")。
if HandlesExpr._is_cdefine_name_pattern(at.attr) != 0:
if self.find_global_in_module(mod, at.attr) is None:
HandlesType.fatal_error(
at,
f"跨模块 CDefine 未找到: '{nm_ma.id}.{at.attr}'(CDefine cross-module lookup failed)",
"TypeError")
# 命中跨模块全局 → 落到下方跨模块全局加载路径
# 非 CDefine: 使用 i8* 作为类型提示 (模块级变量通常存储指针)
i8_ptr_hint: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.Int8(pool))
mod_gv_ref: llvmlite.Value | t.CPtr = self._resolve_module_attribute_global(
pool, mod, trans, at, i8_ptr_hint)
if mod_gv_ref is not None and mod_gv_ref.Ty is not None:
load_ty_mod: llvmlite.LLVMType | t.CPtr = mod_gv_ref.Ty.Pointee
if load_ty_mod is not None:
# 模块级全局数组(如 zdef.zdeflate_len_base: CArray[T, N]):
# 按 C 语义衰减为基址返回(对齐 translate_name_value 局部
# 数组分支 L3383-3388),load [N x T] 数组值无法作 GEP 基址。
match load_ty_mod:
case llvmlite.LLVMType.Array(_, _):
return mod_gv_ref
case _:
pass
return llvmlite.build_load(builder, load_ty_mod, mod_gv_ref)
# 嵌套模块属性访问 (w32.win32file.GENERIC_READ → 查找全局 @GENERIC_READ)
# 当 at.value 是 Attribute 且解析为已导入子模块时,查找 at.attr 作为全局常量
if at.value.kind() == ast.ASTKind.Attribute and trans is not None:
inner_at: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(at.value)
if inner_at.value is not None and inner_at.value.kind() == ast.ASTKind.Name:
inner_nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(inner_at.value)
if inner_nm.id is not None and inner_at.attr is not None:
if HandlesVar.lookup_var(trans.SymTab, inner_nm.id) is None:
# 构建限定模块名: "w32.win32file"
qual_len: t.CSizeT = string.strlen(inner_nm.id) + string.strlen(inner_at.attr) + 2
qual_buf: str = pool.alloc(qual_len)
if qual_buf is not None:
viperlib.snprintf(qual_buf, qual_len, "%s.%s", inner_nm.id, inner_at.attr)
if HandlesImports.is_module_imported(trans._imported_modules, qual_buf) == 1:
# 查找全局变量 at.attr(带限定模块 SHA1 前缀精确匹配,
# 避免多模块同名全局后缀匹配串扰;sha1 未知退化裸名)
gv_nested_q: str = HandlesFunctions._mangle_name_with_sha1(
self._lookup_module_sha1(qual_buf), at.attr)
gv_nested: llvmlite.GlobalVariable | t.CPtr = self.find_global_in_module(mod, gv_nested_q)
if gv_nested is not None and gv_nested.Ty is not None:
gv_ref_nested: llvmlite.Value | t.CPtr = self.make_global_ref(
pool, gv_nested.Name, gv_nested.Ty)
if gv_ref_nested is not None and gv_ref_nested.Ty is not None:
load_ty_nested: llvmlite.LLVMType | t.CPtr = gv_ref_nested.Ty.Pointee
if load_ty_nested is not None:
return llvmlite.build_load(builder, load_ty_nested, gv_ref_nested)
# 未找到全局: 尝试 CDefine 常量查找
# 对于 w32.win32base.INVALID_HANDLE_VALUE 等嵌套模块属性访问,
# 先从限定模块的 .vpi 文件中查找 CDefine 常量值并内联
cdef_val_nested: t.CInt64T = HandlesType.lookup_cdefine_constant(at.attr)
if HandlesType.is_cdefine_found() == 0:
# 构建临时 from_imports "attr:module" 用于跨模块查找
tmp_fi_len: t.CSizeT = string.strlen(at.attr) + string.strlen(qual_buf) + 2
tmp_fi: str = pool.alloc(tmp_fi_len)
if tmp_fi is not None:
viperlib.snprintf(tmp_fi, tmp_fi_len, "%s:%s", at.attr, qual_buf)
cdef_val_nested = HandlesExpr._lookup_cross_module_cdefine(pool, at.attr, tmp_fi)
if HandlesType.is_cdefine_found() != 0:
# 跨模块浮点 CDefine → 浮点常量
dchk_nst: double = HandlesType.lookup_cdefine_double(at.attr)
if HandlesType.is_cdefine_double() != 0:
return llvmlite.ConstFloat(pool, llvmlite.Double(pool), dchk_nst)
return HandlesExpr._const_int_for_cdefine(pool, cdef_val_nested)
# CDefine 未找到: fail-fast 终止编译(不再创建未定义的全局变量引用)
# 之前此处创建 @INVALID_HANDLE_VALUE 等前向引用,但全局未定义,
# 导致 llc 报 "use of undefined value" 错误。
# 正确做法是报错终止,让开发者修复跨模块 CDefine 查找失败的根本原因。
if HandlesExpr._is_cdefine_name_pattern(at.attr) != 0:
HandlesType.fatal_error(
at,
f"嵌套属性跨模块 CDefine 未找到: '{qual_buf}.{at.attr}'(CDefine cross-module lookup failed)",
"TypeError")
return None
# 对于 Name 类型的 obj,直接查找 alloca(不 load 结构体);
# nonlocal 变量(提升函数内)从 env 读共享堆值,避免 lookup_var
# 跨函数引用外层 alloca(SSA 编号跨函数错乱崩溃)
obj_ptr: llvmlite.Value | t.CPtr = None
if at.value.kind() == ast.ASTKind.Name and trans is not None:
nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at.value)
if nm.id is not None:
if HT.is_nonlocal_name(trans, nm.id) != 0:
raw_nl_a: llvmlite.Value | t.CPtr = HandlesNonlocal._load_raw_var_ptr(
trans, nm.id)
if raw_nl_a is not None:
nl_ty_a: llvmlite.LLVMType | t.CPtr = HT.get_nonlocal_type(
trans, nm.id)
if nl_ty_a is not None:
# 对象即指针:Struct/Array 类型直接用堆地址(bitcast 后
# 作为对象指针,不 load);指针类型(如 Ptr(Point))堆里
# 存对象指针,需 load 出对象指针
match nl_ty_a:
case llvmlite.LLVMType.Struct(_, _, _):
obj_ptr = llvmlite.build_bitcast(
builder, raw_nl_a, llvmlite.Ptr(pool, nl_ty_a))
case llvmlite.LLVMType.Array(_, _):
obj_ptr = llvmlite.build_bitcast(
builder, raw_nl_a, llvmlite.Ptr(pool, nl_ty_a))
case _:
nl_val_ptr_a: llvmlite.Value | t.CPtr = \
llvmlite.build_bitcast(
builder, raw_nl_a, llvmlite.Ptr(pool, nl_ty_a))
if nl_val_ptr_a is not None:
obj_ptr = llvmlite.build_load(
builder, nl_ty_a, nl_val_ptr_a)
else:
obj_ptr = HandlesVar.lookup_var(trans.SymTab, nm.id)
# 非 Name 路径:翻译对象值(会 load)
if obj_ptr is None:
# c.Deref(ptr).attr: 属性访问 base 取指针本身而非 load 后的结构体值
# (如 c.Deref(p).a 语义是 (*p).a,直接对 p 做 GEP)
cd_base_rd: llvmlite.Value | t.CPtr = self._get_cderef_attr_base(
builder, pool, mod, at.value, trans)
if cd_base_rd is not None:
obj_ptr = cd_base_rd
elif at.value.kind() == ast.ASTKind.Subscript:
# 下标基座属性访问(如 pts[0].x):list[T]/OOP __getitem__ 类走
# translate_value(返回元素值/地址);普通结构体指针数组走
# get_subscript_ptr(GEP 元素地址,避免结构体值 base=非指针 报错)
if self._sub_base_uses_getitem(pool, mod, trans, at.value) != 0:
obj_ptr = HandlesExpr.translate_value(builder, pool, mod, at.value, None, 0, trans)
else:
obj_ptr = HandlesExpr.get_subscript_ptr(builder, pool, mod, at.value, trans)
elif at.value.kind() == ast.ASTKind.Attribute:
# 嵌套属性基座(如 r.origin.x):取内层字段地址(get_attribute_ptr 递归,
# 地址语义),指针字段(X|t.CPtr)由 _deref_if_ptr_ptr load 解引用
obj_ptr = self.get_attribute_ptr(builder, pool, mod, at.value, trans)
if obj_ptr is None:
obj_ptr = HandlesExpr.translate_value(builder, pool, mod, at.value, None, 0, trans)
else:
obj_ptr = HandlesExpr.translate_value(builder, pool, mod, at.value, None, 0, trans)
if obj_ptr is None or obj_ptr.Ty is None:
return None
# 如果 obj_ptr 是 Ptr(Ptr(Struct))(X|t.CPtr 变量的 alloca),
# load 解引用获取 Ptr(Struct)
obj_ptr = self._deref_if_ptr_ptr(builder, obj_ptr)
# 查找结构体类型信息
# obj_ptr 类型应该是 Ptr(Struct(...))
match obj_ptr.Ty:
case llvmlite.LLVMType.Ptr(struct_ty):
# 确保跨模块结构体的完整定义在当前模块中可用(供 getelementptr 访问字段)
HandlesStruct.ensure_struct_def_in_module(pool, mod, struct_ty)
# 查找字段索引和类型
field_info: HandlesStruct.FieldEntry | t.CPtr = HandlesStruct.lookup_field(
struct_ty, at.attr)
# REnum 变体字段查找: lookup_field 失败时,尝试 REnum 别名
# REnum 结构体字段名是 _p1/_p2/...,但源码用变体字段名访问(如 ty.Pointee)
if field_info is None:
renum_se: HandlesStruct.StructEntry | t.CPtr = HandlesStruct.find_struct_by_type(
struct_ty)
if renum_se is not None and renum_se.Name is not None:
is_rn: int = HandlesClassDef._is_renum_class_name(renum_se.Name)
if is_rn == 1:
alias_idx: int = HandlesClassDef._lookup_renum_field_alias(
renum_se.Name, at.attr)
if alias_idx >= 1:
alias_fe: HandlesStruct.FieldEntry | t.CPtr = \
HandlesStruct._get_field_entry(renum_se, alias_idx)
if alias_fe is not None and alias_fe.Ty is not None:
alias_ptr: llvmlite.Value | t.CPtr = llvmlite.build_gep_struct(
builder, struct_ty, alias_fe.Ty, obj_ptr, alias_idx)
if alias_ptr is not None:
return llvmlite.build_load(builder, alias_fe.Ty, alias_ptr)
# property getter 回退: obj.attr 是 @property/@x.getter 方法时生成调用
# (字段未找到时先尝试,避免报误导性的"字段未找到"错误)
if field_info is None and at.value is not None and trans is not None:
prop_cls_fb: str = HandlesStruct.get_class_name_by_type(pool, struct_ty)
prop_val: llvmlite.Value | t.CPtr = ExprCallCtor._try_property_getter_attr(
pool, builder, mod, at, obj_ptr, trans, prop_cls_fb)
if prop_val is not None:
return prop_val
# 按需过滤: struct_ty 为 i8(简化联合类型)时,通过 AnnotClassName 按类名查找
# 传递 SHA1 以区分跨模块同名类
# base 注解类名提取:Name → 变量注解类名;Attribute → 属性链递归字段注解类名
if field_info is None:
base_class: str = None
if at.value.kind() == ast.ASTKind.Name and trans is not None:
nm_fb: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at.value)
if nm_fb.id is not None:
var_entry: HandlesVar.VarEntry | t.CPtr = HandlesVar.lookup_var_entry(
trans.SymTab, nm_fb.id)
if var_entry is not None:
base_class = var_entry.AnnotClassName
elif at.value.kind() == ast.ASTKind.Attribute:
base_class = self._resolve_attr_field_class(pool, trans, at.value)
if base_class is not None:
cur_sha1: str = trans.ModuleSha1
field_info = HandlesStruct.lookup_field_by_class(
base_class, at.attr, cur_sha1)
# REnum 变体字段别名回退:base_class 是 REnum 类时,变体
# 字段名(如 LLVMType.Pointee → _p2 槽位)不是普通字段,
# lookup_field_by_class 查不到(LLVMType"字段 Pointee 未找到"
# 根因;主路径 L4989-5004 的 REnum 别名查找因 struct_ty 简化
# 为 i8、find_struct_by_type 失败被跳过)
if field_info is None \
and HandlesClassDef._is_renum_class_name(base_class) != 0:
rn_alias: int = HandlesClassDef._lookup_renum_field_alias(
base_class, at.attr)
if rn_alias >= 1:
rn_se: HandlesStruct.StructEntry | t.CPtr = \
HandlesStruct.find_struct(base_class)
if rn_se is not None:
rn_fe: HandlesStruct.FieldEntry | t.CPtr = \
HandlesStruct._get_field_entry(rn_se, rn_alias)
if rn_fe is not None and rn_fe.Ty is not None:
field_info = rn_fe
# 按需过滤成功:bitcast obj_ptr 到 AnnotClassName 对应的结构体类型
# 原始 struct_ty 可能是 i8(X|t.CPtr 简化为 Ptr(i8)),
# 需用实际结构体类型做 GEP,否则 GEP i8 失败
if field_info is not None:
annot_se: HandlesStruct.StructEntry | t.CPtr = \
HandlesStruct.find_struct(base_class)
if annot_se is not None and annot_se.Ty is not None:
annot_ptr_ty: llvmlite.LLVMType | t.CPtr = \
llvmlite.Ptr(pool, annot_se.Ty)
casted_ptr: llvmlite.Value | t.CPtr = \
llvmlite.build_bitcast(builder, obj_ptr, annot_ptr_ty)
if casted_ptr is not None:
obj_ptr = casted_ptr
struct_ty = annot_se.Ty
# 按需过滤更新 struct_ty 后,需重新确保
# 注解类型结构体定义在当前模块可用
# (原始 struct_ty 可能是 i8,ensure 无效)
HandlesStruct.ensure_struct_def_in_module(
pool, mod, annot_se.Ty)
if field_info is not None:
field_ty: llvmlite.LLVMType | t.CPtr = field_info.Ty
# 联合体:bitcast obj_ptr 到 field_ty* 后 load
if HandlesStruct.is_union_by_type(struct_ty) == 1:
field_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, field_ty)
casted: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(
builder, obj_ptr, field_ptr_ty)
if casted is not None:
return llvmlite.build_load(builder, field_ty, casted)
return None
# 普通结构体:GEP + load
field_idx: int = field_info.Index
field_ptr: llvmlite.Value | t.CPtr = llvmlite.build_gep_struct(
builder, struct_ty, field_ty, obj_ptr, field_idx)
if field_ptr is not None:
# 数组类型字段不能 load 为 SSA value,直接返回字段指针
# 用于后续下标访问: self.state[0] → GEP state 字段 → GEP 数组元素
if self.is_array_type(field_ty) != 0:
return field_ptr
loaded_val: llvmlite.Value | t.CPtr = llvmlite.build_load(builder, field_ty, field_ptr)
# 联合类型字段(如 Token | t.CPtr)被编译为 i8*,
# 若 AnnotClassName 指示了具体结构体类型,bitcast 为正确的结构体指针。
# 注意:仅当字段类型本身是指针时才 bitcast(指针→指针);
# 内嵌结构体字段(field_ty=Struct)load 出的是结构体值,
# bitcast 值→指针是非法 IR(llc: invalid cast opcode)。
if loaded_val is not None and field_info.AnnotClassName is not None \
and self.is_ptr_type(field_ty) != 0:
annot_struct: HandlesStruct.StructEntry | t.CPtr = HandlesStruct.find_struct(
field_info.AnnotClassName)
if annot_struct is not None and annot_struct.Ty is not None:
annot_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, annot_struct.Ty)
return llvmlite.build_bitcast(builder, loaded_val, annot_ptr_ty)
return loaded_val
# fast-fail: 字段未找到,立即终止编译(不再打印诊断后返回 None)
# Phase A 依赖未就绪(_deps_pending,父类/依赖类未注册致字段缺失):
# 跳过本属性访问交给下遍重译(对齐 get_attribute_ptr L5541-5547),
# 避免 fail-fast 中断多遍收敛;依赖就绪后仍缺失则走下方真实报错。
if trans is not None and trans._deps_pending != 0:
return None
if trans is not None:
diag_annot: str = None
diag_var_nm: str = None
if at.value is not None and at.value.kind() == ast.ASTKind.Name:
dnm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at.value)
diag_var_nm = dnm.id
if dnm.id is not None:
dve: HandlesVar.VarEntry | t.CPtr = HandlesVar.lookup_var_entry(
trans.SymTab, dnm.id)
if dve is not None:
diag_annot = dve.AnnotClassName
diag_var_txt: str = "?"
if diag_var_nm is not None:
diag_var_txt = diag_var_nm
diag_annot_txt: str = "None"
if diag_annot is not None:
diag_annot_txt = diag_annot
sha1_attr: str = "?"
if trans.ModuleSha1 is not None:
sha1_attr = trans.ModuleSha1
HandlesType.fatal_error(
at,
f"属性访问失败: .{at.attr} (var={diag_var_txt} annot={diag_annot_txt} sha1={sha1_attr})",
"TypeError")
return None
case _:
# fast-fail: 对非指针类型做属性访问(如 i32.x),
# Python traceback 立即终止,不静默返回 None(否则下游 "store to None" 崩溃,丢失错误现场)
attr_nm_ap: str = "?"
if at is not None and at.attr is not None:
attr_nm_ap = at.attr
sha1_ap: str = "?"
if trans is not None and trans.ModuleSha1 is not None:
sha1_ap = trans.ModuleSha1
ln_ap: int = 0
if at is not None and at.lineno is not None:
ln_ap = at.lineno
HandlesType.fatal_error(
at,
f"属性访问失败: .{attr_nm_ap} (base=非指针类型,无法做属性访问 sha1={sha1_ap}:{ln_ap})",
"TypeError")
# fatal_error 后补显式 return 样板:match 末 arm 块必须 terminated,
# 否则掉出末尾被函数收尾 fail-fast 误判(块设计约定)
return None
# ============================================================
# 获取属性访问的字段指针(不加载值,用于赋值)
# 对于 Name 类型的 obj,直接使用 alloca 指针(不 load 结构体)
# ============================================================
def _resolve_attr_field_class(self, pool: memhub.MemBuddy | t.CPtr,
trans: HT.Translator | t.CPtr,
attr_node: ast.AST | t.CPtr) -> str:
"""解析属性链(a.b.c)中 c 字段的注解类名。
联合类型字段(X | t.CPtr)被简化为 Ptr(i8),属性链写入时
(如 lx.tokens_tail.next = tok)obj_ptr.Ty 是 Ptr(i8),
lookup_field(i8, ...) 必然失败。此函数沿属性链递归解析
最终字段的 AnnotClassName(如 tokens_tail → 'Token'),
供按需过滤分支 bitcast 回真实结构体类型后 GEP。
"""
if attr_node is None or trans is None:
return None
k: int = attr_node.kind()
if k != ast.ASTKind.Attribute:
return None
at: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(attr_node)
if at is None or at.attr is None or at.value is None:
return None
# 递归解析基座的注解类名(Name → 变量注解;Attribute → 字段注解)
parent_class: str = None
if at.value.kind() == ast.ASTKind.Name:
nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at.value)
if nm.id is None:
return None
var_entry: HandlesVar.VarEntry | t.CPtr = HandlesVar.lookup_var_entry(
trans.SymTab, nm.id)
if var_entry is None:
return None
parent_class = var_entry.AnnotClassName
elif at.value.kind() == ast.ASTKind.Attribute:
parent_class = self._resolve_attr_field_class(pool, trans, at.value)
else:
return None
if parent_class is None:
return None
# 在父类中查 at.attr 字段,返回其注解类名
cur_sha1: str = None
if trans is not None:
cur_sha1 = trans.ModuleSha1
fe: HandlesStruct.FieldEntry | t.CPtr = HandlesStruct.lookup_field_by_class(
parent_class, at.attr, cur_sha1)
if fe is None:
return None
return HandlesStruct.get_field_annot_class_name(fe)
def get_attribute_ptr(self, builder: llvmlite.IRBuilder | t.CPtr,
pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
node: ast.AST | t.CPtr,
trans: HT.Translator | t.CPtr = None) -> llvmlite.Value | t.CPtr:
"""获取属性访问的字段指针(用于赋值 lhs)"""
if node is None or builder is None:
return None
at: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(node)
if at is None or at.value is None or at.attr is None:
return None
# 对于 Name 类型的 obj,直接查找 alloca(不 load 结构体);
# nonlocal 变量(提升函数内)从 env 读共享堆值,避免 lookup_var
# 跨函数引用外层 alloca(SSA 编号跨函数错乱崩溃)
obj_ptr: llvmlite.Value | t.CPtr = None
if at.value.kind() == ast.ASTKind.Name and trans is not None:
nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at.value)
if nm.id is not None:
if HT.is_nonlocal_name(trans, nm.id) != 0:
raw_nl_a: llvmlite.Value | t.CPtr = HandlesNonlocal._load_raw_var_ptr(
trans, nm.id)
if raw_nl_a is not None:
nl_ty_a: llvmlite.LLVMType | t.CPtr = HT.get_nonlocal_type(
trans, nm.id)
if nl_ty_a is not None:
# 对象即指针:Struct/Array 类型直接用堆地址(bitcast 后
# 作为对象指针,不 load);指针类型(如 Ptr(Point))堆里
# 存对象指针,需 load 出对象指针
match nl_ty_a:
case llvmlite.LLVMType.Struct(_, _, _):
obj_ptr = llvmlite.build_bitcast(
builder, raw_nl_a, llvmlite.Ptr(pool, nl_ty_a))
case llvmlite.LLVMType.Array(_, _):
obj_ptr = llvmlite.build_bitcast(
builder, raw_nl_a, llvmlite.Ptr(pool, nl_ty_a))
case _:
nl_val_ptr_a: llvmlite.Value | t.CPtr = \
llvmlite.build_bitcast(
builder, raw_nl_a, llvmlite.Ptr(pool, nl_ty_a))
if nl_val_ptr_a is not None:
obj_ptr = llvmlite.build_load(
builder, nl_ty_a, nl_val_ptr_a)
else:
obj_ptr = HandlesVar.lookup_var(trans.SymTab, nm.id)
# 模块属性访问 (写路径): sys._mbuddy = mb / os._win32._mbuddy = v
# → store 到模块全局变量。当 obj 是已导入模块名(Name 或嵌套 Attribute
# 模块路径)且不是普通变量时,查找/创建全局变量引用。
if obj_ptr is None and trans is not None:
mod_path_c: str = self._attr_module_path_str(pool, at.value)
if mod_path_c is not None:
# CDefine 常量不可写: 报 FATAL 错误,不创建未定义全局
# (仅单层 Name 模块可精确判 CDefine)
if at.value.kind() == ast.ASTKind.Name:
nm_wma: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at.value)
if nm_wma.id is not None:
if HandlesExpr._is_cdefine_name_pattern(at.attr) != 0:
err_buf_wma: str = pool.alloc(256)
if err_buf_wma is not None:
viperlib.snprintf(err_buf_wma, 256,
"[CD] FATAL: Cannot assign to CDefine constant '%s.%s'\n",
nm_wma.id, at.attr)
VLogger.error(err_buf_wma, "CD")
return None
# 使用 i8* 作为类型提示 (模块级变量通常存储指针)
i8_ptr_w: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.Int8(pool))
wma_ref: llvmlite.Value | t.CPtr = self._resolve_module_attribute_global(
pool, mod, trans, at, i8_ptr_w)
if wma_ref is not None:
return wma_ref
# 非 Name 路径:翻译对象值(会 load)
if obj_ptr is None:
# c.Deref(ptr).attr: 属性写入 base 取指针本身而非 load 后的结构体值
# (如 c.Deref(p).a = val 语义是 (*p).a = val,直接对 p 做 GEP)
cd_base_wr: llvmlite.Value | t.CPtr = self._get_cderef_attr_base(
builder, pool, mod, at.value, trans)
if cd_base_wr is not None:
obj_ptr = cd_base_wr
elif at.value.kind() == ast.ASTKind.Subscript:
# 下标基座属性写入(如 pts[0].x = val):list[T]/OOP __getitem__ 类走
# translate_value(返回元素值/地址);普通结构体指针数组走
# get_subscript_ptr(GEP 元素地址,避免结构体值 base=非指针 报错)
if self._sub_base_uses_getitem(pool, mod, trans, at.value) != 0:
obj_ptr = HandlesExpr.translate_value(builder, pool, mod, at.value, None, 0, trans)
else:
obj_ptr = HandlesExpr.get_subscript_ptr(builder, pool, mod, at.value, trans)
elif at.value.kind() == ast.ASTKind.Attribute:
# 嵌套属性基座写入(如 r.origin.x = val):取内层字段地址
# (get_attribute_ptr 递归,地址语义),指针字段由 _deref_if_ptr_ptr 解引用
obj_ptr = self.get_attribute_ptr(builder, pool, mod, at.value, trans)
if obj_ptr is None:
obj_ptr = HandlesExpr.translate_value(builder, pool, mod, at.value, None, 0, trans)
else:
obj_ptr = HandlesExpr.translate_value(builder, pool, mod, at.value, None, 0, trans)
if obj_ptr is None or obj_ptr.Ty is None:
return None
# 如果 obj_ptr 是 Ptr(Ptr(Struct))(X|t.CPtr 变量的 alloca),
# load 解引用获取 Ptr(Struct)
obj_ptr = self._deref_if_ptr_ptr(builder, obj_ptr)
match obj_ptr.Ty:
case llvmlite.LLVMType.Ptr(struct_ty):
# 确保跨模块结构体的完整定义在当前模块中可用(供 getelementptr 访问字段)
# 与 translate_attribute 一致,避免 opaque 类型导致 GEP 报错
HandlesStruct.ensure_struct_def_in_module(pool, mod, struct_ty)
field_info: HandlesStruct.FieldEntry | t.CPtr = HandlesStruct.lookup_field(
struct_ty, at.attr)
# 按需过滤: struct_ty 为 i8(简化联合类型)时,通过 AnnotClassName 按类名查找
# 传递 SHA1 以区分跨模块同名类
# base 注解类名提取:Name → 变量注解类名;Attribute → 属性链递归字段注解类名
# (如 lx.tokens_tail.next = tok:lx 的注解 Lexer → tokens_tail 字段注解 Token)
if field_info is None:
base_class: str = None
if at.value.kind() == ast.ASTKind.Name and trans is not None:
nm_fb: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at.value)
if nm_fb.id is not None:
var_entry: HandlesVar.VarEntry | t.CPtr = HandlesVar.lookup_var_entry(
trans.SymTab, nm_fb.id)
if var_entry is not None:
base_class = var_entry.AnnotClassName
elif at.value.kind() == ast.ASTKind.Attribute:
base_class = self._resolve_attr_field_class(pool, trans, at.value)
if base_class is not None:
cur_sha1: str = None
if trans is not None:
cur_sha1 = trans.ModuleSha1
field_info = HandlesStruct.lookup_field_by_class(
base_class, at.attr, cur_sha1)
# REnum 变体字段别名回退(同 translate_attribute L5046-5062):
# get_attribute_ptr 赋值路径的按需过滤同样需要——struct_ty 简化
# i8 时主路径 REnum 别名查找被跳过(LLVMType.Pointee 报错根因)
if field_info is None \
and HandlesClassDef._is_renum_class_name(base_class) != 0:
rn_alias_g: int = HandlesClassDef._lookup_renum_field_alias(
base_class, at.attr)
if rn_alias_g >= 1:
rn_se_g: HandlesStruct.StructEntry | t.CPtr = \
HandlesStruct.find_struct(base_class)
if rn_se_g is not None:
rn_fe_g: HandlesStruct.FieldEntry | t.CPtr = \
HandlesStruct._get_field_entry(rn_se_g, rn_alias_g)
if rn_fe_g is not None and rn_fe_g.Ty is not None:
field_info = rn_fe_g
# 按需过滤成功:bitcast obj_ptr 到 AnnotClassName 对应的结构体类型
# 原始 struct_ty 可能是 i8(X|t.CPtr 简化为 Ptr(i8)),
# 需用实际结构体类型做 GEP,否则 GEP i8 失败
if field_info is not None:
annot_se: HandlesStruct.StructEntry | t.CPtr = \
HandlesStruct.find_struct(base_class)
if annot_se is not None and annot_se.Ty is not None:
annot_ptr_ty: llvmlite.LLVMType | t.CPtr = \
llvmlite.Ptr(pool, annot_se.Ty)
casted_ptr: llvmlite.Value | t.CPtr = \
llvmlite.build_bitcast(builder, obj_ptr, annot_ptr_ty)
if casted_ptr is not None:
obj_ptr = casted_ptr
struct_ty = annot_se.Ty
# 按需过滤更新 struct_ty 后,需重新确保
# 注解类型结构体定义在当前模块可用
HandlesStruct.ensure_struct_def_in_module(
pool, mod, annot_se.Ty)
if field_info is not None:
# 联合体:bitcast obj_ptr 到 field_ty*(字段指针用于 store)
if HandlesStruct.is_union_by_type(struct_ty) == 1:
field_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, field_info.Ty)
return llvmlite.build_bitcast(builder, obj_ptr, field_ptr_ty)
# 普通结构体:GEP 获取字段指针
return llvmlite.build_gep_struct(
builder, struct_ty, field_info.Ty, obj_ptr, field_info.Index)
# fast-fail: 字段未找到,立即终止编译(不再打印诊断后返回 None)
# Phase A 依赖未就绪(_deps_pending,父类/依赖类未注册致字段缺失,
# 如 Generator.vp 先于 TypeGenMixin.vp 编译时 self.TraceFrames):
# 跳过本属性写入交给下遍重译(对齐 HandlesAssign L69-78),
# 避免 fail-fast 中断多遍收敛;依赖就绪后仍缺失则走下方真实报错。
if trans is not None and trans._deps_pending != 0:
return None
if trans is not None:
diag_annot_g: str = None
diag_var_nm_g: str = None
if at.value is not None and at.value.kind() == ast.ASTKind.Name:
dnm_g: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at.value)
diag_var_nm_g = dnm_g.id
if dnm_g.id is not None:
dve_g: HandlesVar.VarEntry | t.CPtr = HandlesVar.lookup_var_entry(
trans.SymTab, dnm_g.id)
if dve_g is not None:
diag_annot_g = dve_g.AnnotClassName
diag_var_txt_g: str = "?"
if diag_var_nm_g is not None:
diag_var_txt_g = diag_var_nm_g
diag_annot_txt_g: str = "None"
if diag_annot_g is not None:
diag_annot_txt_g = diag_annot_g
sha1_attr_g: str = "?"
if trans.ModuleSha1 is not None:
sha1_attr_g = trans.ModuleSha1
HandlesType.fatal_error(
at,
f"属性写入失败: .{at.attr} (var={diag_var_txt_g} annot={diag_annot_txt_g} sha1={sha1_attr_g})",
"TypeError")
return None
case _:
# fast-fail: 对非指针类型做属性写入(如 i32.x = ...),
# Python traceback 立即终止,不静默返回 None(否则下游 "store to None" 崩溃)
attr_nm_gp: str = "?"
if at is not None and at.attr is not None:
attr_nm_gp = at.attr
sha1_gp: str = "?"
if trans is not None and trans.ModuleSha1 is not None:
sha1_gp = trans.ModuleSha1
ln_gp: int = 0
if at is not None and at.lineno is not None:
ln_gp = at.lineno
HandlesType.fatal_error(
at,
f"属性写入失败: .{attr_nm_gp} (base=非指针类型,无法做属性写入 sha1={sha1_gp}:{ln_gp})",
"TypeError")
# fatal_error 后补显式 return 样板:match 末 arm 块必须 terminated
return None