Commit Graph
203 Commits
Author SHA1 Message Date
TermiNexus 2b4679073e 维护基线 2026-10-17 23:27:00 +08:00
TermiNexus b51faef1cb fix(tpc): 带注解重声明冲突不再静默丢 store,改为 fail-fast 报错
原有变量分支整段包在 try/except Exception 里,隐式窄化抛出的 FailFastError 被吞成一条 strict 警告 ⇒ alloca 已建、store 整条丢失 ⇒ 之后 load 读到未初始化栈槽(运行期随机崩溃,且无任何编译期提示)。现场:ViperC 由 TPC 引导时 Converter.vp 'di: sizet = ai - (an - dn)'(di 已由 for 循环绑定为 int/i32)→ 生成的 viperc.exe 在 list[AST*].get 处 ASan 越界读(READ 0x1296f00c1808)。现改为捕获后重抛带变量名/两侧类型的清晰错误。
2026-10-14 21:12:56 +08:00
TermiNexus 5f1d2c3300 fix(tpc): 显式强转加宽用 sext、窄化用 trunc,不再被隐式窄化禁令拦下
上一轮把显式强转也统一走 _coerce_value 后,显式窄化(如 ViperC 里 LONG(BUF_B),Windows LLP64 下 sizeof(long)==4,即 sizet/u64 -> int32)被当成隐式窄化而触发 FailFastError,导致 TPC 重编 ViperC 中断。'隐式窄化禁止' 只约束隐式路径,显式转换必须允许 trunc;同时加宽保持 sext 以免 int64(int32(-1)) 变成 4294967295。Test/IntWidthTest 新增 Test 7 覆盖该形态(12/12 PASS)。
2026-10-14 20:57:47 +08:00
TermiNexus 6b87a78239 fix(tpc): 全局注解赋值的一元负号常量按位宽还原带符号真值
_eval_global_const 对负常量直接用无符号读回值取负,负数被物化成 2 的位宽次方减绝对值,例如 -196608 变成 4294770688。改为先按常量自身位宽还原带符号真值再取负,最后按目标类型发射。

与 aaf9330b 同族,覆盖全局路径。
2026-10-14 20:49:52 +08:00
TermiNexus aaf9330b3b fix(tpc): 负数常量与负值整型加宽改为保符号,sext 与常量折叠,不再零扩展
根因:多处手写 zext 做整型加宽,把有符号为负的常量当无符号处理。涉及 HandlesAnnAssign 局部标量初始化两处、HandlesExprCall 的 typedef 与 builtins 强转两处。

一元负号对常量无条件发运行期 neg,常量 -1 变成 neg i32 指令后再被零扩展成 4294967295。

_eval_global_const 用无符号读回值取负,-196608 被物化成 4294770688 即 0x00000000FFFD0000。

现场:b 为 int64 等于 -196608 后,b 右移 1 呈逻辑右移假象,负数右移循环不收敛,见 IntWidthTest Test 5 与 Test 6。

改法:统一收敛到 _coerce_value,加宽用 sext,窄化按其规则 fail-fast;一元负号对常量直接折叠为有符号常量;_eval_global_const 先按位宽还原带符号真值。

另修右移符号性:去掉 emit_binary_op 里按 self._last_var_name 的残留变量名重判,调用方改按 _check_node_unsigned 判定。

验收:Test/IntWidthTest 10 项全 PASS,此前 TPC 侧 5 项通过 2 项失败。
2026-10-14 20:49:35 +08:00
TermiNexus 55a6856c4e fix(tpc): >> 符号性只按左操作数判定,去掉“残留变量名”误判(负数右移发成 lshr)
- 现场:`b: int64 = -196608; b >> 1` 发成 lshr ⇒ 负数变大正数;而同样注解的 `c: int64 = 0 - 196608; c >> 1` 却正确 ⇒ 与 RHS/位置相关(IntWidthTest Test 5/6)

- 根因:emit_binary_op 在 i32/i64 分支又用 self._last_var_name 重判符号性,而它只是“最近见过的变量名”,可能是上一条表达式残留的别的(无符号)变量 ⇒ 误判无符号

- 改动:① ExprGen.emit_binary_op 删除 i32/i64 的 _last_var_name 分支(符号性只信调用方;i8/i16 仍按宽度定无符号);② HandlesExprOps 的 `>>` 改用 _check_node_unsigned(Node.left),覆盖 Name 之外的形态

- 验收:Test/IntWidthTest(Test 5/6),目标 TPC 侧 9/9 PASS
2026-10-14 20:18:26 +08:00
TermiNexus 23b47053d9 chore: 同步 TransPyV→ViperC 改名、new 表达式支持与定宽字面量/窄化适配 2026-10-12 20:59:39 +08:00
TermiNexus 15f77da9d3 chore: 改名同步 TransPyV -> ViperC(源码/配置内的纯替换) 2026-10-12 15:06:48 +08:00
TermiNexus ddf40e02c5 feat(deref): 解引用写入 *p = v 采用 DerefAs 安全语义(方案1)
当 v 是指针且 pointee(p) != typeof(v)(原会因'指针<->非基本类型不匹配'fail-fast)时,把指针 p bitcast 成 typeof(v)* 再按值存 v,即 *(T*(p)) = v(DerefAs 期望写法;旧 DerefAs 的 *(intptr_t*(p))=(intptr_t)v 是错实现)。标量宽度等其余情形维持 pointee(p) 语义不变
2026-10-11 12:22:23 +08:00
TermiNexus 3122793139 fix(define): _try_resolve_nested_cdefine 复用 _make_define_constant,消除模块属性 CDefine 的位宽误判
模块属性访问(win32.win32file.GENERIC_READ)走的是 _try_resolve_nested_cdefine,其内部三处常量物化仍用 val > 0x7FFFFFFF 判 i64,绕过 _make_define_constant 的按值推断;导致 0x80000000 等无符号 32 位 define 变 i64、与 32 位使用点组合时报「64→32 隐式窄化」。改为委托 _make_define_constant(带声明类型按声明位宽,否则按值推断)
2026-10-11 11:51:31 +08:00
TermiNexus 7a061d4d49 fix(define): CDefine 常量位宽按值推断(含无符号 32 位高半区)
_make_define_constant 原用 abs(val) > 2^31-1 判定,把 [2^31, 2^32-1] 的无符号位模式(如 GENERIC_READ=0x80000000、SHARED 掩码)误判为 i64,导致 X | DEFINE 结果为 i64、赋回 32 位使用点时触发「64→32 隐式窄化」。改为按值推断:落进 [-2^31, 2^32-1] 即 i32,与 TPV 的 define_min_width/ExprGenCdef 同口径
2026-10-11 11:46:33 +08:00
TermiNexus 815a6245a5 fix(narrow): HandlesAnnAssign._StoreWithCoerce 补注解式整数窄化 fail-fast
此前该路径对整数窄化静默 trunc,导致 x: <32位> = <64位> 注解声明绕过规则;现与 MemoryOps._coerce_value 同口径:豁免 i1 归一化与落进目标位宽的编译期常量,其余抛 FailFastError
2026-10-11 09:51:03 +08:00
TermiNexus 4f9c234ea8 fix(cdef): external 声明不再遮蔽同名 CDefine 常量;补 t 类名别名解析
两处:① 现场证据(_shared_sym_includes.pickle)显示 all_dc 已含 STD_*-10/-11/-12,但模块 IR 仍 load @STD_ERROR_HANDLE —— 因为 stub 注入的 @X = external global(无初值)让 _lookup_cdefine 的遮蔽判据 AttrName in Gen.module.globals 成立,直接跳过了 CDefine 回退。新增 SymbolUtils.GlobalDeclShadowsDefine:只有**带初值**(本模块真正定义)的全局才遮蔽;仅 external 声明不算。裸名路径同判据改用同一 helper。② all_decl_types 为空的根因:DWORD 的 BaseType 为 None,底层类型存在 OriginalType 字符串 'CUInt32T'(t 类名),原解析只认内层 CTypeInfo。补:按 getattr(t, 类名) 解析该串。离线校验 DWORD|t.CDefine → (32, False)。
2026-10-10 21:25:33 +08:00
TermiNexus d5b4e9a8b9 feat(cdef): define 常量带类型注册——按声明类型物化(DWORD(-11) → i32 位型)
方案 C:CDefine 不再只存无类型 int,而是把**声明整数类型**(位宽 + 符号性)一并登记,使用点按该类型物化,而不是按数值大小猜 i32/i64。链路:① SymbolUtils 新增 DefineIntSignednessOf / DefineDeclIntTypeFromAnnotation(由 T | t.CDefine 注解经符号表 typedef 链解析宽度与符号性)/ DefineDeclTypeValue(按声明类型把值归一成 (位宽, 位型值));② 同模块注册(LlvmGenerator._register_cdefine_constant)与跨模块提取(Phase2Translator._ExtractCDefineConstantsFromPyi,含 _shared_all_decl_types 的 pickle 传递)分别写 Gen._define_decl_types / translator._define_decl_types;③ 物化处(HandlesExpr 裸名两处、HandlesExprAttr._make_define_constant,及 from-import 注册)优先按声明类型出常量。这样 win32file 的 STD_*_HANDLE(DWORD(-10/-11/-12))解析为 i32 位型 0xFFFFFFF5 等,与 GetStdHandle(ULONG) 使用点一致。
2026-10-10 21:20:30 +08:00
TermiNexus 936a43f543 fix(attr): 模块属性访问的 CDefine 查询补 _all_define_constants 回退
上一提交只补了 3 级路径 _try_resolve_nested_cdefine,而 win32file.STD_OUTPUT_HANDLE 是 2 级访问 → 走 _handle_attr_name → _lookup_cdefine,该函数只查 SymbolTable/_define_constants,没有裸名路径已有的 _all_define_constants 回退。includes 的 vpi 模块不会在 SymbolTable 里登记 IsDefine,于是解析落空 → _lookup_module_global 现造一个无定义的 @STD_OUTPUT_HANDLE → 链接期 undefined symbol。补回退(仅在无同名变量/全局时),覆盖 STD_INPUT/OUTPUT/ERROR_HANDLE。
2026-10-10 20:46:52 +08:00
TermiNexus 1050e5a23a fix(decl): stub 跳过联合形态 CDefine + 模块属性访问回退 _all_define_constants
链接报 undefined symbol: STD_INPUT/OUTPUT/ERROR_HANDLE。根因两处:① DeclarationGenerator._generate_global_decl 的 CDefine 跳过判据只认裸 t.CDefine,而 define X: T = V 展开为注解 T | t.CDefine,于是 stub 里写出 @STD_OUTPUT_HANDLE = external global i32(该符号无人定义);② 模块属性访问 win32file.STD_OUTPUT_HANDLE 的 _try_resolve_nested_cdefine 只查 SymbolTable/_define_constants,缺裸名路径已有的 _all_define_constants 回退,解析落空后转去加载那条错误的 stub 全局 → 链接期未定义符号。改为 AnnotationContainsName 判据 + 补 _all_define_constants 回退(仅在本模块无同名全局时,避免遮蔽真实模块全局)。
2026-10-10 20:29:59 +08:00
TermiNexus 04acaa3dd5 fix(funcdef): @life/@willFree 从自定义行为装饰器检测中排除
P2 的编译期生命期标注没有运行时装饰器函数;不排除会生成 wrapper 去调用不存在的 @life/@willFree,并把被装饰的 extern def(stdlib.free)变成本模块 internal 声明。与 TPV 侧 FuncGenDef 同口径。
2026-10-08 21:44:02 +08:00
TermiNexus 5a0eecc22f feat(core): 栈地址逃逸检查(LifetimeError)与标量→聚合 fail-fast
- MemoryOps._IsFrameStackAddr 判据 + HandlesReturn/HandlesAssign 汇点挂钩:禁止栈地址逃逸(&local 与数组退让);新增 LifetimeError(FailFastError) 承载该错误类。 - MemoryOps._coerce_value:标量(整数/浮点)不得初始化/赋值给聚合类型,改为 FailFastError(旧行为让 _store 抛 TypeError,非 strict 下被静默吞掉,聚合未初始化)。
2026-10-08 12:10:24 +08:00
TermiNexus 75aae26881 fix(codegen): arg 糖支持 vprintf 式转发的 va_list,补 stdarg 模块限定分派
_HandleArgLlvm 的回退分支原先把 Gen.variables 里的形参 alloca 直接当 va_list 用,转发形态(vsnprintf(buf,size,fmt,args))因此多一层间接、读错内存;现改为先 load 出槽里的 va_list 指针(自身变参形态仍走 _direct_values 缓冲地址、不 load)。另新增 ModulePath == 'stdarg' 分派,使 stdarg.arg/va_arg/va_start/va_end 命中与裸名相同的内建(点号形式此前会落到外部调用产生未定义符号 @arg)。同时删除 ExprCallHandle 里重复且无调用者的 _HandleArgLlvm 死代码。
2026-10-07 20:15:32 +08:00
TermiNexus 9656bb161a feat(codegen): 值引用自动取址收口到 _adjust_args,补齐 vtable/方法/构造等路径
查表键统一为真实发射出的函数名:FuncGen._adjust_args 开头以 func.name 调用 _apply_auto_addr,于是 direct/vtable 虚分派/构造/静态/__call__/下标赋值/c.Addr 构造/闭包/泛型/方法兜底全部自动取址,不再依赖每个调用点手写;同时删除 16 处冗余显式取址(t.CAutoPtr 的 always 模式不幂等,重复应用会多包一层指针)。登记侧:HandlesImports 把模式登记提前到 FuncName-in-Gen.functions 早退之前并按 func.name 主键写表,_TryLoadStructFromStub 改为无条件调用;HandlesFunctions 在函数对象就绪后按真实名登记,并在 _EmitFunctionForwardDeclLlvm 把逻辑名下的模式播种到 MangledName,覆盖同模块先调用后定义的场景。
2026-10-07 19:59:36 +08:00
TermiNexus 6a7b27e14f chore(c): 清理已删除的 c.CIf 家族残留
c.py 删除 CElif/CElse/CEndif 三个类及其 Library_C 登记;HandlesSpecialCall 删除无人调用的 _HandleCIfLlvm;_REMOVED_CIF_CALL_MSG 的报文补齐 CElif/CElse/CEndif 六个拼写。Converter.py 去掉已无意义的注释掉守卫与 c.CIfndef/c.CEndif 时代注释,示例改为仍在用的 c.CUndef/c.CPragma;ExprParser/ConstEvaluator/HandlesIf 的注释把内部标记统一写成 c.@CIfdef 与 cif 标记。
2026-10-07 19:09:59 +08:00
TermiNexus 2514cee4c9 feat(lang): 同步 cif/celif/celse、值引用注解与 assert 改译 testcheck.check
vast 双路径(Python Parser/ExprParser 与 C 扩展 vast_c.c)新增 cif-celif-celse 语句族与 &T / &temp T 参数注解解析,_parser.pyd 已同步重编。HandlesIf 改认 @CIf 标记并做编译期常量求值,含运行时变量一律 fail-fast,同时删除 c.CIf 家族(c.py)。HandlesAssert 合成 testcheck.check(cond, None, msg) 调用,非字符串字面量消息报 TypeError。FuncGen 调用点按形参模式自动取址(ref 仅接受左值,右值 fail-fast;temp 物化临时栈),SymbolUtils/TypeAnnotationResolver 提供模式识别;t.py 删除 CNeedPtr。
2026-10-07 19:03:29 +08:00
TermiNexus 59378e6a2a fix(codegen): 内建标量别名双参指针包装 + 解引用赋值按 c.Load 原样拷贝
HandlesExprBuiltin: int/char/bool/float/double(v, t.CPtr) 不再被标量分支吞掉第二参,改由 TSpecialCallHandle._HandleTPtrCastLlvm 处理(等价 t.CInt(v, t.CPtr));此前退化为 ptrtoint+trunc,includes/argparse.vp get_bool 因此恒真,--version/--project 一律打印帮助。

HandlesAssign: 目标与右值同为解引用(*a = *b)时按 c.Load 语义做原样字节拷贝,抽出 CSpecialCallHandle.EmitRawCopyLlvm 复用;此前按 fptosi/trunc 转换,mlirlite ConstFloat 的位双关 *&fv_bits = *&val 会把 double 位模式截断为整数,浮点字面量全部错误。
2026-10-05 14:14:25 +08:00
TermiNexus 9fe88831f3 fix(codegen): 修复模块级 str 字面量全局的初始化器缺失
模块级 `X: str = "字面量"` 有两条路径都没写出正确初始化器:

1) 新建全局分支在创建 GlobalVar 之后又查 `VarName not in Gen.module.globals`
(此时刚登记,条件恒假),于是永远走 else 分支、从不写初始化器:全局退化成
无初值的声明,第二遍再被升级成 `common global i8* null`,字符串常量沦为无人
引用的 @NAME_str。只读常量(如 BuildPipeline.MLIR_OPT_CMD)在 mlir 后端
strlen(NULL) 直接崩(ASan 0x0)。

2) 把既有 external 声明升级为本模块定义的分支一律设 common,但 LLVM 要求
common 全局的初始化器必须是零值,字符串字面量会触发 verifier 报错。

新增 _BuildStringPtrInitializer 统一产出「指向 @{Name}_str 常量数组首元素的
GEP」,两处改用它。

验证:TransPyV 自举通过;TPV 顺序回归 77/77 全通过。
2026-10-05 09:26:26 +08:00
TermiNexus 07ba7a4fe5 fix(codegen): 新规范迁移暴露的 4 类解析/代码生成缺陷
1) HandlesExpr._HandleConstantLlvm 引号判定用 AST lineno 直查磁盘行,漏了 _inject_auto_imports 注入行的偏移 N,取到错行;改用 _DiskLineno 还原,并把 col_offset(UTF-8 字节偏移)转字符偏移后再切片。2) Phase2Translator 新增 _DedupComdatDecls:_dso.ll 由各依赖片段拼接,每片段各带同名 comdat 声明,llc 报 redefinition of comdat;在 _AddWeakComdat 入口与两处落盘点去重。3) LLVMTypeMapper._resolve_name 补存储类/类型限定符标记分支(CExport/CExtern/CConst… 返回空串,由 _resolve_bitor 取另一侧真实类型),与 _resolve_attribute 同口径。4) 短别名(int/int64/sizet/char…)在 typedef 右值与类型转换调用处解析不到:SymbolTypeResolver._resolve_typedef_from_value 与 LLVMTypeMapper.resolve_typedef_value_type 的 ast.Name 分支补 builtins typedef / BuiltinTypeMap 兜底(BaseType 可能是 CType 实例,须取 type(x).__name__),HandlesExprCall 的类型转换求值补 _lookup_builtins_typedef 回退并保留双参数 CPtr 指针包装。验证:TransPyV 自举 includes 52/52 + App 76/76 全通过;TPV 顺序回归 77/77。
2026-10-05 09:25:02 +08:00
TermiNexus 65f0e87193 feat(dunder): 补齐 __bool__ 条件真值调度与反向运算符/位运算增量重载
- BaseHandle 新增 _CoerceCondToBool:if/while/and/or/not/三元条件统一归一到 i1,类实例定义了 __bool__ 时优先调度(此前仅 if 支持)

- HandlesExprOps:新增反向运算符重载(__radd__/__rsub__/... 共 12 个),左操作数无普通 dunder 时调度右操作数 __rX__

- HandlesAugAssign:_AUGOP_MAP/_AUGOP_FALLBACK 补齐位运算与移位增量(__iand__/__ior__/__ixor__/__ilshift__/__irshift__)
2026-10-03 00:39:53 +08:00
TermiNexus 2f70bd2733 feat(switch): switch 允许重复 case 标签(match 仍禁止)
- HandlesMatch / MatchGenMixin:值标签去重仅对 match 生效;switch 允许重复 case
  标签(按书写顺序,先出现者生效);通配臂多个(default / case _)仍 fail-fast
- wiki/07-control-flow.md 同步该语义
2026-10-03 00:18:54 +08:00
TermiNexus d6eefcbece fix(switch): case 标签支持裸名(按值引用)并给出常量标签 fail-fast
- 裸名在 match 里是捕获模式(MatchAs),switch 里对齐 C 的 `case NAME:` 语义,
  三端解析均归一为 MatchValue(Name);`_` 仍作通配(等价 default)
- includes/ast/parser.vp、lib/vast/Parser.py、lib/vast_c/vast_c.c 同步实现
- HandlesMatch:case 标签求值非编译期常量时 fail-fast(原先会因取 .constant 崩成
  AttributeError),报文明确“case 标签必须是编译期常量”
2026-10-03 00:14:51 +08:00
TermiNexus 61df58334f fix(match): 补齐 TPC 侧 MatchHandle._CaseLabelKey
- 上一轮重名 case 检测引用了 _CaseLabelKey,但该方法未真正落盘,运行期报
  AttributeError: type object 'MatchHandle' has no attribute '_CaseLabelKey'
- 新增 _CaseLabelKey / _ValueLabelKey:MatchValue(Constant/Name/Attribute 链)、
  MatchSingleton、MatchClass(变体名)、MatchOr(子键拼接);MatchAs/序列返回 None
2026-10-02 23:35:58 +08:00
TermiNexus 74cfaeb917 feat(switch): 新增 switch 语句并禁止 match 内 fallthrough
- vast/Nodes.py:Match 增加 is_switch 字段(含默认值)
- vast/Parser.py:新增 _ParseSwitch(case <常量|枚举变体> / default:,desugar 为 Match(is_switch=True));match / struct / fallthrough 改为硬关键字
- vast/Unparser.py:round-trip 忠实还原 switch 与 default:
- HandlesMatch / HandlesBody:新增 in_switch_case 门控,fallthrough 只允许出现在 switch 的 case 体内
- HandlesFor:补齐嵌套函数的 saved_loop_break/continue_targets 定义(修 NameError),并隔离 in_switch_case
- LLVMCG/BaseGen:新增 in_switch_case 状态
2026-10-02 23:15:42 +08:00
TermiNexus 8b6c36d24f feat(match): 重名 case 标签 fail-fast
HandlesMatch._HandleMatchLlvm 新增前置检查(在产出任何 IR 之前):新增
_CaseLabelKey 提取 case 的 switch 标签(MatchValue 值 / MatchSingleton(None) /
MatchClass 变体名 / MatchOr 组合),标签重复或出现多个 `case _` 时报 FailFastError,
并给出重复的 case 序号与标签键(C switch 不允许重复 case 标签)。
2026-10-02 22:44:34 +08:00
TermiNexus 4353efd8ff fix(flow): 嵌套/协程函数体独立 break/continue 目标,越界即 fail-fast
- HandlesFor._HandleNestedFunctionLlvm:进入嵌套函数体前清空 loop_break_targets /
  loop_continue_targets,退出恢复——原实现会让嵌套函数体内的 break/continue 继承
  外层函数的跳转目标(生成跨函数 br,非法 IR)
- HandlesAsyncFunctions:协程体内循环之外的 break/continue 改为 FailFastError,
  不再静默不生成跳转
2026-10-02 22:40:04 +08:00
TermiNexus 34c4eeff02 fix(flow): 循环/match 外的 break/continue 改为 fail-fast
HandlesBody:loop_break_targets / loop_continue_targets 为空时不再静默不生成跳转,
直接 FailFastError 暴露——原实现会让 break/continue 无声无效果(不兜底原则)。
2026-10-02 22:35:26 +08:00
TermiNexus cc0615e519 feat(match): fallthrough 软关键字 + case 内 break 跳出 match
- vast/Parser:语句位置识别裸 `fallthrough`(仅整条语句命中),脱糖为 c.NoBreak()
  标记调用;作标识符的写法不受影响
- HandlesBody:`c.NoBreak()` 标记语句不产出 IR(语义由 HandlesMatch 消费)
- HandlesMatch:新增 _HandleMatchCaseBody(case 内 break 目标 = match 的 AfterBB,
  经 loop_break_targets 压栈/出栈,不影响外层循环)与 _FallThroughTarget;
  9 处 fallthrough 由"只留无终结符块"改为显式 br 下一个 case body
2026-10-02 22:33:26 +08:00
TermiNexus 9ff301d747 refactor(types): 移除 list[T, N] 的定长数组遗留判据,统一走 _list.vp
- SymbolUtils.IsListAnnotation 不再把双参 list[type, count] 判为栈上定长数组:
  list 是 includes/_list.vp 的堆上 STL 容器泛型类,list[T] / list[T, N] 都走泛型特化
- ParseListAnnotation / ListAnnotationParseResult / ExtractListFromBinOp 相应收窄为
  只处理 t.CArray / rawarray / array,并同步文档串
- HandlesAnnAssign / HandlesBase / HandlesFunctions / HandlesImports / SymbolUtils 的
  注释改按 CArray 形态表述
2026-10-02 22:16:30 +08:00
TermiNexus 0efdc9e111 fix(expr): 整型与 None 比较统一按 == 0 / != 0 处理
- HandlesExprOps.py:is / is not / == / != 遇整型与 None 比较时直接与 0 比较,
  避免把 i8 当作空指针 load;检查置于类型对齐与指针分支之前
- README.md:同步引用 wiki 第 12 章(12-compilation-system.md)
2026-10-02 22:07:06 +08:00
TermiNexus 5eef845e5b feat(struct): 嵌套 class/struct 发射为独立结构体 {外类}_{内类}
用户 2026-10-01 定:struct 内允许叠加 struct(与 class 内写 class 同口径)。
此前类体里的嵌套 ClassDef 在两个编译器里都被**静默丢弃**(字段遍历只认 AnnAssign、
方法遍历只认 FunctionDef),现按既有 REnum/union 嵌套命名先例发射为独立结构体。

命名与引用口径(双端同源):内层结构体 = {外类}_{内类}(多层递归 A_B_C);
引用两种写法都可用 —— 限定名 `外类.内类`、扁平名 `外类_内类`。

- HandlesClassDef.py:新增 _EmitNestedClassDefs(deepcopy + 改名后递归
  _EmitClassLlvm,deepcopy 避免污染源码 AST)+ _RegisterNestedClassName
  (SymbolTable 登记裸名/点号名/扁平名,对齐 REnum 变体的三键先例)。
- TypeAnnotationResolver.py:_from_node_attribute 增加嵌套分支 —— 限定名按**扁平名**
  取条目并让 Name/BaseType 都是扁平名(否则会拼出 t.CStruct(name='内类'),
  与声明端 `%"{sha}.外类_内类"` 不同源 ⇒ 布局分叉)。
- DeclarationGenerator.py:_get_type_str 支持 `外类.内类` → `%"{sha}.外类_内类"`;
  _generate_class_decl 递归产出嵌套类型的完整声明(否则跨模块只拿到 opaque,
  指针可用但字段偏移全失效)。
- Phase1Generator.py / Phase2Translator.py:跨模块注册表按扁平名登记嵌套类。
  Phase2Translator 原先用 ast.walk 把嵌套类按**短名**登记(占住平坦命名空间、
  与定义端不同源),改为递归扁平名。

实测(TPC 侧):嵌套类名字登记 {A_B, A_C, A_C_D};`x: A.B` → CTypeInfo(A_B);
声明端 `%"cafe.A" = type { i32, %"cafe.A_B" }` + `%"cafe.A_B" = type { i32 }`
+ 多层 `%"cafe.A_C_D" = type { i32 }`。

已知缺口(下一步):TransPyV 侧跨模块走 .vpi,VpiReader 只登记顶层类 ⇒ 跨模块引用
嵌套类型仍需在 VpiReader 侧补登记(同模块路径已通)。
2026-10-01 23:29:54 +08:00
TermiNexus 42c13e372b fix(HandlesAssign): 对"指针的指针"右值的自动解引用收窄为取地址形态
原判据只看右值的 LLVM 类型(Ptr(Ptr(...)))就 load 一层,于是显式 cast 的结果
(`entry = str*(uint64(argv) + idx*8)`,右值是 i8**)被多解一次引用 → 静默取到野指针
(includes/argparse.vp 的 parse_args 因此 ASan access-violation 0xffffffffffffffff)。

现限定为 Name/Attribute/Subscript 这类"取地址形态"(求值结果是存储地址,即"对象即指针"
返回 alloca/字段地址)才解引用;Call(显式 cast、构造/普通函数调用)与其它表达式给出的
是值本身,不再多解一层 —— 与 TransPyV 侧行为对齐(其 _deref_if_ptr_ptr 只用于字段访问
与调用分发,不在普通赋值路径)。
2026-10-01 18:33:21 +08:00
TermiNexus b18d57fa38 perf(全仓库/正则): 字面量正则全部改模块级预编译常量(15 文件 / 115 个调用点)
背景(prof 基线 PROF/20260930_224857,主进程 433.69s):正则这一族合计 ≈45s ——
`re.match`(未预编译入口)被调 1970 万次 / 自身 10.70s、它内部 `Pattern.match` 占 8.06s,
`re._compile`(缓存查找)1975 万次 / 7.19s,`re._make`(真正编译)274 万次 / 7.62s
(说明 re 的 512 条缓存已被打穿,每 7 次调用就有 1 次重新编译)。
每次 `re.match(字面量, s)` 都要做"模式串哈希 → 缓存查找";预编译后这层全省掉。

改动:把 15 个文件里 97 处"字面量当模式"的 `re.match/search/sub/subn/fullmatch/
findall/finditer/split(...)` 提升为模块级 `_RE_*: re.Pattern[str] = re.compile(...)`,
共 115 个调用点(含同字面量跨处共用)。模式串与 flags 一字未改(flags 从调用处
搬进 re.compile,含原先以**位置参数**传入的 flags)。4 处拼接型动态模式按设计不动
(FuncGen 2 处、HandlesImports 2 处)。

核验(不靠肉眼):写了 AST 指纹对拍脚本,对每个文件取 HEAD 版与新工作区版,把
"旧 re.<m>(字面量, 其余实参, 关键字) + flags" 与 "新 _RE_X.<m>(实参, 关键字) +
常量 (模式, flags)" 做多重集比对 —— 15 个文件、115 个调用点全部逐一等价(0 失败),
另查无重名常量、无遗留字面量调用、GetDiagnostics 全空。
对拍过程中查出一处子代理抄写错误:`_RE_GLOBAL_DSOLOCAL_MULTILINE` 丢了行首锚 `^`
(原文注释明确"使用 ^ + MULTILINE 避免匹配字符串常量内容中的 define/declare"),已修正。

同轮经查证后**未做改动**的两项(避免把假热点当真事修):
- VLogger 的 `re.sub` 6.43s:profile 当时的 VLogger 版本里根本没有 `import re`
  (`git show 3658ab06:lib/core/VLogger.py` 已确认),属 C 帧错挂,不做。
- tokenize 274 万次:那是 `_generate_tokens_from_c_tokenizer` 的 **token 产出次数**
  (≈350 个整文件的词法量),SymbolUtils 的 4 个 tokenize 调用点 nc 仅数百,且已有
  `_EXPAND_CACHE` 去重;再加一层 token 缓存无收益,不做。
2026-09-30 23:47:36 +08:00
TermiNexus e2c84daa65 fix(Handles): 成员表登记改实时按名判重,消成员表重名条目(AST.children 重复)
类体字段收集循环每轮都可能重入并清空重填同一张成员表(CollectAnnAssignMember → 泛型特化 → _generate_structs → _TryLoadClassMembersFromPyi(force=True) → HandlesImports.py:3415,该 force 分支不受字段收集中保护),而三处只在循环前快照一次名字集合的判据即时失效,会把嵌套帧已登记的同一字段再追加一条:AST 的最后一个字段 children(注解含 list[...] 泛型容器,是全类唯一触发该重入的字段)被登记两次 ⇒ 表 8 项/去重 7 项、重名条目 children@7;该表再被祖先块展平原样拷给 AST 的每个子类 ⇒ StructGen「[结构体布局] 成员表含重名条目」在大量 AST 子类(ExprNode/Stmt/MatchCase/ExceptHandler…)上齐发。三处一律改为实时读表判重(HandlesClassDef 自有字段段与 _PreRegisterClassMembers、HandlesImports 跨模块自有字段段),判据语义与改前逐字相同;自有字段段刻意只跳过本轮循环开始后才出现的同名,以保住字段名遮蔽 fail-fast 与既有布局语义。
2026-09-30 19:31:21 +08:00
TermiNexus a7d8589af6 fix(HandlesAnnAssign): 补上指针占位类名判据的定义,修 _PTR_MARKER_CLASS_NAMES 未定义
「注解声明类台账」只引用未定义 _PTR_MARKER_CLASS_NAMES,_DeclCls 为真时即 NameError(翻译 lib/Projectrans/Utils.vp 触发)。定义为 frozenset 常量并改用 _IsPtrMarkerClassName,覆盖裸名 / sha1.rawptr 前缀 / rawptr[X] 下标三种占位形态。
2026-09-30 19:10:59 +08:00
TermiNexus b0aa9f6d76 fix(Handles): 注解声明的类是权威类型,赋值不得改掉它
新增 var_annotated_class 台账(AnnAssign 声明完成时登记真实类名,随 var_struct_class 在函数作用域清空),HandlesAssign 三处「右值推出类」登记点命中该台账时不再覆盖声明类型。否则 node: rawptr[AST] = Name(...) 会把 node 记成 Name,后续 node = <AST*> 被判成被禁止的向下转型(includes/ast/parser.vp:1919)。
2026-09-30 19:09:37 +08:00
TermiNexus 2e7bac1816 fix(HandlesImports/HandlesClassDef): 剥掉"祖先块前缀的陈旧半截副本",消除成员表重名条目
类别:a(编译器缺陷)

现场(用户构建日志):
  WARN[StructGen]: [结构体布局] 类 'ClassGenDef' 的成员表含**重名条目**:
  表 50 项 / 去重 42 项,现有 body 42 槽恰等于「去重名数 + vtable」

判据(磁盘实证 + 结构推导,非猜测):
  · ClassGenDef 自身 **0** 个字段(全是方法),父类 ClassGenBase 恰 **42** 个字段;
    temp / output 两份 stub 的 ClassGenDef body 都是 **42** 槽,且槽序 = ClassGenBase
    的字段声明序 ⇒ body 正确、**表多出 8 条**(50 = 42 + 8)。
  · ClassGenBase 自己的 Phase-1 stub 就是 42 槽(不是 8)⇒ 多出的 8 条并非"父类 stub 短",
    只能来自**内存里父类表尚未收集完就被展平**(写入了半截祖先块 Block[:k],k=8)——
    正是 PrependInheritedBlock 文档里写明的"已知残留"。
  · 重名条目不是布局槽位(见 FuncGen._get_member_offset 的重名段),却让
    _member_base 的 _Delta = 42 - 50 = -8 走进类型校准,并让回退路径的
    base + 表内下标 整体偏斜(同族现场:MatchCase / TypeGenResolve 的偏移算错)。

修复(判据全部为正向事实,且与插入方向无关):
  · 新增 NormalizeAncestralCopies(Table, Block, InsertAtHead) -> (新表, 是否已含完整块):
      ① 完整块在**头**、尾部挂前缀副本 ⇒ 剥尾(副本是被后来的整块前置推过去的);
      ② 完整块在**尾**、头部挂前缀副本 ⇒ 剥头(副本先写入、整块随后追加);
      ③ 完整块尚未出现 ⇒ 剥插入边(仅当这些名字已另有正本,见下)。
    剥掉的每一项在(表里已有的 / 随后插入的)完整块里同名同型 ⇒ **不丢布局信息**,
    与 PrependInheritedBlock 性质①"不删块内条目"不冲突(剥的是副本)。
    第一版曾"两端都剥",离线冒烟立刻暴露会把**正本**当副本剥掉([A,B,C,D,A,B] →
    [C,D,A,B],再前置整块反而多出 C,D 两份)⇒ 改为先定位完整块在哪一端、再剥另一端。
  · TrimStalePrefixCopy 判据:插入边连续游程与 Block[:j] 逐项同 name 同 IR 类型,
    1 ≤ j < len(Block),且这 j 个名字在表里**各另有一份**(计数 ≥ 2)——最后一条把
    "子类自有字段恰好与祖先块前缀同名同型"的误判排除。
  · 两个插入点(HandlesImports.PrependInheritedBlock 前置 / HandlesClassDef 展平追加)
    统一走该函数,判据对称;真正剥除时各留一条 WARN(含条数与前后表长)。
  · PrependInheritedBlock 原有两条幂等提前返回("表头等于块" / InheritedBlockAlreadyPresent)
    合并为 _HasFull 一条(语义等价),并更新其"已知残留"段落。
  · StructGen 的重名 WARN 追加**逐条明细**(名@表内下标):单看"表 50 / 去重 42"无法区分
    "祖先块前缀的陈旧副本"与"两条收集路径各登记一次"(两种成因修法完全不同)。

核验:py_compile + compileall 全 lib 通过;离线冒烟(调用真实函数,8 组用例 × 两个插入方向):
①/② 均 6→4 正确剥除,"无重复 / 带自有字段 / 类型不同 / 无正本 / 空表"均不改动。
未放宽任何 fail-fast。
2026-09-30 18:41:24 +08:00
TermiNexus c2f53852ee fix(Handles/PythonParser/DeclarationGenerator): 补齐 3 处"调用了不存在成员"的死路径 + 1 处被 hasattr 掩盖的静默降级
类别:a(编译器缺陷)

现场:翻译 TransPyV/App/lib/core/Gen/Call/CallGenLeafKit.vp 时终止编译
  RuntimeError: 翻译 ... 失败: 'ClassHandle' object has no attribute '_PreRegisterClassMembers'
  (FuncGen.py:965 有调用,方法全仓无定义)

按要求做了全仓静态扫描(ast:属性访问 vs 全仓属性定义;symtable:未定义全局名),共修 4 处:

1) HandlesClassDef.ClassHandle._PreRegisterClassMembers(缺失 → 实现)
   调用点:FuncGen._get_member_offset 的"struct body 已存在、class_members[类名] 为空"分支
   —— 该类的成员表在本次翻译里从未被收集,而按名定位字段槽位必须有表。
   实现与既有两处成员收集段同源(CollectAnnAssignMember → 追加 (名, 类型)),但**只做收集**:
     · 不展平继承前缀(调用方随后经 _member_base → EnsureClassMembersFlattened 补齐);
     · 不调用 _generate_structs(body 已存在,重入 set_body 会把残缺布局永久固化);
     · 只追加尚无同名的条目(幂等,重复命中不会把表撑长)。
   跳过 __provides__/__requires__/__require_must__(非布局槽位),并登记字段限定符。

2) HandlesExprAttr.ExprAttrHandle._load_bitfield_member(缺失 → 实现)
   读路径 7 处调用(位域分支)全部指向不存在的方法 ⇒ 任何位域字段读取都会 AttributeError。
   实现与 HandlesAssign._store_bitfield_member 严格对称:位域类 body 只有一个 storage 槽
   (elements[0]),字段位置由 class_member_bitoffsets/bitfields/signeds 表达,
   读取 = `(storage >> 位偏移) & ((1<<位宽)-1)`,有符号字段再做符号扩展;
   缺位偏移 / 位宽 ≤ 0 / struct 未具化 / storage 非整数一律 fail-fast(不返回 i32 兜底)。

3) PythonParserMixin 的 __set_default__ 分支(幽灵调用 → 明确报未支持)
   原调用 self._handle_set_default_in_parse(...) 全仓无定义。核实:该注解在 TransPyC/lib 内
   无任何消费端(只有 t.py 的定义与 StubGen/Converter 的"剥离该调用保留类型"),
   includes 全仓亦无源使用 ⇒ 不静默丢弃源码语义,改为 NotImplementedError 明确报"未支持"。

4) DeclarationGenerator._llvm_type_size_str 的数组分支(静默降级 → 修好)
   原写作 `self._ARR_TYPE_RE`(少一个 A)并以 hasattr 守卫 —— 该属性全仓从未定义 ⇒ 守卫恒假
   ⇒ 数组字段一律落到"未知类型 → 8",与 docstring 声明的"_EmitREnumLlvm 尺寸口径对齐"不符。
   改用本类已定义的 _ARRAY_TYPE_RE,并去掉守卫(属性再丢失应当响亮失败)。

核验:compileall 全 lib 通过;属性扫描剩余 26 条全为仓外对象(logging / vast AST /
llvmlite Value·Instruction·IRBuilder / 内置 Exception.args / __class__ / __dict__);
symtable 未定义全局名仅 __file__(运行时内置)。未新增/放宽任何 fail-fast。
2026-09-30 18:23:25 +08:00
TermiNexus ab906f2cde fix(HandlesExprCall): 构造的 self 按 callee 形参类型解析(同名类消歧),并合并"sha1 全名键"分支
类别:a(编译器缺陷)

现象(用户重跑后仍复现):includes/ast/parser.vp:1774
`kw: rawptr[AST] = KwNode(ps.pool, None, val)` 报
  FailFastError: 指针隐式转换已禁止(非基本类型(结构体/类/联合)):调用
  8312ce86a2fd82c0.Keyword.__new__ 第 0 个实参 %"fef783da4d24330d.Keyword"*
  不能隐式转为形参 %"8312ce86a2fd82c0.Keyword"*

根因:`Gen.structs` 的键恒为**短名**(StructGen._generate_structs 以 ClassName 作键,
IR 名才带 sha1 前缀),别名注册只是**浅拷贝**(HandlesImports
`Gen.structs[asname] = Gen.structs[name]`)。Keyword 同时存在于 ast.tokens
(fef783da4d24330d) 与 ast.astaux(8312ce86a2fd82c0):裸名 'Keyword' 先被 .tokens 占用,
别名 'KwNode' 键又只是浅拷贝 ⇒ 构造的 self 按名字取到 tokens 的实例,而 callee
(_find_function('KwNode.__new__'))命中的是 astaux 的。llvmlite 的类型相等按 **IR 名字**
判定(实测同名不同对象 == 为真,见提交说明)⇒ 两份不同名实例必被
_ClassifyPtrImplicitConvert 拒绝。

上一版修复(ba7e117e)不成立:它按"callee 限定类名 + 与 ClassName 同短名"改判,而
ClassName 是别名 'KwNode'、限定类名是 '...Keyword',同短名判据恒假 ⇒ 从未生效。本版删除。

修复:
  1) 新增 _CalleeSelfType():self 的 struct 直接取 callee(__new__ → __before_init__ →
     __init__)第 0 个形参的 pointee —— 正是 _adjust_args 的比较对象,无歧义;形参上的
     实例未具化时按 IR 名换同名已具化实例(_FindStructByIrName;IR 里类型身份就是名字)。
  2) _get_struct_type 统一解析序:callee 形参 → sha1 全名键 → 名字键。
  3) 合并"sha1 全名键命中"分支:原实现只解析 StructType、**不产出 alloca / 构造调用**,
     控制流随后落到函数末尾 `return i32 0` ⇒ 该路径上的跨模块构造会**静默不构造**
     (同为类别 a,一并修掉)。
2026-09-30 17:00:50 +08:00
TermiNexus ba7e117ecb fix(HandlesExprCall): 同名类构造消歧——ctor 的 self 实参按 callee 限定类名解析,不再回退有歧义的短名
类别:a(编译器缺陷)

现象:includes/ast/parser.vp:1774 `kw: rawptr[AST] = KwNode(ps.pool, None, val)` 报
  FailFastError: 指针隐式转换已禁止(非基本类型(结构体/类/联合)):调用
  8312ce86a2fd82c0.Keyword.__new__ 第 0 个实参 %"fef783da4d24330d.Keyword"*
  不能隐式转为形参 %"8312ce86a2fd82c0.Keyword"*

根因:Keyword 同时存在于 ast.astaux(8312ce86a2fd82c0) 与 ast.tokens(fef783da4d24330d)。
  KwNode 是 `from .astaux import Keyword as KwNode` 的导入别名,_ctor_module_sha1
  只从 ModulePath(ast.attr 形态) 解析 ⇒ 裸名/别名调用拿不到 sha1;
  _get_struct_type 回退短名 Gen.structs.get('Keyword') ⇒ 撞上 tokens 的同名实例。

修复:从**已解析到的构造函数对象**的 IR 名({sha1}.{Class}.{method})取限定类名,
  仅在「同短名 + 已具化」时改判——同短名保证不会把无关类引进来,已具化保证
  _allocaEntry 的尺寸正确(opaque 实例算不出大小)。取不到限定名时保持原短名回退,
  对无同名冲突的类零变化。
2026-09-30 16:38:09 +08:00
TermiNexus cad70fa8f3 chore(诊断): len 分派 fail-fast 追加成员槽来源明细(项数/base/下标/名序/布局槽/实例同源判定)
类别:b(机制漏豁免)/诊断增强,不改变任何成功路径。

现场:编译 StmtGenDeclMixin.vp:242 `len(mcc_pre.children)` 报
  [fast-fail] len(mcc_pre.children) 分派 'list[10f3f43ddd447eec.AST*].__len__' 失败:
  类='MatchCase' 偏移=6 接收者类型=i32
静态可查到的**正确**布局(temp/8c9751aec486f41c.stub.ll 实测 10 槽):
  [0]i8*(vtable) [1]AST*(parent) [2]MemManager*(pool) [3..6]i32(lineno/col_offset/
  end_lineno/end_col_offset) [7]list[AST*]*(children) [8]AST*(pattern) [9]AST*(guard)
声明侧(.vpi):`class AST` 7 字段 + `class MatchCase(AST)` 2 字段 ⇒ 表 9 项、
children 表内下标 6 ⇒ 正确 base=1 ⇒ 正确偏移 7。而失败现场是 6 ⇒ **base 被算成 0**
(表 10 项触发 `_Delta==0 ⇒ base=0`,或取值处实例与类键实例不同源)——
只看 `偏移=6` 无法区分,故把两侧读数打进报文。

- `AssignHandle._DescribeMemberSlot` 增可选 `StructHint`:base 与失败现场**同源**
  (不传时行为与改前逐字一致)。
- `HandlesExprBuiltin._HandleLenLlvm` 的 len 分派失败路径(仅该路径)追加上述明细:
  `class_members[类] 项数/base/下标/声明槽类型`、**完整名序**、
  `布局 struct name+元素数+elements[offset]`、`structs[类] 与布局是否同一对象`。
  明细生成失败只追加一行说明,绝不掩盖原始 TypeError。

静态核验:py_compile 通过;全仓 symtable 未定义名扫描 0 条。
2026-09-30 16:16:38 +08:00
TermiNexus 9afd6a45ed fix(HandlesClassDef/HandlesImports): 展平块幂等——块已整段在表头/尾时不再重复插入(修 41→73 的 body 分叉)
类别:a(编译器缺陷,且已泄漏进 emit 的 IR)。

on-disk 实证:Phase 1 声明(temp/<sha1>.stub.ll)与 Phase 2 emit(output/<sha1>.stub.ll)
对同一类的 body 槽数不一致,共 7 个类,全在 Generator/ClassGen/TypeGen 继承链上:

  TypeGenCType 41 → 73(+32)   TypeGenCDef 41 → 73   TypeGenErr 41 → 73
  ClassGenMixin 42 → 50(+8)   ClassGenVTable 42 → 50
  ClassGenEnum 42 → 50          ClassGenDef 42 → 50

逐槽比对:output[0:41] == temp,且 **output[41:73] == temp[9:41]** —— 多出的 32 槽
正是"追加前那张表**尾部** 32 项的原样副本"。下游即 Repl.vp:802
`GenApi._g_gen.TypeGenCType.CTypedefNames` 因两份 body(41/73)歧义而
"基座 pointee 未匹配 Gen.structs 中任何 struct"。

成因:两条展平路径判据**不对称**——
- 导入侧 `PrependInheritedBlock`:前置,且自带"表头是否等于块"的幂等判据;
- 定义侧 HandlesClassDef 展平:直接 `class_members + _flat` **追加**,**无任何判据**。
一旦块已整段位于表尾(或表中段),就会被再插一次,表里多出 len(块) 条重复条目,
body 随之多出同名槽。

修法:新增唯一判据源 `HandlesImports.InheritedBlockAlreadyPresent(Table, Block)`——
"块是否整段位于 `Table` 的**头部或尾部**(名字 + IR 类型逐项相同)",
两个插入点都接它(导入侧前置 ⇒ 头部;定义侧追加 ⇒ 尾部)。
刻意**不判表中段**:中段的同序列与"合法的同名并存"(子类在自有段重复声明父类字段,
body 本来就有两个槽)不可区分,判成"已存在"会误删本该有的槽;真出现中段重复时
仍由 StructGen 的重名条目告警兜住。跳过重复时打印一行 WARN(类名 / 块长 / 表长),
只跳过重复、不改其余布局。

冒烟(内联 python,无临时文件):现场形状(块 = 表[9:41])→ True;中段 → False;
仅名字相同而类型不同 → False;空块 / 块比表长 / 非 tuple 条目 → False。
静态核验:compileall 通过;全仓 symtable 未定义名扫描 0 条。
2026-09-30 15:44:13 +08:00
TermiNexus 83453c68db fix(HandlesImports): 解冻 struct 加载——缓存键缺失也删、索引陈旧定点再读、opaque 条目不再压住好 body
类别:a(编译器缺陷)。三处都会把"该类本 Gen 内始终 opaque"**永久固化**,
下游表现为 Repl.vp:802 `GenApi._g_gen.TypeGenCType.CTypedefNames` 报
"基座 pointee 未匹配 Gen.structs 中任何 struct(同短名候选=['TypeGenCType'],
全部未命中)"——而磁盘上 temp/<sha1>.stub.ll 明明有 41 槽 body。

1) `_TryLoadStructFromStub` 尾部缓存键删除判据:原只在"该键**有**实例且 opaque"时删;
   若索引里**有该类的条目**、而该键此刻根本没有实例(例如只落了 sha1 全名键、短名键
   尚未建立),这一轮的成功缓存被永久留下 ⇒ 之后每次同名查询都在入口直接 return。
   现改为"无可用 body(缺失或 opaque)且索引有条目"即删键,失败可重试。

2) `_entry_priority`:把 `is_opaque` 提到 `is_temp` 之前。原顺序让**陈旧的 output 占位
   条目压住 temp 里已有的好 body**(两侧都有 body 时仍保持 output > temp 原判据)。

3) 新增 `_ReReadStructBodyFromDisk`:`_build_stub_index` 只按"文件名集合"增量,假定
   stub `只增不改`;而 Phase 2 会**原地重写** output/<sha1>.stub.ll(译文生成点 :2194、
   依赖变化重组点 :1785-1790)⇒ 新 body 永不进索引。现在"选中的索引条目没有 body"
   这条失败路径上按磁盘当前内容**定点再读一次**(只读那一个文件,取不到即维持原状)。

静态核验:compileall 通过;全仓 symtable 未定义名扫描 0 条。
2026-09-30 15:44:10 +08:00
TermiNexus 393ffefcd5 fix(HandlesAssign): self 属性写入的基址收口统一为 _ConcretizeGepBase;收口失败不再静默丢写
类别:a(编译器缺陷)。

1) 收口统一:`self.字段 = v` 的基址此前走**内联**孪生体搜索,缺
   `_TryLoadStructFromStub` 一步 —— 类键那份仍 opaque 且 Gen.structs 里尚无已具化
   孪生体时(body 只在 stub 文件里待加载),内联版会一路走到 GEP 撞 opaque 守卫
   (现场:includes/ast/astaux.vp:165 `self.arg = _copy_str(pool, arg)`,基址 pointee
   是 opaque 的 %"8312ce86a2fd82c0.Keyword"*,而同短名 'Keyword' 键那份已具化 10 槽)。
   现改为调用读路径的**同一判据源** HandlesExprAttr._ConcretizeGepBase /
   _ConcretePointeeFor:① 类键那份 + stub 补 body → ② 同短名已具化孪生体 →
   ③ 都拿不到则原样返回、由守卫报根因。偏移与 GEP 同源(返回实例同时作为
   _get_member_offset 的 StructHint)。

2) 收口失败改 fail-fast:三条路都拿不到 elements 时,此前会落回
   `offset >= max_offset` 的「存回临时变量」分支 —— 该临时变量不在对象布局里,
   后续 `self.attr` 读路径按 GEP 取值,写入被**静默丢弃**。现直接 FailFastError,
   并报出三次尝试的结果(不兜底)。

静态核验:py_compile 通过;全仓 symtable 未定义名扫描 0 条;以 _GuardIrBuilder +
真实 _ConcretizeGepBase 复现现场实例形状(opaque '8312ce86a2fd82c0.Keyword' +
已具化 'fef783da4d24330d.Keyword'(10 槽))→ 解析成功、GEP 正常发出(索引 3);
未收口的裸 opaque 基址仍被守卫拦下。
2026-09-30 15:14:31 +08:00