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 侧补登记(同模块路径已通)。
This commit is contained in:
TermiNexus committed 2026-10-01 23:29:54 +08:00
1 parent a8f6ca006d
commit 5eef845e5b
5 files changed
+159 -3

No files matched your search

+25
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import vast as ast
import copy
import re
from typing import List
@@ -581,6 +582,20 @@ class DeclarationGenerator:
struct_type_name: str = f'%"{self.module_sha1}.{class_name}"' if self.module_sha1 else f'%struct.{class_name}'
struct_decl: str = f'{struct_type_name} = type {{ {", ".join(member_types)} }}'
decls.append(struct_decl)
# 嵌套类 / 嵌套 struct:定义端以扁平名 `{外类}_{内类}` 发射(见 HandlesClassDef.
# _EmitNestedClassDefs),此处同步产出同名完整类型声明 —— 否则跨模块引用
# `外类.内类` 只会得到一个 opaque 前向声明(指针可用、字段偏移全失效)。
# deepcopy 后改名再递归:本函数所有名字都派生自 node.name,改名即全链路一致;
# 判断/枚举/联合等分支在递归时会各自按内层类自身的基类重新分派。
for _nested_item in getattr(node, 'body', None) or []:
if not isinstance(_nested_item, ast.ClassDef):
continue
_nested_inner: str | None = getattr(_nested_item, 'name', None)
if not _nested_inner:
continue
_nested_node: ast.ClassDef = copy.deepcopy(_nested_item)
_nested_node.name = f'{class_name}_{_nested_inner}'
decls.extend(self._generate_class_decl(_nested_node))
if is_cpython_object or is_cvtable:
new_func_name: str = f'{class_name}.__before_init__'
if self.module_sha1:
@@ -1265,6 +1280,16 @@ class DeclarationGenerator:
module_name = annotation.value.id
elif isinstance(annotation.value, ast.Attribute) and hasattr(annotation.value, 'attr'):
module_name = annotation.value.attr
# 嵌套类 / 嵌套 struct:限定名 `外类.内类`(module_name 是**类名**而非模块名)。
# 定义端以扁平名 `{外类}_{内类}` 发射(见 HandlesClassDef._EmitNestedClassDefs),
# 故此处按扁平名拼类型;命中与否由 struct_names 里是否存在该扁平名裁决
# (不存在即维持下方既有路径,不改变任何既有类型名)。
if module_name and module_name != 't' and module_name != 'c':
NestedName: str = f'{module_name}_{attr_name}'
if NestedName in self.struct_names:
NestedSha1: str | None = self.struct_sha1_map.get(NestedName, self.module_sha1)
NestedStr: str = f'%"{NestedSha1}.{NestedName}"' if NestedSha1 else f'%struct.{NestedName}'
return NestedStr if embedded else f'{NestedStr}*'
if (module_name == 't' and attr_name == 'State') or attr_name == 'State':
return ''
if (module_name == 't' and attr_name == 'Callable') or attr_name == 'Callable':
+33
View File
@@ -756,6 +756,11 @@ class Phase1Generator:
else:
struct_names.add(node.name)
struct_sha1_map[node.name] = sha1_key
# 嵌套类 / 嵌套 struct:定义端以扁平名 `{外类}_{内类}` 发射
#(HandlesClassDef._EmitNestedClassDefs),此处同步登记,
# 供跨模块引用 `外类.内类` / 扁平名时拿到同一类型名与前缀。
_RegisterNestedStructNames(node, node.name, sha1_key,
struct_names, struct_sha1_map, class_def_map)
# 收集所有类(含枚举/异常)的 ClassDef 节点,供 DeclarationGenerator
# 跨模块字段展平使用(如 exprs.py 的 Name(AST) 需查找 base.py 的 AST)
class_def_map[node.name] = node
@@ -775,3 +780,31 @@ class Phase1Generator:
_vlog().warning(f".ll 声明生成失败: {e}")
with open(ll_path, 'w', encoding='utf-8') as f:
f.write(f"; declaration for {os.path.basename(src_path)}\n; error: {e}\n")
# ======================================================================
# _RegisterNestedStructNames - 嵌套类/嵌套 struct 的扁平名登记
#
# 与定义端 HandlesClassDef._EmitNestedClassDefs 的命名口径同源(也同
# Phase2Translator._build_struct_sha1_map 的登记),使跨模块引用 `外类.内类`
# 或扁平名 `外类_内类` 时,DeclarationGenerator._get_type_str 与
# TypeAnnotationResolver 都落到同一类型名与前缀(否则会拼出 `%"{sha}.内类"`
# 这种与定义端 `%"{sha}.外类_内类"` 不同源的幽灵名)。多层嵌套递归为 A_B_C。
# ======================================================================
def _RegisterNestedStructNames(Node, Prefix: str, Sha1Key: str,
StructNames: set, StructSha1Map: dict, ClassDefMap: dict) -> None:
"""把类体内的嵌套 ClassDef 以扁平名 `{外类}_{内类}` 递归登记"""
Body = getattr(Node, 'body', None)
if not Body:
return
for Item in Body:
if not isinstance(Item, ast.ClassDef):
continue
InnerName: str | None = getattr(Item, 'name', None)
if not InnerName:
continue
FlatName: str = f'{Prefix}_{InnerName}'
StructNames.add(FlatName)
StructSha1Map[FlatName] = Sha1Key
ClassDefMap[FlatName] = Item
_RegisterNestedStructNames(Item, FlatName, Sha1Key, StructNames, StructSha1Map, ClassDefMap)
+26 -3
View File
@@ -1384,7 +1384,7 @@ class Phase2Translator:
_, tree = parse_python_file(pyi_path)
if tree is None:
continue
for node in ast.walk(tree):
for node in ast.iter_child_nodes(tree):
# 每类只登记一次,**先扫到的为准**:判据必须是**类名**。
# 原实现写成 `if sha1_key in struct_sha1_map` —— 用模块 SHA1 去查一张
# 按**类名**建的表,判据恒假 ⇒ 实际是"后扫到的覆盖先扫到的",
@@ -1392,10 +1392,33 @@ class Phase2Translator:
# includes/ast/tokens.vp 各有一个 `class Keyword`,定义端拿到的前缀
# 可能是**另一个模块**的 sha1 ⇒ 同一类出现两个 struct 实例、
# `__before_init__` 声明与调用点分叉)。
if isinstance(node, ast.ClassDef) and node.name not in struct_sha1_map:
struct_sha1_map[node.name] = sha1_key
if isinstance(node, ast.ClassDef):
# 嵌套类按扁平名 `{外类}_{内类}` 登记(与定义端
# HandlesClassDef._EmitNestedClassDefs、Phase1Generator.
# _RegisterNestedStructNames 同源)。此前用 ast.walk 会把嵌套类按
# **短名** `Inner` 登记 —— 既占住平坦命名空间,又与定义端的
# `外类_内类` 不同源(跨模块前缀分裂)。
self._RegisterStructSha1Rec(node, node.name, sha1_key, struct_sha1_map)
return struct_sha1_map
def _RegisterStructSha1Rec(self, Node, FlatName: str, Sha1Key: str,
StructSha1Map: dict) -> None:
"""递归登记"类名 → 定义模块 SHA1"(嵌套类用扁平名 `{外类}_{内类}`)"""
if not FlatName:
return
if FlatName not in StructSha1Map:
StructSha1Map[FlatName] = Sha1Key
Body = getattr(Node, 'body', None)
if not Body:
return
for Item in Body:
if not isinstance(Item, ast.ClassDef):
continue
InnerName: str | None = getattr(Item, 'name', None)
if not InnerName:
continue
self._RegisterStructSha1Rec(Item, f'{FlatName}_{InnerName}', Sha1Key, StructSha1Map)
def run(self) -> None:
"""扫描源目录,翻译并生成 .ll 含代码文件(导入遍历)"""
import sys as _sys
+57
View File
@@ -617,6 +617,54 @@ class ClassHandle(BaseHandle):
if isinstance(Node, ast.ClassDef) and self._is_generic_class(Node):
self._RegisterGenericClassTemplate(Node)
# ==================================================================
# _EmitNestedClassDefs - 递归发射类体内的嵌套 class / struct
#
# 命名与引用口径(用户 2026-10-01 定,与 union/renum 嵌套命名同源):
# · 内层结构体名 = `{外类}_{内类}`(Gen.structs / class_members 等同键);
# · 引用:限定名 `外类.内类`(TypeAnnotationResolver._from_node_attribute 的
# 扁平名回退)与扁平名 `外类_内类`(裸名直查 SymbolTable)都可用;
# · 多层嵌套自然递归为 A_B_C(deepcopy 后改名再递归)。
# 用 deepcopy 而不是临时改原名:源码 AST 节点还会被 _CollectOwnMemberTypes /
# SymbolExtractor / vpi 写出等路径读到,不得留下改名的副作用。
# ==================================================================
def _EmitNestedClassDefs(self, Node: ast.ClassDef, Gen: LlvmGeneratorMixin) -> None:
"""把类体内的嵌套 ClassDef 逐个以 `{外类}_{内类}` 递归发射(见上方注释)"""
if Node is None:
return
Body: list | None = getattr(Node, 'body', None)
ClassName: str | None = getattr(Node, 'name', None)
if not Body or not ClassName:
return
for Item in list(Body):
if not isinstance(Item, ast.ClassDef):
continue
InnerName: str | None = getattr(Item, 'name', None)
if not InnerName:
continue
NestedName: str = f'{ClassName}_{InnerName}'
# 已发射过(多遍/重复调用)只补名字登记,不重复发射
if NestedName in Gen.structs and NestedName in Gen.class_members:
self._RegisterNestedClassName(ClassName, InnerName, NestedName)
continue
NestedNode: ast.ClassDef = copy.deepcopy(Item)
NestedNode.name = NestedName
self._EmitClassLlvm(NestedNode, Gen)
self._RegisterNestedClassName(ClassName, InnerName, NestedName)
def _RegisterNestedClassName(self, ClassName: str, InnerName: str, NestedName: str) -> None:
"""登记嵌套类名的三种写法(裸名 / `外类.内类` / 扁平名)
与 _EmitREnumLlvm 的变体三键登记同口径(见本文件 REnum 变体注册处),
供"限定名扁平回退"与"裸名直查"两条解析路径命中。
"""
MemberNode: CTypeInfo = CTypeInfo()
MemberNode.Name = NestedName
MemberNode.BaseType = t.CStruct(name=NestedName)
MemberNode.IsStruct = True
self.Trans.SymbolTable.insert(NestedName, MemberNode)
self.Trans.SymbolTable.insert(f'{ClassName}.{InnerName}', MemberNode)
def _EmitClassLlvm(self, Node: ast.ClassDef, Gen: LlvmGeneratorMixin, declare_only: bool = False) -> None:
ClassName: str = Node.name
# t.CType 及其子类(CChar/CInt/CVoid/CPtr/CTypeDefault 等)是"类型强转"标记,
@@ -677,6 +725,15 @@ class ClassHandle(BaseHandle):
IsNoVTable: bool
VtableByClassLevel: bool
IsCpythonObject, IsCVTable, ParentClassName, IsNoVTable, VtableByClassLevel = self._resolve_class_flags(Node, Gen)
# 嵌套类 / 嵌套 struct:类体里再写 class / struct 此前被**静默忽略**(本函数的
# 字段/方法遍历只认 AnnAssign / FunctionDef,CollectAnnAssignMember 对其它节点
# 直接返回 None)。现按既有扁平命名先例发射为独立结构体 `{外类}_{内类}`
# (与 _EmitUnionLlvm:1569 / _EmitREnumLlvm:1715 的 `f"{ClassName}_{Nested}"`
# 同源),并登记 SymbolTable 三键,使 `外类.内类` 与扁平名 `外类_内类` 都能解析。
# 时机:在 flags 解析之后、class_members 初始化之前 —— 让嵌套结构体先就绪,
# 外层字段注解引用 `外类.内类` 时即可命中(_resolve_class_flags 内的名字键
# 均已按 ClassName 建立,递归改名不影响外层)。
self._EmitNestedClassDefs(Node, Gen)
# 跟踪正在发射的类,防止递归特化时 _generate_structs 过早为空成员类设置 body
Gen._classes_in_progress.add(ClassName)
# 标记正在收集字段,阻止继承处理(_ResolveGenericSlice → _specialize_generic_class)
+18
View File
@@ -1208,6 +1208,24 @@ class TypeAnnotationResolver:
return _Recovered2
return CTypeInfo()
# 嵌套类 / 嵌套 struct:限定名 `外类.内类`(ModulePath 是**类名**而非模块名)。
# 命名口径见 HandlesClassDef._EmitNestedClassDefs —— 嵌套类以扁平名
# `{外类}_{内类}` 发射,并由 _RegisterNestedClassName 把扁平名与 `外类.内类`
# 一并插入 SymbolTable。本分支按**扁平名**取条目:BaseType/Name 必须是扁平名
# (若走下方 FullName 分支,会按短名拼出 `t.CStruct(name='Inner')`,与
# 声明端的 `%"{sha}.外类_内类"` 不同源 ⇒ 布局/字段偏移分叉)。
if ModulePath and TypeName:
NestedKey: str = f"{ModulePath}_{TypeName}"
if NestedKey in SymbolTable:
NestedEntry: CTypeInfo = SymbolTable[NestedKey]
if NestedEntry is not None:
Result = NestedEntry.Copy()
Result.Name = NestedKey
if Result.BaseType is None:
Result.BaseType = t.CStruct(name=NestedKey)
Result.IsStruct = True
return Result
if FullName in SymbolTable:
Info: CTypeInfo = SymbolTable[FullName]
if Info and Info.IsTypedef: