Commit Graph
434 Commits
Author SHA1 Message Date
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 a8f6ca006d feat(struct): TPC 前端支持 struct 关键字(含 C 加速解析器)
与 TransPyV/includes 侧(c27ebc1)逐点对齐:`struct X:` / `struct X(Base):` 脱糖为
`@t.NoVTable class X(t.CStruct):`,禁止 virtual/novirtual 修饰、禁止类体内 def。

- lib/vast/Parser.py:软关键字分派(语句起始 + 后随标识符;`struct = 5` 不受影响)、
  _ParseStructDef(复用 _ParseClassDef 全路径 + 合成 @t.NoVTable + 追加 t.CStruct
  标记基类 + 类体禁 def 校验)、_StructSyntaxError 报文、_ParseVirtualModified
  显式拦截 `virtual/novirtual struct`;@deco 前缀同样支持。
- lib/vast_c/vast_c.c:同构实现(ParseStatement / ParseDecorated 分派、
  ParseStructDef、ParseVirtualModified 拦截)。
- lib/vast_c/_parser.pyd:按 README §4.3 的命令重建(gcc 15.2.0)。

验证(两条路径逐一对照):bases=[t.CStruct] / [Base, t.CStruct]、
decos=[t.NoVTable];`virtual struct` 与 `struct 内 def` 均 fail-fast;
`struct = 5` 仍解析为普通赋值。
2026-10-01 23:07:49 +08:00
TermiNexus 85d68e5913 fix(type): 支持内建裸名函数指针构造子 callable[[...], R]
TransPyV 侧 TypeGenResolve._IsCallableSubscriptValue 已把 t.Callable[...] 与内建裸名
callable[...] / Callable[...] 视为逐点等价,TPC 侧只有模块限定形态能识别:

- TypeAnnotationResolver._from_node_subscript 的 Callable 分支要求 IsTModule(parts[0]),
  裸名落进泛型特化兜底未命中 → 空 CTypeInfo;改为裸名同判并共用同一构造体。
- DeclarationGenerator._get_type_str 的 Subscript 分支缺 callable 判定,落到末尾
  兜底 'i32';补判返回 'i8*'(与 Attribute 分支的 t.Callable 一致)。

现场:Repl.vp:307 ReplFragFn: callable[[rawptr], int] 被声明成 i32 全局,
Repl.vp:2727 ReplFragFn = fp_addr(i8*)触发「指针隐式转整数已禁止」。
2026-10-01 21:53:43 +08:00
TermiNexus f0e630457f chore(Phase2Translator): 自动并行度上限 8 worker,并精简并行提示文案
- 新增 _MAX_WORKERS=8(用户定:16 worker 太多,收益不足而内存压力翻倍);
  只约束自动取值,显式 TPC_JOBS=N 仍按硬件/文件数上限
- 并行相关的 RuntimeError 与日志精简为一行数字 + 出路,去掉"拒绝降级为串行 /
  串行会让编译时间从 ~400s 掉到 ~1180s 档"等长说明
2026-10-01 20:58:08 +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 2ba38ae842 fix(Phase2Translator): 并行 worker 数不再按可用内存降级串行,改为"预分配校验 + 强制预留"(TPC_JOBS 可显式覆盖)
现象:同一条命令的编译时间在 ~400s 与 ~1180s 两档之间随机跳(PROF 目录当日
366s~2239s),用户反馈"耗时不降反增"。

根因(三份 profile 实证):
- 旧 _PickWorkerCount 用 `by_mem = max(1, avail_mb // per_worker_mb)` 按**可用**
  物理内存夹 worker 数:可用稍紧(<~2.8GB)就整体降为 1 ⇒ **静默退回串行**。
  其文档字符串自己就写着"76 文件逐个翻译 1180s,并行时 ~400s,波动 2-3 倍"。
- PROF/20260930_224857(438.40s):主进程 _translate_file **0 次**、
  StructGen/HandlesImports/HandlesClassDef **0 条记录**、总调用 7.28 亿
  ⇒ 翻译在 worker 进程里做完了(未 profile),主进程只在等 → **并行档**。
- PROF/20260930_231916(1048.55s)与 20260930_235354(1168.15s):
  _translate_file **75 次全在主进程**、_parallel_translate_worker/_batch 0 次、
  总调用 19.6 亿 ⇒ **串行档**(1180s 档)。
⇒ 拿 438s 当基线比 1168s 是"并行 vs 串行"。本次 A/B(同为串行、总调用
  19.616 亿 vs 19.631 亿)里各项优化均为正收益(_RewritePlain −5.0s、
  __exit__ −1.5s、re.match+_compile −1.3s、_RewriteQuoted −0.75s…);
  +12% 属机器波动(同一批 C 函数在调用次数不变下变慢 25%~73%)。

改动(口径由用户定:不降级 + 预分配内存避免波动 + 强制要求预留内存):
1. **不降级**:删掉"内存紧就退串行"。worker 数 = min(cpu_count, n_files),
   不再因可用内存抖动而变化。
2. **预分配校验 + 强制预留**:新增 _MemStatus()(读可用/总量)与
   _ProbeReserveMb()——按 base×per_worker_mb(翻译 350 / 编译 400)分块**真正
   提交**该预算(峰值=预算),成功后立即整体释放,紧接其后创建 worker 进程
   由 OS 复用这批页。等于先向 OS 确认"这笔预算确实拿得到",而不是只看含大量
   可回收页的 ullAvailPhys。
3. **fail-fast**:可用内存 < 预算、或预分配校验提交失败 ⇒ RuntimeError 报出
   "需要 X MB(N worker × M MB)/ 当前可用 Y MB / 总量 Z MB",由使用者释放内存
   或**显式** TPC_JOBS=N 重跑(N=1 即显式接受串行)。不再静默降级。
4. 新增 TPC_JOBS=N 显式覆盖(跳过校验,使用者自担内存风险)。
5. 每次决策都打日志:显式指定 / 读不到内存状态 / 预分配校验耗时 / 最终 workers。

已知取舍:内存确实不足时编译会**直接失败**(这是"不降级"的应有语义),出路是
释放内存或 TPC_JOBS=N;预分配校验会短暂占用峰值=预算的内存(约 1GB/秒级耗时)。

未动(下一步候选):串行档的真热点 StructGen._generate_structs(151s 自身 /
480s 累计,被调 36,389 次≈每类 22 次,O(类数²))。
2026-10-01 00:23:16 +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 4641777db1 perf(Phase2Translator/_split_ll): 括号计数走 C 级 str.count 快路径 + 调用点预筛
prof 基线(PROF/20260930_224857,主进程 433.69s)实测 _split_ll:
ncalls 53 / tottime 0.263s / cumtime 3.347s,其中
_count_braces_outside_strings 102,397 次 / tottime 1.999s / cumtime 2.769s
(占其 cumtime 的 83%);同函数内 strip 0.078s、startswith 0.084s 可忽略。

- _count_braces_outside_strings:行内不含 c" 时退化为 C 级
  str.count('{') - str.count('}'),不再逐字符跑 Python 循环。
  语义逐字等价:逐字符版唯一的额外分支就是"跳过 c" 字面量",而该分支
  只在出现子串 c" 时进入 ⇒ 无 c" 时等于纯括号计数。
- 两个调用点(in_define 函数体续行 / in_global_def 全局初始化器续行)加预筛
  `'{' in line or '}' in line`:无花括号的行必然返回 0,省掉一次函数调用。
- `stripped == '{'` 分支的 3 处 re.sub 字面量改用同模式串的预编译常量
  (_RE_DEFINE_PREFIX / _RE_DEFINE_LINKAGE / _RE_ALWAYSINLINE),与上面
  define 分支保持同一口径(模式串一字未改)。

顺带实测(供下一步参考):str.strip 179M 次 / 22.8s 是全流程最大项,但
cProfile 归属帧几乎全在 stdlib(re.match 5.25M、threading.wait 4.88M、
futures.as_completed 3.63M、json.load 0.66M、subprocess 各 ~0.4M);
本项目内只有 _extract_llvm_type_name 1.37M、_ParseDwarfLineRows 0.34M、
_split_ll 0.35M。
2026-09-30 23:18:29 +08:00
TermiNexus e292e2390d perf(Phase2Translator): 文本读缓存改按路径直存(零 stat),正确性靠写/删点显式失效
- _ReadTextCached 不再 os.stat:原实现每次调用都要 stat 判新旧,等于把省下的
  1.2 万次 open 换成 1.2 万次 stat(同为系统调用热点),命中路径仍有系统开销。
  改为 OrderedDict[路径 → 文本],命中零系统调用;newline 非 None 的逐字节读不进缓存。
- _InvalidateTextCache 改 O(1)(按路径直接 pop),并在 14 处文本写点 + 1 处 stale
  文件删除(os.remove)处调用 —— 读侧不再 stat 后,写/删点显式失效是唯一正确性保证。
- 失效为何必须显式(时间戳判据在同 tick 内会失效):_compile_one 前的 type 定义
  重排(行集合不变仅次序变,长度逐字节相同)、_recombine_ll 的旧 sha1 前缀替换
  (16 位 hex 等长替换)。
- 安全性前提:TPC 单次运行内被读文件内容不变。已核对:self.sig_files /
  self.stub_files 指向的都是 Phase1 产物(temp_dir / _GLOBAL_CACHE_DIR,Phase2
  期间不再改写);Phase2 自产的 .ll/.stub.ll/.deps.json/.vpi 每次写、删都走上面的失效。

基线 PROF/20260930_224857:_io.open 12,740 次/self 28.8s、read 11,902 次/30.4s、
TextIOWrapper.__exit__ 16.6s(128 个模块却上万次读,同一份产物被多阶段反复打开)。
2026-09-30 23:15:22 +08:00
TermiNexus 1748940d30 perf(Phase2Translator): 文本读全走 LRU 缓存 + 写侧显式失效,削重复文件 I/O
- 新增 _ReadTextCached:键 =(路径, st_mtime_ns, st_size),命中即免 open/read/close;
  按总字节预算(64MB)LRU 淘汰,单文件 >32MB 不入缓存;newline 非 None 时退化为直读。
- 21 处文本读点全部改走缓存(.vpi/.pyi 解析、.ll/.stub.ll/.text.ll、deps.json、
  doc.json、include 源、pyi 缓存预热等),json.load(f) 统一改 json.loads(_ReadTextCached(...))。
- 新增 _InvalidateTextCache:14 处本进程文本写点(原地改写/新落盘)后显式失效。
  Windows 文件时间为系统时钟派生,粒度约 15.6ms,"先读 → 同 tick 内原地改写 → 再读"
  且改写前后字节数相同时 (mtime, size) 键完全一致(实测两例:_compile_one 前的 type
  定义重排——行集合不变仅次序变;_recombine_ll 的旧 sha1 前缀替换——16 位 hex 等长替换),
  只靠 stat 判新旧会读到过期文本,故写侧必须显式失效。

基线 PROF/20260930_224857:_io.open 12,740 次/self 28.8s、read 11,902 次/30.4s、
TextIOWrapper.__exit__ 16.6s(128 个模块却上万次读,同一份产物被多阶段反复打开)。
2026-09-30 23:13:01 +08:00
TermiNexus 3658ab0637 perf(Phase2Translator/IRMetadataFix): 削逐行热点——跳过无谓 strip、预编译逐行正则、C 级数字扫描
prof 基线(PROF/20260930_224857/main_time.txt=438.40s,7.28 亿次调用)显示全流程最大的 Python 侧热点是逐行扫描产生的内置字符串/正则调用:str.startswith 2.372 亿次(32.1s)、str.strip 1.791 亿次(22.8s)、re.Pattern.match 3130 万次(11.4s)、re.match 模块级 1971 万次(10.7s)+re._compile 1975 万次(7.2s)。四处等价改动:
1) _split_ll 第二遍:in_define/in_global_def 续行(.ll 里占比最高)不再 str.strip——这两支只用行内容与花括号计数,前导/尾随空白不含花括号,_count_braces_outside_strings 结果逐字不变;
2) _split_ll 第一遍:加 "define" not in line 的 C 级快路径,绝大多数行连 strip 都不做;
3) Phase2Translator 逐行正则全部预编译为模块级 _RE_* 常量(11 处 re.match + 4 处 re.sub,模式串一字未改)——消掉每次调用的模式串哈希+缓存查找;
4) IRMetadataFix:_RewritePlain 的前导数字扫描由逐字符 Python 循环改为 str.lstrip 的 C 级扫描(该函数 prof 5.8M 次调用,是项目内 self time 最大者);SanitizeIRMetadata 的逐行 startswith("!") 改为 [:1] 索引切片。
离线自测:数字扫描与旧实现逐例一致;SanitizeIRMetadata 幂等性保持;_split_ll 冒烟输出与预期语义一致(global/多行初始化器/define→declare/constant/metadata 分流正确)。
2026-09-30 23:04:25 +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 ecd336771f fix(FuncGen): base/字段偏移一律按"重名条目不占槽位"算,修 MatchCase 偏移 6(应为 7)
类别:a(编译器缺陷)

现象(早前现场,本轮定位):
  TypeError: [fast-fail] len(mcc_pre.children) 分派 'list[….AST*].__len__' 失败:
  类='MatchCase' 偏移=6 接收者类型=i32

判据(磁盘实证):
  · MatchCase 的 body 在 temp 与**全部** output stub 中都是 **10 槽**
    (vtable + AST 的 7 字段 + pattern/guard)⇒ `children` 的正确偏移 = base(1) + 表内下标(6) = **7**。
  · 观测到 6 ⇒ base 被算成 0 ⇒ `_member_base` 的 `_Delta = 槽数 - 表长` 为 0,
    即**表长 = 10**(9 个不同字段 + 1 条重名条目),而不是 9。
  · 重名条目是**产物**:源码层面的字段名遮蔽已由 HandlesClassDef 的
    "字段名遮蔽 fail-fast"(:1083)拦下 ⇒ 表里的重名只可能来自"展平残留
    (半截祖先块,已由 2e7bac18 在插入点剥除)/ 同一字段被两条收集路径各登记一次"。

修复(与 `_get_member_offset` 配对路径已有的"重名条目不占槽位"同口径,全线统一):
  · `_member_base` 的长度校准改用**去重名数**(`_Delta` 不再被重名条目冲掉 ⇒ base 正确);
  · 新增 `_MemberSlotIndex(Members, FieldName)`:取"首个同名的**去重**下标"
    (重名条目不计槽位),并把 `_get_member_offset` 中**全部 12 处** "base + 表内下标"
    站点改为调用它(含继承链回退、REnum 变体搜索、stub 重载后、_PreRegisterClassMembers
    之后等分支);"最后回退"里的 `_num_members_final` 同步改为去重计数。
  · 对无重名的表,取值与改前**逐字相同**(去重下标 == 原始下标)。

核验:py_compile + compileall 全 lib 通过;离线冒烟(真实方法、未绑定实例调用):
含 1 条重复 'children' 的表上 `children -> 6`、`guard -> 8`、缺名 -> -1;
去重名数 9 ⇒ `_Delta = 10 - 9 = 1` ⇒ base=1 ⇒ `children` 偏移 7(与 body 10 槽一致)。
未放宽任何 fail-fast。
2026-09-30 18:44:48 +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 52ea21f082 fix(LlvmGenerator/Phase2Translator): 本模块声明的类钉死本模块 SHA1 前缀,消除同名类前缀漂移
类别:a(编译器缺陷)

现象(上一版 ab906f2c 之后的下一步):
  includes/ast/parser.vp:1774 `kw: rawptr[AST] = KwNode(ps.pool, None, val)` 报
  TypeError: Type of #1 arg mismatch:
    %"fef783da4d24330d.Keyword"* != %"8312ce86a2fd82c0.Keyword"*
  (llvmlite 该报文是「形参 != 实参」且下标 1-based ⇒ 实际就是**第 0 个形参 self**)

根因(磁盘实证,非推断):TransPyV/output/8312ce86a2fd82c0.stub.ll 内自相矛盾
  - `%"fef783da4d24330d.Keyword" = type {…}` 与 `%"8312ce86a2fd82c0.Keyword" = type {…}`
    两份同名类的 struct 同时存在;
  - `declare %"fef783da4d24330d.Keyword"* @"8312ce86a2fd82c0.Keyword.__new__"(%"8312ce86a2fd82c0.Keyword"* %self, …)`
  - `declare void @"8312ce86a2fd82c0.Keyword.__before_init__(%"fef783da4d24330d.Keyword"* %".1")`
  其中 8312ce86a2fd82c0 = includes/ast/astaux.vp、fef783da4d24330d = includes/ast/tokens.vp
  (TransPyV/temp/_sha1_map.txt),两者各自定义了一个 `class Keyword`。
  机制:`_build_struct_sha1_map` 建的是「**类名** → 定义模块 SHA1」,但去重判据写成
  `if sha1_key in struct_sha1_map` —— 用模块 SHA1 去查一张按类名建的表,判据恒假 ⇒
  实际是「后扫到的覆盖先扫到的」,同名类归属随 os.listdir 顺序漂移。定义端
  `StructGen._generate_structs` 在 `class_sha1_map` 为空时用此表取前缀,于是 astaux
  **自己**的 Keyword 被命名成 `fef783da4d24330d.Keyword`(另一个模块的 sha1);而按本
  模块前缀另建的 `8312ce86a2fd82c0.Keyword` 被 `__new__`/`__init__` 当作 self,
  `HandlesClassDef._EmitNewFunctionLlvm` 生成的 `__before_init__` 却取
  `Gen.structs['Keyword']`(错名前缀那份)⇒ 声明与调用点分叉。

修复(两点,均为正向事实判据):
  1) LlvmGenerator(Gen 构造处,**先于** setup_from_symbol_table 内部的 _generate_structs):
     把本文件顶层 ClassDef 名单钉死为本模块 SHA1,写进 Gen.class_sha1_map(其优先级高于
     _struct_sha1_map)。"本文件声明的类"就在本次要翻译的 AST 里;class_sha1_map 是 Gen
     实例级、每模块重建 ⇒ 非本模块声明的类不受影响。
  2) Phase2Translator._build_struct_sha1_map:去重判据改为**类名**(先扫到的为准),并另按
     模块 SHA1 去重"同一模块文件被两处目录重复解析"——归属不再随 listdir 顺序漂移。

未放宽任何 fail-fast;未改动 __new__/__before_init__ 的调用点。
2026-09-30 18:12:24 +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
TermiNexus 7ee6ebe6b0 fix(类型转换): int→i1 目标改为真值归一化 != 0,与 bool(x)/if 条件同判据
类别:a(编译器缺陷)。TPC 侧此前对 i1 目标一律 trunc(取最低位):运行时值 2 →
最低位 0 → false,而同一值在 `if 2:` / `bool(2)`(icmp ne 0)为真 ⇒ 条件上下文与
落存储/强转两处结论相反(静默语义分裂)。

- MemoryOps._CoerceIntToI1(新增,唯一判据源):非 i1 整数 → `icmp ne x, 0`。
- MemoryOps._coerce_value:窄化且目标位宽 == 1 时走 _CoerceIntToI1。
- HandlesAnnAssign._StoreWithCoerce:同上(self.字段 / obj.字段 落存储)。
- HandlesExprCall._HandleTypeCastLlvm:显式强转目标 i1 时同上。

与 TransPyV 侧同批修复(coerce_to_type / _translate_t_type_cast)对齐。

注:TPC 的 `bool` 注解仍是 i8(t.CBool.Size=8),故 `b: bool = 2` 仍按 i8 保位存储
(int(b)=2);本次只覆盖**真 i1 目标**,双端 bool 位宽差异见
SymbolUtils.py:1218-1223 已记录的全局语义分裂。
2026-09-30 15:14:31 +08:00
TermiNexus 3efe83f1ea fix(编译期名称解析): 修 StructGen 缺 _vlog 导入与 HandlesImports 的 node/Node 误用
两处均为 a(编译器缺陷),且都被 HandlesImports.py:1055 的
`except Exception -> WARN 忽略异常` 吞掉,表现为模块声明静默退化
(本次现场:VpiReader.vp 阶段 WARN + 结构体声明路径中断)。非源码违规、
非机制漏豁免。

1) lib/core/LLVMCG/StructGen.py
   - `_generate_structs` 的「成员表含重名条目、body 已是权威布局」提示调用
     `_vlog().warning(...)`,但本文件从未定义/导入 `_vlog` ⇒ NameError。
     该分支一旦命中(astaux.ExceptHandler 一类),`_generate_structs` 整体中断。
   - 补 `from lib.core.VLogger import get_logger as _vlog`,与同目录
     FuncGen.py / TypeConvert.py 同口径。

2) lib/core/Handles/HandlesImports.py:1317
   - `_EmitExternalGlobalDeclLlvm` 形参名为 `Node`,该 elif 分支误写成小写 `node`
     ⇒ 只要传入的是**非带注解**的跨模块全局量(`ast.Assign`)就恒抛 NameError,
     分支整体不可达:外部全局变量从未声明 external,消费侧表现为
     unknown global / 类型占位。
   - 判据改回 `isinstance(Node, ast.Assign)`。

静态核验:对 TransPyC 全部 .py 做 symtable 未定义名扫描,结果 0 条;
`python -m py_compile` 通过。
2026-09-30 14:55:40 +08:00
TermiNexus e5f044dd03 chore(诊断): AnnAssign RHS 为 接收者链.方法(...) 时补接收者基址诊断 + 最近一次属性读取失败原因
【b 机制漏豁免 —— 只加取证,不改任何判定/IR】现场:翻译
TransPyV/App/lib/core/Gen/Class/ClassGenEnum.vp:59
`ty: llvmlite.LLVMType* = self.Gen.TypeGenResolve.resolve_typedef_or_map(pool, at.attr,
imported_modules, from_imports)` 报"(Call 形态)编译期求值为空"。

已按代码**排除**出来的事实(写进注释,供后续不再重复排查):
  · 方法声明缺失会在 HandlesExprCall._HandleMethodCallLlvm 内 raise
    "Undefined method …",不会走到该 fail-fast;
  · 该函数在"方法已找到"之后不会返回 None,唯一的 None 出口是
    `ObjVal = HandleExprLlvm(Node.func.value)` 为假(L3335-3337)——
    即**接收者链 `self.Gen.TypeGenResolve` 自身求值为空**才是根因;
  · 但原报文的"基址诊断"只在 RHS 是 **Attribute** 时才打印(HandlesBase:784),
    Call 形态下接收者失败原因被丢弃 ⇒ 报错点与真因相隔数层。

修法(纯取证):HandlesBase 的通用 fail-fast 增 `elif Call(func=Attribute)` 分支,补三行:
  ① `被调者接收者诊断:` —— 复用 `_DescribeAttrBase(Gen, Node.func.value)`:接收者
     (如 `self.Gen`)的基座形态 / var_struct_class / alloca 类型 / 是否在 Gen.structs /
     class_members 项数与字段名单 / struct body 元素数 / 该字段计算偏移;
  ② `最近一次属性读取失败:` —— 回显 `Gen._LastAttrNone` 的 (类, 字段, 原因):Call 形态下
     无法用 (类, 字段) 与被报节点比对,故此处**无条件**回显(Attribute 形态仍保持
     "必须与被报节点一致"的原判据,避免旧记录误导);
  ③ `被调方法=… 已声明同名方法键(示前 4 / 共 N):` —— 区分"方法根本没声明"与
     "接收者读不出来"(后者才会命中本 fail-fast,但这一行让两者一眼可分)。

未验证:本提交只改诊断文本,行为不变;需再跑一次构建才能看到新增三行。
2026-09-30 14:45:55 +08:00
TermiNexus 00edd90c28 fix(Handles*): GEP 基址 opaque pointee 收口统一为单一判据源,补齐同族 12 处缺口
【a 编译器缺陷】7aa12263 修好 `_get_attr_ptr` 的 self 分支后,同族其余 GEP 点仍会在
"基址 pointee 是另一份 opaque 同名实例"时抛裸 TypeError(被 IR 边界守卫拦下,报文为
"GEP 的基址指向**未具化**的具名结构体")。根因是同一套收口逻辑被各点各写一份、且
**多数点根本没写**:真正需要的判据只有一条。

新增唯一判据源(HandlesExprAttr):
  · `_ConcretePointeeFor(Gen, ClassName, Pointee)`:取该类的**已具化实例** ——
    ① 类键那份先 `_TryLoadStructFromStub` 补 body;② 否则 `Gen._FindConcreteTwin`
    (同短名、槽数最多);③ 都取不到 ⇒ None(**不兜底**,交守卫 fail-fast 报同短名
    实例清单 + TPC 发射点)。
  · `_ConcretizeGepBase(Gen, Ptr, ClassName, Tag)`:pointee 非 opaque ⇒ 原样返回
    (对既有正确路径零变化);opaque ⇒ bitcast 到已具化实例并**同时返回该实例**,
    供 `_get_member_offset` 作 StructHint(偏移与 GEP 必须同源,与 a43513c9 同口径)。

接入点(全部改为调用上述判据源,逐点行为只在"原本必崩"的情形下变化):
  · HandlesExprAttr._get_attr_ptr — self / 变量 / 属性链 / i8* / 下标 / 强转基座 六条分支
    (self 分支已由 7aa12263 修,此处并入统一判据;下标分支此前**完全没有**收口);
  · HandlesAnnAssign._HandleAttributeStoreLlvm — self / 变量 / 属性链 三条分支;
  · HandlesBase._get_int_ptr — 属性分支;
  · HandlesExprBuiltin(len() 的两个接收者 GEP:`obj.len` 与 list 特化字段);
  · HandlesMatch — REnum 变体 match 的载荷 GEP(变体 struct 可能未 set_body)。

冒烟(临时脚本已删,鸭子类型 Gen + 真实方法):
  · `_get_attr_ptr` self 分支 ⇒ 基址 `%"fef783da4d24330d.Keyword"*`(经 cast_self_Keyword)PASS;
  · `_get_attr_ptr` i8* 分支 ⇒ 同上 PASS;
  · `HandlesAnnAssign` self 分支 ⇒ GEP 基址为已具化实例 PASS;
  · 回归:基址本就具化时必须**原样**(不得多出 bitcast)PASS。
另:`python -m compileall TransPyC/lib` 全绿。
2026-09-30 14:42:56 +08:00
TermiNexus 7aa12263f5 fix(HandlesExprAttr/BaseGen): _get_attr_ptr 的 self 分支补 opaque 基址收口(孪生体 bitcast)+ GEP 守卫报出 TPC 发射点
【a 编译器缺陷】现场:Phase2 翻译 includes/ast/astaux.vp:165
`self.arg = _copy_str(pool, arg)`,守卫报
"getelementptr(GEP) 的基址指向**未具化**的具名结构体:基址类型=%"8312ce86a2fd82c0.Keyword"*,
GEP 名='arg'",而同短名实例清单显示 'Keyword' 键上那份已具化(10 槽)。

根因:`_get_attr_ptr` 的 **self 分支**是全仓"self 字段取址"家族里唯一**没有**做 opaque
收口的一处 —— 同函数下方各分支(变量分支 / 属性链分支 / i8* 分支 / 强转基座分支)都先
`_TryLoadStructFromStub` 补 body、再 bitcast 到已具化实例;`HandlesAssign` 的 `self.X = v`
写路径与读路径 `_gep_and_Load_member` 也都有同一套孪生体搜索(`_FindConcreteTwin`)。
本分支直接 `gep(SelfPtr, …)`,typed pointer 下 llvmlite 必须在编译期从 pointee 算元素类型
⇒ 撞 opaque 即抛裸 TypeError(被 IR 边界守卫拦下)。

修法(与同函数其余分支同口径,不引入新判据):
  · 基址 pointee 为 opaque 时,先在类键(`Gen.structs[ClassName]`)上 `_TryLoadStructFromStub`
    补 body;类键那份仍不可用则退回 `Gen._FindConcreteTwin`(同短名、取槽数最多);
  · 命中即 bitcast 到该实例,并把该实例作为 `StructHint` 传给 `_get_member_offset`
    —— 偏移与 GEP 必须同源(与 a43513c9 的"hint 只接受同类"口径一致);
  · 顺带补 `offset is None ⇒ return None`(原实现会把 None 塞进 `ir.Constant(i32, None)`,
    属同一函数其余分支已有的判断)。

【a 编译器缺陷·取证】`RequirePointerOperand` 的 opaque 分支新增一行
「TPC 发射点(由内向外)」:取 Python 栈中位于本仓 `lib/`、且不在 BaseGen.py(守卫自身)
的最内 3 帧,报出 `文件:行(函数)`。理由:该守卫的报文只能指到**语句**,而同一语句里常有
多个候选 GEP 点(本例 astaux.vp:165 在读写两侧共有 4 个 self 字段 GEP 点),逐个读代码
无法判定是哪一个 —— 本次即靠该取证能力闭环(先按"唯一漏收口的分支"修正,下一次构建若
仍失败会直接指出真实发射点)。只在失败路径执行,热路径零开销。

冒烟(临时脚本已删):
  · `_get_attr_ptr` 复刻现场(self 槽持有 opaque `%8312…Keyword*`、'Keyword' 键那份 10 槽)
    ⇒ 输出 GEP 基址类型 `%"fef783da4d24330d.Keyword"*`(经 `cast_self_Keyword`),PASS;
  · 守卫取证:伪造 lib/ 下的发射帧 ⇒ 报文含
    `TPC 发射点(由内向外): core/Handles/_FakeProbe.py:2(_LibEmit)`,PASS。
2026-09-30 14:35:50 +08:00
TermiNexus 49930c56c7 fix(LLVMCG/HandlesExprAttr): REnum 变体字段按唯一变体键取槽 + 修 _PointeeMatchesStruct 的「同模块前缀」误判
【a 编译器缺陷】两处根因,同一现场(CallGenDispatch.vp:787
`len_ap: llvmlite.LLVMType* = len_val.Ty.Pointee`):

1) `_PointeeMatchesStruct` 只比 sha1 前缀 ⇒ 把**同模块的兄弟类**当成同一个类
   (`50741ad4b4b0934d.ParamNode` 与 `50741ad4b4b0934d.LLVMType` 前缀相同,同出
   includes/llvmlite/__types.vp)。结构体分支迭代 Gen.structs 时先命中 ParamNode
   (它在 __types.vp 里声明更早)⇒ `_get_member_offset('Pointee','ParamNode')`
   直接 fast-fail,报出与真因无关的类名。判据收紧为:**同一对象** 或 **IR 全名逐字相同**
   (前缀标识的是模块,不是类)。同短名不同前缀(同一类的另一模块实例)仍由调用点的
   `_UniqShortCN`(同短名候选唯一)放行,不受影响。

2) REnum 的变体字段在旧路径上无处可取:'Pointee' 只属变体键 'LLVMType_Ptr',
   而主类 'LLVMType' 的成员表为空(HandlesClassDef:716)⇒ 即使接收者判对类名也必 fail-fast。
   新增 `FuncGen._RenumVariantFieldOwner`,在 `_get_member_offset` 里当"主类不声明该字段"时改按
   变体键取偏移。判据(逐条可查):① 主类经 `_IsRenumLayoutClass`(唯一判据源)判为 REnum;
   ② 变体键固定 `<主类短名>_<变体名>` 且成员表**首项恒为 `__tag`**(唯一产源:
   HandlesClassDef._EmitREnumLlvm 与 HandlesImports 的 REnum 跨模块注册);
   ③ 恰有**一个**变体声明该字段(多个 ⇒ 不猜,照常 fail-fast);④ 变体 struct 已注册。
   槽位正确的依据:变体 struct 的 body 是**主 REnum 镜像** `{i32 __tag, <max_variant_payload>}`,
   变体成员表 `[__tag] + 载荷` 与主 REnum 槽位一一对应。

3) 载荷槽按**最大变体**宽度物化('Pointee' 槽是 i64),而字段本身声明为指针 ⇒
   新增 `HandlesExprAttr._RenumVariantPayloadType` / `_CoerceVariantPayload`,在
   `_gep_and_Load_member` 载入后按字段声明类型收口(int→ptr: inttoptr;ptr→ptr: bitcast;
   int→int: trunc/zext;不可转换原样返回,交下游照常 fail-fast)。非 REnum 变体键逐字不变。

冒烟(临时脚本已删):_PointeeMatchesStruct 5 例、_RenumVariantFieldOwner 7 例、
载荷类型与转换 6 例、_get_member_offset 钩子(绑定真实方法)5 例 —— 全部通过。
2026-09-30 12:46:37 +08:00
TermiNexus 64ddabbe17 feat(HandlesAnnAssign/HandlesExprAttr): 左值注解靶型提示(target-typing)——属性链读错类名时按注解类兜回
现场:CallGenDispatch.vp:787 `len_ap: llvmlite.LLVMType* = len_val.Ty.Pointee`
报 "[fast-fail] 属性 'Pointee' 在类 'ParamNode' 中找不到(class_members=2 个: ['Next','Ty'])"。
'Pointee' 的真实归属是 LLVMType 的 REnum 变体镜像字段(LLVMType.Ptr.Pointee,
includes/llvmlite/__types.vp:33-34);ParamNode 只有 Next/Ty。更隐蔽的是**两边都有 Ty**
(Value.Ty = rawptr[LLVMType]、ParamNode.Ty = 裸 rawptr = i8*)⇒ 读错类不会当场报错,
只静默把类型变成 i8*,再往下 `.Pointee` 才炸。

机制(与 TPV translate_value 的 target_ty「使用处真实站点种类」同思路):
  ① HandlesAnnAssign._EvalRhsWithTargetHint:注解赋值求值 RHS 前,把**注解指向的结构体类**
     作为 `Gen._TargetStructHint` 传给属性链解析;求值结束(含异常)复位,不跨语句泄漏。
     两个 RHS 求值点(_reg_values 提升路径 + 常规路径)都改走该入口。
  ② HandlesExprAttr._apply_target_struct_hint:**仅当两条件同时成立**才改判——
     Ⅰ 当前解析出的类(或解析不出)**不声明**该字段(此刻原本会 fail-fast);
     Ⅱ 靶型类**确实声明**该字段。
     任一不满足即原样返回(不猜、不篡改本来正确的链);改判时 `_vlog().warning` 留痕。
     接入点:char*(i8*)分支与单级 Name 分支(含"原键解析不出"的情形)。

验证:py_compile 通过;`_apply_target_struct_hint` 冒烟 7 例全部符合预期——
无提示原样 / 原类不声明且靶型声明⇒改判(本次现场)/ 原类声明该字段⇒不改判 /
原键即靶型⇒原样 / 原键解析不出且靶型声明⇒改判 / 靶型也不声明⇒原样 / 靶型类不存在⇒原样。
2026-09-30 12:28:39 +08:00
TermiNexus a1c43a7b3b chore(诊断): MemberLookupFailFast 补取证——字段所有者、类键/实例名、登记为该类的变量与实际 pointee
现场:CallGenDispatch.vp:787 `len_ap: llvmlite.LLVMType* = len_val.Ty.Pointee`
报 "[fast-fail] 属性 'Pointee' 在类 'ParamNode' 中找不到(class_members=2 个:
['Next','Ty'])"。'Pointee' 实际属 LLVMType 的 REnum 变体镜像字段(LLVMType.Ptr.Pointee),
而 ParamNode 只有 Next/Ty ⇒ 说明**读取时的接收者类名被判成了 ParamNode**
(`len_val` 的注解与 RHS 都写的是 `llvmlite.Value*`,Value.Ty 才是 rawptr[LLVMType];
ParamNode.Ty 是裸 rawptr = i8*)。

本轮只补取证、不改行为,让下一轮能一次定位"是谁把接收者记成了 ParamNode":
  ① 本类键 → struct 实例 IR 名(键/实例名不一致 ⇒ 同一类名两份实例);
  ② 其它**声明了该字段**的类键列表(唯一者即"本该用的类");
  ③ var_struct_class 中登记为**本类**的变量(封顶 6 个)及其实际 pointee IR 名
     —— 记录类名与实际 IR 类型不一致时,直接圈定记录点。

验证:py_compile 通过。
2026-09-30 12:26:05 +08:00
TermiNexus 43853bc455 fix(HandlesExprAttr): 基座 pointee 与 Gen.structs 的匹配补「同 sha1 前缀」与「同短名唯一候选」
现场(Repl.vp:802):`ct_names: list[str]* = GenApi._g_gen.TypeGenCType.CTypedefNames`
的基座 `.TypeGenCType` 取值后 pointee 为 `%"750b8c10137d273e.TypeGenCType"*`(= 撤销
6a287b9 后的新 sha1),而 Gen.structs(250 键)里没有该 IR 名的实例 ⇒ 旧判据只有
"身份相同 / IR 全名逐字相同" ⇒ 全部未命中 ⇒ 属性链求值为空 → 带注解赋值判定"无值可存"
而终止编译。(a18ca4da 的记账正是把"基座形态 / pointee / 键数"三项事实直接印了出来,
本轮不必再猜。)

改动(判据分级,强到弱;找不到仍留痕失败,不兜底造类型):
  ① 身份相同(原判据);
  ② IR 全名逐字相同(原判据,覆盖"同名第二份实例");
  ③ 同 sha1 前缀 —— IR 名形如 `<sha1>.<短名>`,前缀相等即"同一模块的同一类";
  ④ 同短名——**仅当候选唯一**时放行(调用点算 `_UniqShortCN`):两个及以上同名类无法
     判定是哪一个,宁可留痕失败也不选错布局(同名类冲突在本仓一律以 sha1 全名区分)。
同时把"未命中"记账扩为:列出分级判据、同短名候选清单,以及"候选 >1 会被主动拒绝"。

验证:py_compile 通过;`_PointeeMatchesStruct` 冒烟 6 例(身份 / 同名第二份实例 /
同短名不同 sha1 / 裸短名 / 不同类 / 非 Identified 一侧)全部符合预期。
2026-09-30 12:20:20 +08:00
TermiNexus 2aa55f36b9 fix(LLVMCG/Handles): GEP 基址 opaque 时按同短名找已具化孪生体并 bitcast;守卫报文补实例清单
现场:includes/ast/astaux.vp:165 `self.arg = _copy_str(pool, arg)` 的基址 pointee 是
opaque 的 `%"<sha1>.Keyword"*` —— 同一类名常有两份 struct 实例(短名 'Keyword' /
sha1 全名 '<sha1>.Keyword'),"函数签名里的 self 用哪一份"与"StructGen 具化的是哪一份"
并不保证一致 ⇒ GEP 无法在编译期算元素类型(llvmlite 抛裸 TypeError),
_GuardIrBuilder 的 opaque 守卫先行 fail-fast。

改动:
1) 新增 BaseGen._FindConcreteTwin:按**同短名**(IR 全名相同者优先)在 structs 里取一份
   已具化实例(元素最多者),与既有"同名实例取字段最多版本"(HandlesAssign:1156-1163)
   同一判据;返回 None 表示确无孪生体(真因 = "body 从未 set_body")。
2) 写入路径 HandlesAssign(`self.字段 = ...`):pointee opaque 时 bitcast 到孪生体,
   并把它同时用作 struct_type 与 `_get_member_offset` 的 hint ⇒ 偏移与 GEP 同源。
3) 读取路径 HandlesExprAttr._gep_and_Load_member 共用同一工具函数(读写两路同判据,
   避免"写修了读没修"再吃一轮)。
4) opaque-GEP 守卫报文补"同短名 struct 实例(key(IR 名)=槽数)"清单:清单含已具化项
   ⇒ 属"偏移与 GEP 未同源";全是 opaque ⇒ 属"body 从未 set_body"——一次报文即可判定是哪一侧。

找不到孪生体时**不兜底**:仍由守卫 fail-fast 并报出实例清单(本仓"不许静默兜底"约定)。

验证:py_compile 通过(BaseGen / HandlesAssign / HandlesExprAttr 三文件)。
2026-09-30 11:57:07 +08:00
TermiNexus 55cbf1d261 fix(HandlesImports): 继承前缀前置改为"按表头判幂等",不再按名字过滤整块(重名继承项不再被删)
类别:a 编译器缺陷(静默删掉布局条目,使继承段下标整体前移、写到类型不同的槽)。

问题(旧判据):`_EmitExternalClassDeclLlvm` 的前置段用
`nm2 not in {class_members[ClassName] 整表所有名字}` 过滤祖先块。该判据把
**与子类自有字段重名的继承项整条删掉**,于是继承段与 body 的继承前缀失去一一对应:
被删项**之后**的继承字段表内下标整体前移 k(k = 被删项数),而查询侧回退路径用
`base + 表内下标`(FuncGen._get_member_offset)⇒ 落到类型完全不同的槽。
现场(本轮):TypeGenCType 重复声明 5 个父类已有字段后,继承字段
LastBuiltinsTypedefResult 的下标 32→27,`= "t.CPtr"` 写进 i32 槽 ⇒
"指针隐式转整数已禁止:不能把指针类型 i8* 赋给整数类型 i32"。

新判据(抽成模块级 PrependInheritedBlock,可独立验证):
  ① 不删块内条目——块内每项都对应 body 的一个继承槽,删任一项都会整体前移;
  ② 幂等——只问"表头前 len(块) 项的名字序列是否等于祖先块的名字序列",相等即整块
     已前置 → 不再前置(保住 2026-08-10 修的重复字段问题:BasicBlock [Next, Next, …]);
  ③ 同名并存——子类自有字段与继承项同名时两者都留(定义端 body 同样是"继承前缀 +
     自有段",两侧槽数一致);源码层面的这种重名已由 HandlesClassDef 的"字段名遮蔽
     fail-fast"拦下(1903ed14)。

冒烟(纯逻辑,跑完删除):首次前置=块+自有;重复调用不再前置;**同名项仍全保留**
(对照旧实现会删掉块首项 ⇒ 下标前移);空块 no-op。py_compile 通过。

已知残留(写进函数文档):若此前只前置过部分祖先块(父类表当时较短),本次会整块再前置
一次 ⇒ 该前缀出现重复条目;调用点上方刚对父类 force 重载(块内容稳定),该情形仅在
"父类表被中途改写"时出现,且重复条目会在成员槽失败报文的"名序"里直接可见。
2026-09-30 11:18:07 +08:00
TermiNexus a43513c9a7 fix(FuncGen): 成员表与 body 等长时不再做类型对齐校准(base 恒为 0),修 TypeGenMixin 字段偏移平移
判据:_member_base 的 delta 长度启发式在 _Delta == 0 时给出 base=0(FuncGen.py:441),
而 base=2 **只可能**来自其后的"类型一致性校准"(:446-480,对 b∈{0,1,2} 逐项比类型取高分)。
但该校准用的是**严格类型相等**:声明侧的联合类型(如 `list[str] | t.CPtr`)可能被记成
折叠后的 i8*,而 body 槽仍是具体指针(`%list[str]*`)⇒ b=0 丢掉这些匹配,某个平移 b
却能靠指针槽互相撞分拿到更高分。
_Delta == 0(成员表项数 == body 槽数)本身就是"逐位对齐"(定义端 body 由成员表生成;
跨模块端 body(.stub.ll) 与 .vpi 同一声明顺序)⇒ base 必须为 0,此时不应再校准。

现场(本次):TypeGenMixin 41 成员 / 41 槽(@t.NoVTable,_Delta=0),base 被抬到 2 ⇒
Generator.vp:136 `SubTypeGen.Gen = self`(Gen 声明 `%"47382dc8277c9d28.Generator"*`、
正确槽为 0)写到槽 40(`%"6fbe68e8fca81223.TypeSpecArena"*`)⇒
"指针隐式转换已禁止(非基本类型(结构体/类/联合)):不能把指针类型 … 隐式转为 …"。
(该报文本身是上一提交 909d8ff0 补的"成员槽来源明细",这次正是靠它直接读出了
base/下标/声明槽类型/布局槽类型四项事实。)

附带:成员槽失败报文再补"名序"(_DescribeMemberSlot 打印 index:name 全表)——偏移算错时
第一现场就是"名序与 body 布局不同源",逐项列出即可直接判定是哪一侧漂移,不必再猜。

验证:py_compile 通过。_Delta != 0 的既有校准现场(ast.Constant 的 `cnst.lineno` 落到
pool 槽等)不受影响(本次只加前置条件,未改校准逻辑本身)。
2026-09-30 11:15:58 +08:00
TermiNexus de89fda73d fix(HandlesClassDef/DeclarationGenerator): 未声明字段不再静默兜底 i32,推不出类型即 fail-fast
类别:a 编译器缺陷(静默兜底推断)+ 隐患预防。

判据(为什么必须去掉 i32 兜底):成员类型是 struct 布局的唯一来源,静默把"没声明/
推不出"的字段当成 i32 会把错误推到下游——值域不匹配时表现为与真因无关的报文
(同族既有现场:`free_lists: uint64*` 被当成 i32 后,`self.free_lists = base`(i8*) 报
"指针隐式转整数已禁止")。项目约定:类型信息缺失立即终止(与同函数 `self.X: T` 分支
HandlesClassDef.py:1356-1386 已有 fail-fast 同口径)。

改动 1(定义端布局,权威侧)HandlesClassDef._scan_node_for_self_assign 的 Assign 分支:
  删除 `MemberType = ir.IntType(32)` 默认值,改为显式 `MemberType = None` + 白名单定型;
  推不出即 compile_error(含 文件:行:列、源码行、取值原文、建议写法,行号按 _line_offset
  还原)+ sys.exit(1)。
  采纳形态(逐条写进注释,必须与 stub 侧一致):
    整型/布尔字面量→i32;浮点字面量→double;字符串字面量→i8*;
    值为本方法带注解形参→该形参类型;白名单调用 float()/double()→double、char()→i8、
    int()/long()/short()→i32、`x.len()`→i32;其余(None 字面量/BinOp/任意调用…)→终止。

改动 2(stub 声明侧)DeclarationGenerator._CollectOwnMemberTypes 的 Assign 分支:
  删除 `InferredType = 'i32'` 默认值,改白名单 + 推不出即 raise `[FATAL] …拒绝静默写成
  i32`;同时纠正两处与定义端不一致的映射:浮点字面量 'float'(f32) → 'double'(f64),
  布尔字面量 'i8' → 'i32'(定义端 bool 走 int 分支),否则 stub 与定义端槽宽/类型分叉。

预检(确认不打断既有构建):用 TPC 自家解析器扫全工作区 .vp(678 类;口径 =
类体 AnnAssign + 任意位置 `self.X: T` + 祖先传递声明),"未声明字段 + 首次裸赋值"
实例数 = 0(曾疑似的 4 例 `self.Next = None` 均为继承字段:G4Node/Value 继承
GSListNode 已声明 Next)。

冒烟(合成样例,跑完删除):
  A `self.flag = None`(未声明、None 无类型信息)⇒ SystemExit(1) + 明确报文;
  B `self.n = 5`(可定型)⇒ 正常翻译;C `flag: int` 已声明后赋 None ⇒ 正常翻译。

已知残留(本次未动,已在代码注释里写明,避免下次盲修):stub 侧方法体内裸
`self.X = ...` 仍被 seen_member_names 过滤(该集合只含本类自有声明、不含继承字段);
若放行,继承字段(如 G4Node.Next)会被当成自有字段重复加槽 → 反而制造布局分叉。
要放行必须先在该处补"祖先字段名"判定。
2026-09-30 10:58:23 +08:00
TermiNexus 1903ed14f6 fix(HandlesClassDef): 类体字段与祖先字段重名 ⇒ fail-fast(消"继承段错位写错槽"与 split-brain)
类别:a 编译器缺陷(静默写错槽)+ c 源码违规(子类重复声明父类字段)的入口拦截。

判据(为什么重名必须终止,而不是容忍):
1. 子类同名声明会成为**自有字段**,与**同名继承项**在 class_members 里并存;跨模块侧
   的前置过滤器(HandlesImports._FlattenExt 前置段 :2516-2521)按"名字已在表内"过滤
   ——它本意只是防同一函数重入时重复前置,却把**同名继承项整条丢掉** ⇒ 继承段与
   struct body 的继承前缀失去一一对应:被丢项**之后**的继承字段表内下标整体前移 k,
   而查询侧回退路径用 `base + 表内下标`(FuncGen._get_member_offset)⇒ 落到类型完全
   不同的槽。现场:TypeGenCType 重复声明 5 个父类已有字段后,继承字段
   LastBuiltinsTypedefResult(下标 32)前移为 27 = CTypedefNameIdxBuilt(i32)
   ⇒ `self.Gen.TypeGenCType.LastBuiltinsTypedefResult = "t.CPtr"` 报
   "指针隐式转整数已禁止:不能把指针类型 i8* 赋给整数类型 i32"。
2. 父类方法按父类布局访问该名字的槽、子类方法按自有槽访问 ⇒ 同一变量 split-brain
   (一侧写、另一侧读),运行期静默错值。

实现:在类体自有字段收集段之后(:1047-1116)比对"自有字段名"与"沿 class_parents
传递收集到的全部祖先声明字段名",命中即 compile_error(含 文件:行:列、源码行、
祖先类名与逐字段明细,行号按 _line_offset 还原)+ sys.exit(1)。自有段用快照切分
(:903),祖先未就绪时视为无祖先 ⇒ 只少报不误报。

预检(避免新检查打断既有构建):用 TPC 自家解析器扫 App+includes 全部 .vp
(226 文件 / 382 类,口径=类体 AnnAssign + __init__ 带注解 self.x),既有代码
重名遮蔽数 = 0。

冒烟(合成样例,跑完删除):
  正例 class Child(Base) 重复声明 A ⇒ SystemExit code=1,报文给出 Child/A/Base/6:4;
  反例 Child 只声明新字段 C ⇒ 正常翻译。

配套:TransPyV 侧已撤销 TypeGenCType 的重复声明(0e5ebd0)。
2026-09-30 10:50:23 +08:00
TermiNexus 909d8ff0eb fix(诊断): _get_node_info 还原自动注入 import 的行偏移;属性写入失败补成员槽来源明细
1) 行号还原(b 机制缺陷,实测可证):Phase2Translator._inject_auto_imports 在源码头
   注入 import _list/_dict/json,AST 节点行号整体下移 N
   (translator._line_offset = N),而 Gen._current_source_lines 是**磁盘**原文。
   BaseGen._get_node_info / _DescribeValueNode 此前直接用 AST 行号读源码 ⇒ 同一次
   fail-fast 里出现两套互相矛盾的行号:_ErrorStack 帧印磁盘行 1140/1141,
   _get_node_info 印 1142,并把源码显示成 1142 行的 `return "t.CPtr"`
   (真实错误行是 1141 的 `...LastBuiltinsTypedefResult = "t.CPtr"`,与被指语句无关)。
   新增 BaseGen._DiskLineno,判据与 HandlesBase._FailFastFixedArrayKeyword:687、
   HandlesAssign._RaiseGlobalAttrError:2051、LlvmGenerator/HandlesBody 的
   _ErrorStack 各站点逐字一致;标签行号(_current_node_info)与源码行一并还原。

2) 成员槽来源明细:HandlesAssign 的「对象.字段 = 值」写入路径(:1182)此前
   _StoreWithCoerce 失败时只报隐式转换禁令,不报目标槽来源,报文与被指语句、
   偏移对不上。现与 `self.x = ...` 路径(:897-905)同口径补 _DescribeMemberSlot:
   附上 class_members 声明侧(项数/base/表内下标/声明槽类型)与布局侧
   (struct name/元素数/elements[offset])两侧数据,可直接判定是声明侧退化
   还是布局侧退化(本次现场即"表内下标被同名继承项过滤移位"一类)。
2026-09-30 10:46:42 +08:00
TermiNexus a18ca4da3b fix(ExprAttr): 嵌套属性读的每个 silent return 都留痕(含内层失败原因链),Repl.vp:802 类错误不再只能看到"基座形态"
类别:a 编译器缺陷(可观测性补齐:报错点与真因相隔数层)

现象:TPC 编译 TransPyV/App/lib/core/Repl.vp:802
  `ct_names: list[str]* = GenApi._g_gen.TypeGenCType.CTypedefNames`
报「带注解赋值表达式 …(Attribute 形态)编译期求值为空(表达式未解析出目标、无值可存)…
赋值将被跳过」,报文里只有「基座形态=Attribute(.TypeGenCType←Attribute(._g_gen←Name(GenApi)))」,
看不出是三级链的哪一级失败、为什么失败。

判据:`_handle_attr_nested`(多级属性读)里有 7 处 `return None` 是**静默**的,而
`Gen._LastAttrNone`(`_NoteAttrNone` 记账,供 `_DescribeAttrBase` 回显"读取侧原因")只在
`_handle_attr_name`/`_gep_and_Load_member` 一侧登记 ⇒ 本函数的失败在报文里完全没有原因。
且嵌套读取时**内层**的记账会被外层覆盖,即使内层登记了也回显不出来。

修法(纯记账,不改任何判定/IR):
1. `_handle_attr_nested` 的每处静默 return 都调 `_NoteAttrNone` 记下**具体事实**:
   · 基座求值为空/非指针 → 附 type + **内层链的失败原因**(把 `_LastAttrNone` 里"字段名不同"
     的那条记录串进来,形成原因链);
   · 类名提示未命中 Gen.structs 键(报 structs 键数);
   · struct 实例类型异常;
   · body 仍 opaque 且无泛型特化成员;
   · **字段在类中取不到偏移**(报成员表项数 / body 槽数 —— 跨模块成员表缺失或漂移的关键判据);
   · 槽是 void 占位;
   · 结构体分支 pointee 未匹配任何类(报 structs 键数)。
2. `_DescribeAttrBase` 的回显键是 (字段名, 类名),本次登记与报错节点一致 ⇒ 下次同类报错将
   直接给出"读取侧原因=…",一轮定位到级与因。

验证:py_compile EXIT=0。真因证据(同一现场、另一侧):`TypeGenCType` 类体一个字段都没声明,
5 个字段全靠方法体裸 `self.x = ...`;其 .vpi(temp/05d515186a7c3668.vpi)里确实没有
CTypedefNames —— 跨模块成员表缺该字段即本次链断之处,已在 TransPyV 侧按"跨模块可见字段
必须在类体声明"补上(见另一仓 commit)。
2026-09-30 09:59:27 +08:00
TermiNexus c86ecb0c89 refactor(LLVMCG/Handles): 直建 ir.Function 点全部收敛到幂等入口,3 处不再吞 fail-fast
类别:a 编译器缺陷(同一根因的其它变体收口:函数声明创建的唯一入口 + fail-fast 不被吞)

背景(前几轮已修两处):llvmlite 的 `Function.__init__` 不是事务性的(名字在 `_set_name`
就进 module.scope,之后建 Argument/ReturnValue 才断言类型,最后才 add_global),中途失败会
留下悬空名字;本仓已加两道防线:全局守卫 `InstallFuncCreateGuard()`(回滚名字 + 带调用点的
FailFastError)与唯一幂等入口 `_CreateOrReuseFuncDecl`(创建前自检、同名同类型复用、异类型
带两侧签名 fail-fast)。但仍有 20+ 处**绕过入口直建**(同名时抛裸 DuplicatedNameError、且
名字被占/半注册时只能看到没有上下文的名字),以及若干处 `except Exception` 会把这颗
FailFastError 吞掉(声明静默缺失、真因丢失)。

改动:
1. 创建点收敛(21 处,行为等价或更严)——`X = ir.Function(<gen>.module, <fty>, name=<N>)`
   → `X = <gen>._CreateOrReuseFuncDecl(<N>, <fty>)`:
   · Handles:HandlesAnnAssign[llvm.memcpy]、HandlesAssign[llvm.memcpy.p0i8.p0i8.i64 ×2]、
     HandlesBase[GetOrCreateFuncDecl / GetOrCreateStubFuncDecl]、
     HandlesExpr[malloc]、HandlesExprBuiltin[strlen / llvm.memcpy]、
     HandlesExprCall[llvm.* 与普通 / win32 API / strlen / llvm.memcpy]、
     HandlesAsyncFunctions[malloc / create_* / resume_* / CRT 声明 / free]、
     HandlesExprLambda[lambda 包装]、HandlesFunctions[_replace_function_decl 的 old_func=None 分支]、
     HandlesImports[output stub 按需加载]
   · LLVMCG:BaseGen[_DeclareExternalFunc 与 6 处 --hotspot-sites 内部符号]、
     ExprGen[llvm.pow.f64]、VaArg[llvm.va_start / llvm.va_end]
   原有 globals/functions 预检查与随后的 `Gen.functions[...] = X` 赋值一律保留。
   注:`_DeclareExternalFunc` 收敛后外部声明会同时登记进 `self.functions`(幂等入口固有行为,
   与既有 `get_or_declare_c_func` 一致;该路径仅 --hotspot-sites 调试特性使用)。
2. fail-fast 不再被吞(3 处):在包裹创建的 `except Exception` 首部加
   `isinstance(_e, FailFastError) → raise`:
   · HandlesExpr[malloc 的 null 回退分支]、HandlesExprBuiltin[strlen]、HandlesExprCall[strlen]
   (其余异常维持原行为,本轮不扩大 fatal 面。)
   另有 HandlesFunctions / HandlesImports 的同类 handler 前几轮已具备 FFE 重抛,本次未重复改。

验证:13 个改动文件 `py_compile` 全部 EXIT=0;冒烟 4 例通过——同名同类型复用同一对象、
异签名 → FailFastError(报文含两侧签名 i64/i32)、名字被非函数全局占用 → FailFastError、
`llvm.va_start` 幂等。另:`ir.Function(` 在 lib 内仅剩 `BaseGen._CreateOrReuseFuncDecl` 内部
这一处(豁免点);`DecoratorPass.py` 的 `module` 为外部传入(非 Gen 的模块)故其两处保持原样。
2026-09-30 09:53:41 +08:00
TermiNexus 4d14334bd2 fix(LLVMCG): 成员表对齐改为「前缀锚 vs 尾部锚」按类型可写入性打分,修 Generator.vp:164 误报
类别:a 编译器缺陷(TPC 侧;修正 1776dfa6 / b06dac1d 的过宽判据)

现象:翻译 TransPyV/App/lib/core/Gen/Generator.vp 时 :164 `SubTypeGenErr.Gen = self` 报
「指针隐式转换已禁止:不能把 %"47382dc8277c9d28.Generator"* 隐式转为
%"6fbe68e8fca81223.TypeSpecArena"*」—— 目标类型是槽 41 的 Specs,而 Gen 的真身在槽 0。

判据:TypeGenErr 继承 TypeGenBase(41 槽)且只有 1 个自有字段 ⇒ body 42 槽;其成员表只含
['Gen'] 一项(**残缺前缀**,真身在 body 头部)。上一轮"尾部对齐"的前置校验(前缀数 == 父类
struct 槽数)在本例**巧合成立**:42 - 1 == 41 ⇒ 该表被锚到槽 41(Specs)⇒ 取到错误槽类型。
同族上一例为 Generator.vp:136(TypeGenMixin,已由 1776dfa6 挡住,但同类巧合仍会复现)。

修法:不再用"长度关系 / 父类槽数"去断言表的对齐方式(那是猜),改为**比较两个候选锚**的
逐项得分(判据与建 body / 存储侧同一套 `_MemberSlotAccepts`):
  · 前缀锚 `_BaseNow()` —— 展平表 / 残缺前缀;
  · 尾部锚 `len(body) - len(表)` —— 仅自有字段(父类前缀未补齐,真身在 body 尾部)。
只有尾部锚**严格更高分**才采用它;同分/更低一律保持原行为(配对 → 下标法)⇒ 对既有正确
布局零行为变化,同时消除"巧合成立"的假阳性。命中尾部锚时每类留一次告警(报出两锚得分)。

验证:5 例冒烟全部通过 ——
A TypeGenErr(声明类型分别为通用指针 / Generator*,body 42 槽).Gen → 0(修前 41);
C AnnAssign 仅自有字段表 → target/annotation/value/simple = 8/9/10/11(尾部锚仍生效);
D ExceptHandler 重名表 + 联合类型 children → name = 9(布局配对仍生效);
E 自洽展平表 .value → 10(行为不变);FuncGen.py py_compile 通过。
2026-09-30 00:39:06 +08:00
TermiNexus 1776dfa60a fix(LLVMCG): 尾部对齐加「前缀数 == 父类 struct 槽数」前置校验,修正 Generator.vp:136 误报
类别:a 编译器缺陷(TPC 侧;修正 b06dac1d 引入的过宽判据)

现象:翻译 TransPyV/App/lib/core/Gen/Generator.vp 时 `SubTypeGen.Gen = self`(:136)报
「指针隐式转换已禁止(非基本类型):不能把 %"47382dc8277c9d28.Generator"* 隐式转为
%"6fbe68e8fca81223.TypeSpecArena"*」。

判据(事实链):
· SubTypeGen 静态类型 = rawptr[TypeGenMixin.TypeGenMixin],Gen 字段声明在基类 TypeGenBase;
  该 struct 41 槽:槽 0 = Generator*(Gen),槽 40 = TypeSpecArena*(Specs)。
· 抛错点 `_StoreWithCoerce` 用的是 **GEP 出来的槽类型**(TargetType = Ptr.type.pointee)
  ⇒ 报错里的 TypeSpecArena* 说明 "Gen 的偏移被算成了 40"(Specs 那一槽),而不是 0。
· 成因:b06dac1d 的「尾部对齐」在"成员表短于 body 字段区"时按 `len(elements) - len(表)`
  求前缀数;TypeGenMixin 的表只有 ['Gen'] 一项(且其声明类型已退化成通用指针 ⇒
  类型自校验被放行),于是被锚到 body 末尾 = 槽 40。

修法:尾部对齐加**必需的前缀校验**——前缀数必须等于**父类 struct 的槽数**(展平前缀就是
父类整份布局,含其继承链);父类 struct 不可得(跨模块未加载 / opaque)时**不介入**。
TypeGenMixin:40 ≠ len(TypeGenBase)=41 ⇒ 不介入,落回下标法得 0(Generator*,赋值合法)。
(正面场景仍生效:AnnAssign 表仅 4 项自有字段、body 12 槽、父类 Stmt 8 槽 ⇒ 8 == 8。)

验证:冒烟两例——反例 TypeGenMixin.Gen → 0(修前 40);正例 AnnAssign 仅自有字段表仍得
target/annotation/value/simple = 8/9/10/11;FuncGen py_compile 通过。
2026-09-29 23:58:09 +08:00
TermiNexus bd92dfdadf fix(LLVMCG): 成员表重名/联合类型字段不再打断布局配对,ExceptHandler.name 取到槽 9
类别:a 编译器缺陷(TPC 侧)

现象:翻译 TransPyV/App/lib/core/Gen/AsyncGenMixin.vp 时 `self._add_local(trans, hd.name)`
(:667)的第 2 个实参求值失败并终止编译。基址诊断给出的事实链:类 ExceptHandler
(%"831ce86a2fd82c0..." 即 astaux.vp)成员表 10 项 =
['parent','pool','lineno','col_offset','end_lineno','end_col_offset','children','children','type','name']
('children' 重复一次),struct body 10 槽 =
[i8*, AST*, MemManager*, i32×4, list*, AST*(type), i8*(name)],字段 'name' 计算偏移 = 10
⇒ 越界(正确槽 = 9)。

判据与修法(两条,均已写进代码注释与告警报文):
1) 布局配对断链:成员表里 'children' 的声明类型是**联合类型折叠后的通用指针**
   (`list[rawptr[AST]] | t.CPtr` ⇒ i8*),而 body 槽是具体指针(%"...list[...]"*)⇒
   原配对只比 `is` / `str()`,在这一条断链,其后所有条目不再消费槽位 ⇒ 目标字段退化为
   "base + 表内下标"('name' ⇒ 1 + 9 = 10)。修:两侧都是指针时复用存储/建 body 侧同一
   判据 `_MemberSlotAccepts`(通用指针 i8* 放行、具体指针按被指类型),标量仍走严格比较
   ——不引入新的过配对。修后 type = 8、name = 9 ✓。
2) 重名条目使长度判据失真:`_expected_count_pre = len(class_members) + vtable` 把重复项
   也算一槽 ⇒ 误判"body 不完整"而用**带重复项的表**重建 body(本模块布局比其它模块多一
   槽 ⇒ 跨模块 ABI 撕裂)。新增判据:表有重名**且** body 槽数恰等于「去重名数 + vtable」
   ⇒ 认定 body 已是权威布局(来自定义模块 stub),只报事实、不重建。
   另在"未被 body 配对命中"的回退路径上,把"成员表含重名条目 + 表项数/去重项数 + 该
   返回值取自回退路径可能失准"作为事实告警报出(每类一次)。

验证:冒烟复刻本现场(表含重名 'children' 且其类型为联合 i8*;body 10 槽)——逐字段
1..9 全部命中(parent=1 … children=7、type=8、name=9),无重名的自洽表行为不变;
FuncGen / StructGen 两文件 py_compile 通过。
2026-09-29 23:48:52 +08:00
TermiNexus 8f1ec5b715 fix(LLVMCG): llvmlite Function 创建事务化 + 不再吞 fail-fast,悬空名字不再复现
类别:a 编译器缺陷(TPC 侧)

现象:编译 StubMergerIR.vp 时 `_EmitExternalFuncDeclLlvm` 报「函数声明名字悬空占用:
'50741ad4b4b0934d.LLVMTypeCore.__init__' 已进 module.scope,但 module.globals 中无对应
对象」,且该 FailFastError 被 `_LoadModuleDeclarationsFromFile` 的节点级 `except Exception`
吞成一行 WARN「忽略节点 ClassDef LLVMTypeCore 异常」后 continue。

机制(判据已写进代码注释与报文字段):
1) llvmlite 的 `Function.__init__` 不是事务性的:名字在 `_set_name` 即进 module.scope,
   之后逐个建 Argument/ReturnValue 才做类型断言,最后才 add_global;中间任一步抛错都不
   回滚 scope ⇒ 留下「globals 查无、scope 已占用」的悬空名字,后续同名创建只能报裸
   DuplicatedNameError(或本仓的「名字悬空占用」),而真因——上一处被吞的创建失败——已丢失。
   修法:BaseGen 新增 `InstallFuncCreateGuard()`(随模块导入装一次),在**失败路径**上
   回滚名字(补 llvmlite 缺的那半截事务,模块状态可重试)并抛 FailFastError,报文含
   函数名 / 本次签名 / 原始异常 / 创建调用点(栈上首个非 llvmlite、非包装函数的帧);
   `DuplicatedNameError` 原样上抛——那是各调用点有意处理的正常分支(如「已存在则复用」)。
2) 首建失败与该 FailFastError 被 5 处兜底吞掉,使真因退化成一行 WARN:
   · HandlesImports._LoadModuleDeclarationsFromFile 节点级 except(本现场的直接来源)
   · HandlesImports stub 声明加载循环的两层 except
   · HandlesImports._TryLoadFuncDeclsFromOutputStub 的裸 `except Exception: pass`
   · HandlesFunctions._replace_function_decl 的 `except Exception: return old_func`
     (此处 old_func 必为 None ⇒ 调用点把 None 当函数声明用)
   修法:一律 `except FailFastError: raise`;其余异常维持原有口径(严格模式上抛 / 其余告警)。

验证:4 例冒烟(非法签名 → FailFastError 且报文含调用点;名字已回滚 ⇒ 同名可重试成功;
重复创建仍是 DuplicatedNameError 且旧名未被回滚;合法创建零副作用)全部通过;
BaseGen / HandlesImports / HandlesFunctions 三文件 py_compile 通过。
2026-09-29 23:31:57 +08:00
TermiNexus b06dac1d6b fix(LLVMCG): 字段偏移改用「冻结表有效性锚点 + 尾部对齐」,AnnAssign.value 不再读到 i32
类别:a 编译器缺陷(TPC 侧)

现象:编译 TranslatorGenMixin.vp 时 fail-fast —— getelementptr(GEP) 的指针操作数不是
指针类型:操作数类型=i32、操作数名='value.9'、GEP 名='vtable_slot_AST'。即
`aa.value.kind()`(TranslatorGenMixin.vp:788)的接收者读成了整数:AnnAssign.value 落到
槽 3(lineno, i32),随后虚分派取 vtable 时把 i32 当指针用。

根因(两条独立判据,均写进代码注释与告警报文):
1) 冻结表陈旧:StructGen 建 body 时把「成员名→槽位」挂在 struct 实例上,而 body 之后会被
   「不完整即清除」(StructGen 预检 existing_st.elements = None)与 stub 加载
   (HandlesImports._TryLoadStructFromStub 的 set_body)清空后重设,重设路径不更新该表
   ⇒ 陈旧表按旧布局取槽(AnnAssign 在「自有 4 字段 + vtable」时冻结 {'value': 3},body
   被重设为 12 槽正确布局后仍取槽 3)。修:建表时同时记下 body 对象身份
   (_tpc_member_body,set_body 每次赋新 tuple),查询侧用 `is` 判同一性,失效即不再采用。
2) 未展平成员表:表只含自有字段(父类前缀未补齐)时,「base(0/1) + 表内下标」会把整类字段
   整体前移(target/annotation 落到指针槽 ⇒ 类型巧合、静默读错对象;value 落到 i32 槽 ⇒
   本次崩溃)。修:表短于 body 字段区时按**尾部对齐**取槽(展平规则 = 父类前缀 + 自有字段
   ⇒ 自有字段恒为 body 尾部 len(表) 项),并用 _MemberSlotAccepts 逐项类型比对自校验;
   表自洽(长度 == 字段区)时本路径不介入 ⇒ 对既有正确行为零变化;REnum 是 max-variant
   镜像布局,明确排除。命中时每类留一次告警,报出表长/字段区/取槽结果。

验证:8 例冒烟(冻结表新鲜;陈旧×展平表 / ×自有表;无冻结×自有表 / ×展平表;无 vtable;
整类逐字段 = 8/9/10/11;尾部类型不符时落回旧路径)全部通过;两文件 py_compile 通过。
2026-09-29 23:18:37 +08:00
TermiNexus 90d81df4f8 fix(tpc): 自指针指向 opaque 同名实例时归一为具化实例;GEP 边界拦下 opaque pointee
类别 a(编译器缺陷):llvmlite 的 typed pointer GEP 必须在编译期算出元素类型——
`GEPInstr` → `ptr.type.gep(i)` → `IdentifiedStructType.gep` → `self.elements[i]`,
对 **opaque**(elements is None)pointee 抛裸 TypeError('NoneType' object is not
subscriptable)。现场:includes/ast/astaux.vp `Keyword.__init__` 的
`self.arg = _copy_str(pool, arg)`,经 HandlesAssign._HandleAttributeStoreLlvm:869
的 GEP 崩溃。成因:同名 struct 存在**两份实例**——函数签名/变量里那份仍 opaque,
类键 `Gen.structs[ClassName]` 上那份已具化;偏移取自类键那份、GEP 却用 self 那份。

- HandlesAssign._HandleAttributeStoreLlvm:GEP 前把 self 指针 bitcast 到**有 body 的
  那份实例**(仅当 self 的 pointee opaque 且类键实例已具化),并把该实例作为
  StructHint 传给 _get_member_offset —— 偏移与 GEP 必须同源(与属性读取路径
  _gep_and_Load_member 的 415916b3 同口径);max_offset 的取值也补上 elements 为
  None 的判据(原实现会对 opaque 实例 len() 触发断言)。
- BaseGen.RequirePointerOperand 新增 RequireConcretePointee(GEP 专用):基址指向
  opaque 具名结构体时 fail-fast,报文给出基址类型、结构体名、GEP 名与当前节点位置,
  不再落到 llvmlite 的裸 TypeError(typed pointer 下"GEP 指向 opaque 结构体"无任何
  合法场景:元素类型必须编译期可知)。

本地冒烟:opaque pointee 被拦下(FailFastError,而非 llvmlite 的 TypeError);
具化 pointee 正常通过。
2026-09-29 22:10:11 +08:00