feat(overload): vpi 写入端把签名后缀写进 def 名,签名 hash 与上下文无关

_overload_sig_text 算 hash 时固定传 imported_modules/from_imports/trans = None,只靠全局结构体表解析,使定义端、vpi 写入端(translator 尚未创建)、读取端天然算出同一个 hash,无需跨阶段搬运 import 上下文;解析不到即 fail-fast。Converter 新增 _PyiOverloadSuffix:同一作用域同名 def >=2 时追加 _v{hash}(方法跳过 self,@staticmethod 不跳;extern/state/export/main 不加)。读取端 _inject_function_declare 用 fd.name 拼符号,自动与定义端一致,返回/形参注解缓存也按后缀分组不再互相覆盖。
This commit is contained in:
TermiNexus committed 2026-10-24 17:29:51 +08:00
1 parent 20e31b8dbd
commit f54c365b20
2 files changed
+362 -59

No files matched your search

+358 -58
View File
@@ -13,7 +13,7 @@ import sys
# _eval_binop_int / _eval_binop_valid - recursively evaluate BinOp constant values.
# Handles BitOr/BitAnd/Add/Sub and nested Name refs to registered CDefine constants.
# Handles BitOr/BitAnd/Add/Sub and nested Name refs to registered DefineT constants.
def _eval_binop_valid(node: ast.AST*) -> int:
"""Return 1 if a BinOp node is fully computable from constants or registered CDefines"""
if node is None: return 0
@@ -33,10 +33,10 @@ def _eval_binop_valid(node: ast.AST*) -> int:
if _eval_binop_valid(bop.right) != 0: return 1
return 0
if k == ast.ASTKind.Name:
# Name reference: check whether it is a registered CDefine constant
# Name reference: check whether it is a registered DefineT constant
nm: ast.Name* = ast.Name*(node)
if nm.id is not None:
# Look up in the local CDefine table
# Look up in the local DefineT table
val: int64 = _lookup_cdefine_for_pyi(nm.id)
if _pyi_cdefine_found() != 0: return 1
return 0
@@ -81,11 +81,11 @@ def _eval_binop_int(node: ast.AST*) -> int64:
return 0
# Forward declaration for CDefine lookup (real impl in HandlesType);
# Forward declaration for DefineT lookup (real impl in HandlesType);
# weak refs avoid circular dependencies.
_g_pyi_cdefine_found: int = 0
# Per-file CDefine value cache (vpi only): vpi runs before the translate phase, so
# Per-file DefineT value cache (vpi only): vpi runs before the translate phase, so
# _GeneratePyiFromAst registers in order; C arrays dodge list.get misdispatch.
_g_pyi_cdef_names: str* = None
_g_pyi_cdef_vals: int64* = None
@@ -93,12 +93,12 @@ _g_pyi_cdef_count: int = 0
def _lookup_cdefine_for_pyi(name: str) -> int64:
"""Look up a constant value from this file's CDefine cache or the local CDefine table"""
"""Look up a constant value from this file's DefineT cache or the local DefineT table"""
global _g_pyi_cdefine_found
# Look up this file's CDefine cache first (globals are empty during vpi gen)
# Look up this file's DefineT cache first (globals are empty during vpi gen)
if _g_pyi_cdef_names is not None and _g_pyi_cdef_vals is not None:
# Bound loop by _g_pyi_cdef_count; never len()/.get on i8** (len falls back
# to strlen, .get misdispatches to HashTable.get) -- index via t.CPtr(base + i*8).
# to strlen, .get misdispatches to HashTable.get) -- index via rawptr(base + i*8).
pn: sizet = sizet(_g_pyi_cdef_count)
for pi in range(int(pn)):
entry_pc: str* = (str*)(uint64(_g_pyi_cdef_names) + pi * 8)
@@ -115,7 +115,7 @@ def _lookup_cdefine_for_pyi(name: str) -> int64:
def _eval_cdefine_value_cache(node: ast.AST*) -> int64:
"""Evaluate a CDefine value for preprocessing registration (Constant/UnaryOp/BinOp/Call/Name)"""
"""Evaluate a DefineT value for preprocessing registration (Constant/UnaryOp/BinOp/Call/Name)"""
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.Constant:
@@ -160,6 +160,30 @@ def _PyiEmit(buf: bytes, buf_size: sizet, pos: sizet, text: str) -> sizet:
return pos + tlen
# _PyiEmitEnumValue - 把枚举成员值写成 " = N",与 ClassGenEnum._extract_int_value 同口径。
# 值为字面量(含负号)时写真实值;其余形态定义端也算作 0,这里同样写 0。
# 为什么必须写:vpi 是跨模块唯一的签名来源,枚举成员值丢了就没法在依赖方重建 EnumTable。
def _PyiEmitEnumValue(buf: bytes, buf_size: sizet, pos: sizet, value_node: ast.AST*) -> sizet:
"""Emit " = <int>" for an enum member value (mirrors ClassGenEnum._extract_int_value)."""
if value_node is None: return pos
val: int64 = 0
k: int = value_node.kind()
if k == ast.ASTKind.Constant:
c: ast.Constant* = ast.Constant*(value_node)
if c.const_kind == ast.CONST_INT: val = c.int_val
elif k == ast.ASTKind.UnaryOp:
u: ast.UnaryOp* = ast.UnaryOp*(value_node)
if u.op == ast.OpKind.USub and u.operand is not None and u.operand.kind() == ast.ASTKind.Constant:
c2: ast.Constant* = ast.Constant*(u.operand)
if c2.const_kind == ast.CONST_INT: val = -c2.int_val
num_buf: str = stdlib.malloc(32)
if num_buf is None: return pos
viperlib.snprintf(num_buf, 32, " = %lld", val)
pos = _PyiEmit(buf, buf_size, pos, num_buf)
stdlib.free(num_buf)
return pos
# ============================================================
# _PyiIndent - append level*4 spaces to buf at pos
def _PyiIndent(buf: bytes, buf_size: sizet, pos: sizet, level: int) -> sizet:
@@ -169,6 +193,59 @@ def _PyiIndent(buf: bytes, buf_size: sizet, pos: sizet, level: int) -> sizet:
return pos
# _PyiMarkerName - bare name of a marker node (Name / Attribute / Call), else None.
def _PyiMarkerName(node: ast.AST*) -> str:
"""Return the bare marker name of Name / Attribute / Call(node), else None."""
if node is None: return None
inner: ast.AST* = node
if inner.kind() == ast.ASTKind.Call:
cl_m: ast.Call* = ast.Call*(inner)
if cl_m.func is None: return None
inner = cl_m.func
if inner.kind() == ast.ASTKind.Name:
nm_m: ast.Name* = ast.Name*(inner)
return nm_m.id
if inner.kind() == ast.ASTKind.Attribute:
at_m: ast.Attribute* = ast.Attribute*(inner)
return at_m.attr
return None
# _PyiMarkerKind - 0=not the marker / 1=bare marker / 2=marker[T] wrapper.
def _PyiMarkerKind(node: ast.AST*, marker: str) -> int:
"""Whether node is `marker` (bare) or `marker[T]` (wrapper)."""
if node is None or marker is None: return 0
if node.kind() == ast.ASTKind.Subscript:
sub_m: ast.Subscript* = ast.Subscript*(node)
sub_nm: str = _PyiMarkerName(sub_m.value)
if sub_nm is not None and string.strcmp(sub_nm, marker) == 0: return 2
return 0
bare_nm: str = _PyiMarkerName(node)
if bare_nm is not None and string.strcmp(bare_nm, marker) == 0: return 1
return 0
# _PyiMarkerPayload - inner type of `marker[T]`; None for a bare marker or a non-match.
def _PyiMarkerPayload(node: ast.AST*, marker: str) -> ast.AST*:
"""Return marker[T]'s T; None when node is not that wrapper."""
if _PyiMarkerKind(node, marker) != 2: return None
sub_p: ast.Subscript* = ast.Subscript*(node)
return sub_p.slice
# _PyiHasDecorator - whether decorator_list contains the given bare marker name.
def _PyiHasDecorator(decors: list[ast.AST*]*, marker: str) -> int:
"""1 when any decorator is `marker` (bare / Attribute / Call form)."""
if decors is None or marker is None: return 0
dn_d: sizet = len(decors)
for di_d in range(int(dn_d)):
dn_node: ast.AST* = decors.get(di_d)
if dn_node is None: continue
dn_name: str = _PyiMarkerName(dn_node)
if dn_name is not None and string.strcmp(dn_name, marker) == 0: return 1
return 0
# _PyiBinOpOpText - emit the " OP " text for a binary operator.
# Kept byte-identical to TPC's BinOp operator table; unknown ops fail fast.
def _PyiBinOpOpText(buf: bytes, buf_size: sizet, pos: sizet, op: int, node: ast.AST*) -> sizet:
@@ -201,12 +278,18 @@ def _PyiTypeStr(buf: bytes, buf_size: sizet, pos: sizet, annot: ast.AST*, depth:
# Name: simple type name (e.g. int, str, MyClass)
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(annot)
if nm.id is not None: return _PyiEmit(buf, buf_size, pos, nm.id)
if nm.id is not None:
# Retired bare markers map to their source spellings.
if string.strcmp(nm.id, "PtrT") == 0: return _PyiEmit(buf, buf_size, pos, "rawptr")
if string.strcmp(nm.id, "CVoid") == 0: return _PyiEmit(buf, buf_size, pos, "void")
return _PyiEmit(buf, buf_size, pos, nm.id)
return _PyiEmit(buf, buf_size, pos, "int")
# Attribute: attribute reference (e.g. int, memhub.MemBuddy)
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(annot)
if at.attr is not None and string.strcmp(at.attr, "PtrT") == 0: return _PyiEmit(buf, buf_size, pos, "rawptr")
if at.attr is not None and string.strcmp(at.attr, "CVoid") == 0: return _PyiEmit(buf, buf_size, pos, "void")
pos = _PyiTypeStr(buf, buf_size, pos, at.value, depth + 1)
pos = _PyiEmit(buf, buf_size, pos, ".")
if at.attr is not None: pos = _PyiEmit(buf, buf_size, pos, at.attr)
@@ -225,16 +308,59 @@ def _PyiTypeStr(buf: bytes, buf_size: sizet, pos: sizet, annot: ast.AST*, depth:
pos = _PyiEmit(buf, buf_size, pos, ")")
return pos
# Subscript: subscripted type (e.g. list[int], CArray[elem, count])
# Subscript: subscripted type (e.g. list[int], ArrayT[elem, count])
if k == ast.ASTKind.Subscript:
sub: ast.Subscript* = ast.Subscript*(annot)
# Builtin markers carry source syntax in a stub: `T*` / `[N]T` / `const T` /
# `volatile T` / `&T` / `&temp T`. The retired t.* C-names must never be written.
if sub.value is not None and sub.value.kind() == ast.ASTKind.Name:
mk_n: ast.Name* = ast.Name*(sub.value)
if mk_n.id is not None:
if string.strcmp(mk_n.id, "PtrT") == 0:
# One trailing '*' per pointer layer (nested PtrT recurses into `T**`).
pos = _PyiTypeStr(buf, buf_size, pos, sub.slice, depth + 1)
return _PyiEmit(buf, buf_size, pos, "*")
if string.strcmp(mk_n.id, "ConstT") == 0:
pos = _PyiEmit(buf, buf_size, pos, "const ")
return _PyiTypeStr(buf, buf_size, pos, sub.slice, depth + 1)
if string.strcmp(mk_n.id, "VolatileT") == 0:
pos = _PyiEmit(buf, buf_size, pos, "volatile ")
return _PyiTypeStr(buf, buf_size, pos, sub.slice, depth + 1)
if string.strcmp(mk_n.id, "RefT") == 0:
pos = _PyiEmit(buf, buf_size, pos, "&")
return _PyiTypeStr(buf, buf_size, pos, sub.slice, depth + 1)
if string.strcmp(mk_n.id, "TempRefT") == 0:
pos = _PyiEmit(buf, buf_size, pos, "&temp ")
return _PyiTypeStr(buf, buf_size, pos, sub.slice, depth + 1)
if string.strcmp(mk_n.id, "ArrayT") == 0:
# `[N]T` (or `[]T` when the count is None).
if sub.slice is not None and sub.slice.kind() == ast.ASTKind.Tuple:
tup_a: ast.Tuple* = ast.Tuple*(sub.slice)
if tup_a.elts is not None and len(tup_a.elts) >= 2:
pos = _PyiEmit(buf, buf_size, pos, "[")
cnt_a: ast.AST* = tup_a.elts.get(1)
cnt_none_a: int = 0
if cnt_a is not None and cnt_a.kind() == ast.ASTKind.Constant:
cnt_ca: ast.Constant* = ast.Constant*(cnt_a)
if cnt_ca.const_kind == ast.CONST_NONE: cnt_none_a = 1
if cnt_none_a == 0: pos = _PyiTypeStr(buf, buf_size, pos, cnt_a, depth + 1)
pos = _PyiEmit(buf, buf_size, pos, "]")
return _PyiTypeStr(buf, buf_size, pos, tup_a.elts.get(0), depth + 1)
if string.strcmp(mk_n.id, "DefineT") == 0:
# `define NAME: T = value`: only the wrapped T is a type here.
return _PyiTypeStr(buf, buf_size, pos, sub.slice, depth + 1)
# Storage-class wrappers carry no value type; never write them into a stub.
if string.strcmp(mk_n.id, "ExportT") == 0 or string.strcmp(mk_n.id, "ExternT") == 0 \
or string.strcmp(mk_n.id, "StaticT") == 0 or string.strcmp(mk_n.id, "State") == 0 \
or string.strcmp(mk_n.id, "AutoPtrT") == 0:
return _PyiTypeStr(buf, buf_size, pos, sub.slice, depth + 1)
pos = _PyiTypeStr(buf, buf_size, pos, sub.value, depth + 1)
pos = _PyiEmit(buf, buf_size, pos, "[")
pos = _PyiTypeStr(buf, buf_size, pos, sub.slice, depth + 1)
pos = _PyiEmit(buf, buf_size, pos, "]")
return pos
# BinOp: union types and CArray count expressions (MAX_ORDER + 1,
# BinOp: union types and ArrayT count expressions (MAX_ORDER + 1,
# 2 * ZHUFF_MAX_CODES); operator text via _PyiBinOpOpText, byte-identical to TPC.
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(annot)
@@ -301,7 +427,7 @@ def _PyiTypeStr(buf: bytes, buf_size: sizet, pos: sizet, annot: ast.AST*, depth:
pos = _PyiEmit(buf, buf_size, pos, "'")
return pos
if cn.const_kind == ast.CONST_NONE: return _PyiEmit(buf, buf_size, pos, "None")
# Numeric constants (e.g. the count in t.CArray[uint32, 64]): emit the value
# Numeric constants (e.g. the count in t.ArrayT[uint32, 64]): emit the value
# itself; falling back to int makes stub loading raise TypeError.
if cn.const_kind == ast.CONST_INT or cn.const_kind == ast.CONST_CHAR:
num_buf_ct: str = stdlib.malloc(24)
@@ -312,8 +438,8 @@ def _PyiTypeStr(buf: bytes, buf_size: sizet, pos: sizet, annot: ast.AST*, depth:
return pos
return _PyiEmit(buf, buf_size, pos, "int")
# List: function pointer parameter table (e.g. the inner [t.CPtr, ULONG] of
# t.Callable[[t.CPtr, ULONG], BOOL]); must emit "[a, b]" or it fails fast.
# List: function pointer parameter table (e.g. the inner [rawptr, ULONG] of
# t.Callable[[rawptr, ULONG], BOOL]); must emit "[a, b]" or it fails fast.
if k == ast.ASTKind.List:
lst: ast.List* = ast.List*(annot)
pos = _PyiEmit(buf, buf_size, pos, "[")
@@ -390,7 +516,7 @@ def _PyiDefaultStr(buf: bytes, buf_size: sizet, pos: sizet, node: ast.AST*, dept
if cn.const_kind == ast.CONST_NONE: return _PyiEmit(buf, buf_size, pos, "None")
return pos
# Name: CDefine constant name (e.g. share: ULONG = SHARE_READ)
# Name: DefineT constant name (e.g. share: ULONG = SHARE_READ)
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is not None: return _PyiEmit(buf, buf_size, pos, nm.id)
@@ -453,8 +579,58 @@ def _PyiEnsureEscScratch() -> int:
# _PyiFunction - emit a function signature (def name(...) -> Ret: pass)
def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet, fn: ast.FunctionDef*, indent_level: int, class_name: str) -> sizet:
# _PyiIsStaticMethod - 是否 @staticmethod(重载签名需跳过 self/cls 时用)
def _PyiIsStaticMethod(fn: ast.FunctionDef*) -> int:
"""方法带 @staticmethod 时返回 1"""
if fn is None or fn.decorator_list is None: return 0
dn: sizet = len(fn.decorator_list)
for i in range(int(dn)):
d: ast.AST* = fn.decorator_list.get(i)
if d is None: continue
nm: str = _PyiMarkerName(d)
if nm is not None and string.strcmp(nm, "staticmethod") == 0: return 1
return 0
# _PyiOverloadSuffix - vpi 里的重载符号后缀。
# 同一作用域(模块体 / 类体)内同名 def >=2 时返回 "_v{参数签名hash}",否则 None。
# hash 由 GenApi.overload_sig_suffix 计算,与定义端 / 读取端是同一个函数且与 import 上下文无关,
# 所以三端算出的名字必然一致;把后缀直接写进 vpi 的 def 名,读取端按名注入即天然对齐。
def _PyiOverloadSuffix(mb: memhub.MemBuddy*, body: list[ast.AST*]*, fn: ast.FunctionDef*, skip_first: int) -> str:
"""重载组返回 "_v{hash}",否则 None"""
if mb is None or body is None or fn is None or fn.name is None: return None
# extern/state/export/main 走裸名(C ABI / 入口点),定义端会直接报错,这里也不加后缀
if string.strcmp(fn.name, "main") == 0: return None
if _PyiMarkerKind(fn.returns, "ExternT") != 0: return None
if _PyiMarkerKind(fn.returns, "State") != 0: return None
if _PyiMarkerKind(fn.returns, "ExportT") != 0: return None
n: sizet = len(body)
cnt: int = 0
for i in range(int(n)):
ch: ast.AST* = body.get(i)
if ch is None: continue
if ch.kind() != ast.ASTKind.FunctionDef: continue
f2: ast.FunctionDef* = ast.FunctionDef*(ch)
if f2 is None or f2.name is None: continue
if string.strcmp(f2.name, fn.name) == 0:
cnt += 1
if cnt >= 2: break
if cnt < 2: return None
return GenApi.overload_sig_suffix(mb, fn, None, None, skip_first)
def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet, fn: ast.FunctionDef*, indent_level: int, class_name: str, ovl_suffix: str = None) -> sizet:
"""Emit a function signature into buf and return the new pos"""
# Storage-class / export markers become source keywords; the wrapper is not written.
ret_node: ast.AST* = fn.returns
fd_is_define: int = _PyiMarkerKind(ret_node, "DefineT")
fd_is_export: int = _PyiMarkerKind(ret_node, "ExportT")
fd_is_extern: int = _PyiMarkerKind(ret_node, "State")
if fd_is_define != 0: ret_node = _PyiMarkerPayload(ret_node, "DefineT")
elif fd_is_export != 0: ret_node = _PyiMarkerPayload(ret_node, "ExportT")
elif fd_is_extern != 0: ret_node = _PyiMarkerPayload(ret_node, "State")
fd_is_static: int = _PyiHasDecorator(fn.decorator_list, "StaticT")
fd_is_virtual: int = _PyiHasDecorator(fn.decorator_list, "VTableT")
# Decorators
if fn.decorator_list is not None:
fn_decorator_list: list[ast.AST*]* = fn.decorator_list
@@ -466,14 +642,26 @@ def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet, fn: ast.FunctionDef*,
for ddi in range(int(dn)):
dec: ast.AST* = fn_decorator_list.get(ddi)
if dec is not None:
dmn: str = _PyiMarkerName(dec)
if dmn is not None and (string.strcmp(dmn, "StaticT") == 0 or string.strcmp(dmn, "VTableT") == 0 \
or string.strcmp(dmn, "NoVTable") == 0):
continue
pos = _PyiIndent(buf, buf_size, pos, indent_level)
pos = _PyiEmit(buf, buf_size, pos, "@")
pos = _PyiTypeStr(buf, buf_size, pos, dec)
pos = _PyiEmit(buf, buf_size, pos, "\n")
pos = _PyiIndent(buf, buf_size, pos, indent_level)
pos = _PyiEmit(buf, buf_size, pos, "def ")
if fd_is_define != 0: pos = _PyiEmit(buf, buf_size, pos, "define ")
elif fd_is_export != 0: pos = _PyiEmit(buf, buf_size, pos, "export def ")
elif fd_is_extern != 0: pos = _PyiEmit(buf, buf_size, pos, "extern def ")
else:
if fd_is_static != 0: pos = _PyiEmit(buf, buf_size, pos, "static ")
elif fd_is_virtual != 0: pos = _PyiEmit(buf, buf_size, pos, "virtual ")
pos = _PyiEmit(buf, buf_size, pos, "def ")
if fn.name is not None: pos = _PyiEmit(buf, buf_size, pos, fn.name)
# 重载组:名字后带 "_v{签名hash}",跨模块注入端按此名生成符号,天然与定义端一致
if ovl_suffix is not None: pos = _PyiEmit(buf, buf_size, pos, ovl_suffix)
pos = _PyiEmit(buf, buf_size, pos, "(")
# Parameter list
@@ -527,9 +715,9 @@ def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet, fn: ast.FunctionDef*,
pos = _PyiEmit(buf, buf_size, pos, ")")
# Return type
if fn.returns is not None:
if ret_node is not None:
pos = _PyiEmit(buf, buf_size, pos, " -> ")
pos = _PyiTypeStr(buf, buf_size, pos, fn.returns)
pos = _PyiTypeStr(buf, buf_size, pos, ret_node)
else:
# Without a `->` annotation, @c.CReturn is removed: fail fast to regenerate vpi
_PyiRejectCReturnDecorator(fn, fn.decorator_list)
@@ -605,6 +793,34 @@ def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet, fn: ast.FunctionDef*,
# _PyiClass - emit a class definition (fields and method stubs).
def _PyiClass(buf: bytes, buf_size: sizet, pos: sizet, cls: ast.ClassDef*, indent_level: int) -> sizet:
"""Emit a class definition into buf and return the new pos"""
# enum / struct / union / renum sugar: never write EnumBase / StructBase / ... bases.
cls_header: str = "class"
cls_base_skip: int = -1
if cls.bases is not None:
cls_nb: sizet = len(cls.bases)
for cbi in range(int(cls_nb)):
cb_nm: str = _PyiMarkerName(cls.bases.get(cbi))
if cb_nm is None: continue
# `struct X(Base):` appends StructBase after the real parents, so it may coexist.
if string.strcmp(cb_nm, "StructBase") == 0:
cls_header = "struct"
cls_base_skip = cbi
elif cls_nb == 1 and string.strcmp(cb_nm, "EnumBase") == 0:
cls_header = "enum"
cls_base_skip = cbi
elif cls_nb == 1 and string.strcmp(cb_nm, "UnionBase") == 0:
cls_header = "union"
cls_base_skip = cbi
elif cls_nb == 1 and string.strcmp(cb_nm, "REnum") == 0:
cls_header = "renum"
cls_base_skip = cbi
cls_is_virtual: int = _PyiHasDecorator(cls.decorator_list, "VTableT")
cls_is_novirtual: int = _PyiHasDecorator(cls.decorator_list, "NoVTable")
# Decorator form (@EnumBase class X:) is the same sugar as the base-class form.
if string.strcmp(cls_header, "class") == 0:
if _PyiHasDecorator(cls.decorator_list, "EnumBase") != 0: cls_header = "enum"
elif _PyiHasDecorator(cls.decorator_list, "StructBase") != 0: cls_header = "struct"
elif _PyiHasDecorator(cls.decorator_list, "UnionBase") != 0: cls_header = "union"
# Decorators
if cls.decorator_list is not None:
cls_decorator_list: list[ast.AST*]* = cls.decorator_list
@@ -612,13 +828,23 @@ def _PyiClass(buf: bytes, buf_size: sizet, pos: sizet, cls: ast.ClassDef*, inden
for dci in range(int(dcn)):
ddec: ast.AST* = cls_decorator_list.get(dci)
if ddec is not None:
ddn: str = _PyiMarkerName(ddec)
if ddn is not None and (string.strcmp(ddn, "VTableT") == 0 or string.strcmp(ddn, "NoVTable") == 0 \
or string.strcmp(ddn, "EnumBase") == 0 or string.strcmp(ddn, "StructBase") == 0 \
or string.strcmp(ddn, "UnionBase") == 0):
continue
pos = _PyiIndent(buf, buf_size, pos, indent_level)
pos = _PyiEmit(buf, buf_size, pos, "@")
pos = _PyiTypeStr(buf, buf_size, pos, ddec)
pos = _PyiEmit(buf, buf_size, pos, "\n")
pos = _PyiIndent(buf, buf_size, pos, indent_level)
pos = _PyiEmit(buf, buf_size, pos, "class ")
if string.strcmp(cls_header, "class") == 0:
if cls_is_virtual != 0: pos = _PyiEmit(buf, buf_size, pos, "virtual ")
elif cls_is_novirtual != 0: pos = _PyiEmit(buf, buf_size, pos, "novirtual ")
pos = _PyiEmit(buf, buf_size, pos, "class ")
else: pos = _PyiEmit(buf, buf_size, pos, cls_header)
if string.strcmp(cls_header, "class") != 0: pos = _PyiEmit(buf, buf_size, pos, " ")
if cls.name is not None: pos = _PyiEmit(buf, buf_size, pos, cls.name)
# PEP 695 type parameters (class Foo[T]:)
@@ -635,17 +861,21 @@ def _PyiClass(buf: bytes, buf_size: sizet, pos: sizet, cls: ast.ClassDef*, inden
if tpname is not None: pos = _PyiEmit(buf, buf_size, pos, tpname)
pos = _PyiEmit(buf, buf_size, pos, "]")
# Base classes
if cls.bases is not None:
# Base classes: plain class keeps all; struct keeps its real parents and drops StructBase.
if (string.strcmp(cls_header, "class") == 0 or string.strcmp(cls_header, "struct") == 0) and cls.bases is not None:
cls_bases: list[ast.AST*]* = cls.bases
bn: sizet = len(cls_bases)
if bn > 0:
pos = _PyiEmit(buf, buf_size, pos, "(")
for bi in range(int(bn)):
if bi > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
base: ast.AST* = cls_bases.get(bi)
pos = _PyiTypeStr(buf, buf_size, pos, base)
pos = _PyiEmit(buf, buf_size, pos, ")")
base_first: int = 1
for bi in range(int(bn)):
if bi == cls_base_skip: continue
base: ast.AST* = cls_bases.get(bi)
if base is None: continue
if base_first != 0:
pos = _PyiEmit(buf, buf_size, pos, "(")
base_first = 0
else: pos = _PyiEmit(buf, buf_size, pos, ", ")
pos = _PyiTypeStr(buf, buf_size, pos, base)
if base_first == 0: pos = _PyiEmit(buf, buf_size, pos, ")")
pos = _PyiEmit(buf, buf_size, pos, ":\n")
@@ -689,7 +919,9 @@ def _PyiClass(buf: bytes, buf_size: sizet, pos: sizet, cls: ast.ClassDef*, inden
pos = _PyiEmit(buf, buf_size, pos, "\n")
has_member = 1
ici += 1
pos = _PyiFunction(buf, buf_size, pos, mfn, indent_level + 1, cls.name)
skip_s: int = 1
if _PyiIsStaticMethod(mfn) != 0: skip_s = 0
pos = _PyiFunction(buf, buf_size, pos, mfn, indent_level + 1, cls.name, _PyiOverloadSuffix(mb, cls_children, mfn, skip_s))
has_member = 1
# Annotated attributes
@@ -698,12 +930,43 @@ def _PyiClass(buf: bytes, buf_size: sizet, pos: sizet, cls: ast.ClassDef*, inden
if aa.target is not None and aa.target.kind() == ast.ASTKind.Name:
tn: ast.Name* = ast.Name*(aa.target)
pos = _PyiIndent(buf, buf_size, pos, indent_level + 1)
if _PyiMarkerKind(aa.annotation, "StaticT") != 0: pos = _PyiEmit(buf, buf_size, pos, "static ")
if tn.id is not None: pos = _PyiEmit(buf, buf_size, pos, tn.id)
pos = _PyiEmit(buf, buf_size, pos, ": ")
pos = _PyiTypeStr(buf, buf_size, pos, aa.annotation)
if _PyiMarkerKind(aa.annotation, "StaticT") == 2:
pos = _PyiEmit(buf, buf_size, pos, ": ")
pos = _PyiTypeStr(buf, buf_size, pos, _PyiMarkerPayload(aa.annotation, "StaticT"))
else:
pos = _PyiEmit(buf, buf_size, pos, ": ")
pos = _PyiTypeStr(buf, buf_size, pos, aa.annotation)
# 枚举成员(`NAME: T = value`)必须带值:跨模块 EnumTable 只认 .vpi,
# 值丢了依赖方的成员常量就跟定义端不一致。普通字段的初值不属于 stub 契约,不写。
if string.strcmp(cls_header, "enum") == 0 and aa.value is not None:
pos = _PyiEmitEnumValue(buf, buf_size, pos, aa.value)
pos = _PyiEmit(buf, buf_size, pos, "\n")
has_member = 1
# 枚举成员 `NAME = value`(含 `A = B = 1` 多目标):源码 AST 里是 Assign
# (parser 只把裸名/逗号组改写成 `NAME: State`,带值形式保持 Assign)。
# 旧代码只处理 AnnAssign,于是整块带值成员在 .vpi 里丢失 —— `enum FRESULT: pass`
# 让依赖方拿不到基类型(只能把枚举名当类名做前向声明)和成员值。
# 只对 `enum X:` 生效:普通类体的赋值不属于 stub 契约。
elif sk == ast.ASTKind.Assign and string.strcmp(cls_header, "enum") == 0:
asg: ast.Assign* = ast.Assign*(stmt)
if asg.targets is not None and asg.value is not None:
asg_tn: sizet = len(asg.targets)
for asg_i in range(int(asg_tn)):
asg_t: ast.AST* = asg.targets.get(asg_i)
if asg_t is None or asg_t.kind() != ast.ASTKind.Name: continue
asg_n: ast.Name* = ast.Name*(asg_t)
if asg_n.id is None: continue
pos = _PyiIndent(buf, buf_size, pos, indent_level + 1)
pos = _PyiEmit(buf, buf_size, pos, asg_n.id)
# Assign 形式的成员在定义端固定按 i32 处理(ClassGenEnum 的 member_ty_a)
pos = _PyiEmit(buf, buf_size, pos, ": State")
pos = _PyiEmitEnumValue(buf, buf_size, pos, asg.value)
pos = _PyiEmit(buf, buf_size, pos, "\n")
has_member = 1
# Nested classes
elif sk == ast.ASTKind.ClassDef:
ncls: ast.ClassDef* = ast.ClassDef*(stmt)
@@ -747,7 +1010,7 @@ def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*, rel_path: str, buf
if tree.children is not None:
pre_children: list[ast.AST*]* = tree.children
pcn: sizet = len(pre_children)
# Pass 1: count t.CDefine declarations (C arrays need a fixed size first)
# Pass 1: count t.DefineT declarations (C arrays need a fixed size first)
_g_pyi_cdef_count = 0
for pci0 in range(int(pcn)):
stmt_pc0: ast.AST* = pre_children.get(pci0)
@@ -755,9 +1018,7 @@ def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*, rel_path: str, buf
aa_pc0: ast.AnnAssign* = ast.AnnAssign*(stmt_pc0)
if aa_pc0.target is not None and aa_pc0.target.kind() == ast.ASTKind.Name and aa_pc0.annotation is not None:
tgt_pc0: ast.Name* = ast.Name*(aa_pc0.target)
if tgt_pc0.id is not None and aa_pc0.annotation.kind() == ast.ASTKind.Attribute:
ann_pc0: ast.Attribute* = ast.Attribute*(aa_pc0.annotation)
if ann_pc0.attr is not None and string.strcmp(ann_pc0.attr, "CDefine") == 0: _g_pyi_cdef_count += 1
if tgt_pc0.id is not None and _PyiMarkerKind(aa_pc0.annotation, "DefineT") != 0: _g_pyi_cdef_count += 1
if _g_pyi_cdef_count > 0:
_g_pyi_cdef_names = (str*)(mb.alloc(sizet(_g_pyi_cdef_count) * 8))
_g_pyi_cdef_vals = (int64*)(mb.alloc(sizet(_g_pyi_cdef_count) * 8))
@@ -770,14 +1031,12 @@ def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*, rel_path: str, buf
aa_pc: ast.AnnAssign* = ast.AnnAssign*(stmt_pc)
if aa_pc.target is not None and aa_pc.target.kind() == ast.ASTKind.Name and aa_pc.annotation is not None:
tgt_pc: ast.Name* = ast.Name*(aa_pc.target)
if tgt_pc.id is not None and aa_pc.annotation.kind() == ast.ASTKind.Attribute:
ann_pc: ast.Attribute* = ast.Attribute*(aa_pc.annotation)
if ann_pc.attr is not None and string.strcmp(ann_pc.attr, "CDefine") == 0:
if _g_pyi_cdef_names is not None and _g_pyi_cdef_vals is not None:
slot_pc: str* = (str*)(uint64(_g_pyi_cdef_names) + cdi * 8)
slot_pc[0] = tgt_pc.id
_g_pyi_cdef_vals[cdi] = _eval_cdefine_value_cache(aa_pc.value)
cdi += 1
if tgt_pc.id is not None and _PyiMarkerKind(aa_pc.annotation, "DefineT") != 0:
if _g_pyi_cdef_names is not None and _g_pyi_cdef_vals is not None:
slot_pc: str* = (str*)(uint64(_g_pyi_cdef_names) + cdi * 8)
slot_pc[0] = tgt_pc.id
_g_pyi_cdef_vals[cdi] = _eval_cdefine_value_cache(aa_pc.value)
cdi += 1
# File header
pos = _PyiEmit(buf, buf_size, pos, "\"\"\"Auto-generated Python stub file\"\"\"\n\n")
@@ -861,7 +1120,7 @@ def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*, rel_path: str, buf
# Generic function templates (def f[T]) emit no vpi: the signature has an
# unbound T. Specializations are monomorphized per call site, not here.
if fn.type_params is not None: continue
pos = _PyiFunction(buf, buf_size, pos, fn, 0, "")
pos = _PyiFunction(buf, buf_size, pos, fn, 0, "", _PyiOverloadSuffix(mb, tree_children, fn, 0))
# Class definition
elif k == ast.ASTKind.ClassDef:
@@ -874,19 +1133,56 @@ def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*, rel_path: str, buf
if aa.target is not None and aa.target.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(aa.target)
if nm.id is not None:
pos = _PyiEmit(buf, buf_size, pos, nm.id)
pos = _PyiEmit(buf, buf_size, pos, ": ")
pos = _PyiTypeStr(buf, buf_size, pos, aa.annotation)
# If there is a value (e.g. a CDefine constant) emit = value.
# CTypedef is special: its value is a type expression, rendered by _PyiTypeStr.
# Retired t.TypedefT / t.DefineT markers are written as their source
# syntax (`typedef NAME = T` / `define NAME[: T] = value`).
is_ctypedef_ann: int = 0
if aa.annotation is not None and aa.annotation.kind() == ast.ASTKind.Name:
ctd_nm: ast.Name* = ast.Name*(aa.annotation)
if ctd_nm.id is not None and string.strcmp(ctd_nm.id, "CTypedef") == 0: is_ctypedef_ann = 1
if is_ctypedef_ann != 0 and aa.value is not None:
pos = _PyiEmit(buf, buf_size, pos, " = ")
pos = _PyiTypeStr(buf, buf_size, pos, aa.value)
elif aa.value is not None and aa.value.kind() == ast.ASTKind.Constant:
ctd_nm0: ast.Name* = ast.Name*(aa.annotation)
if ctd_nm0.id is not None and string.strcmp(ctd_nm0.id, "TypedefT") == 0: is_ctypedef_ann = 1
cd_kind: int = 0
cd_inner: ast.AST* = None
if aa.annotation is not None:
if aa.annotation.kind() == ast.ASTKind.Name:
cd_nm0: ast.Name* = ast.Name*(aa.annotation)
if cd_nm0.id is not None and string.strcmp(cd_nm0.id, "DefineT") == 0: cd_kind = 1
elif aa.annotation.kind() == ast.ASTKind.Subscript:
cd_sub0: ast.Subscript* = ast.Subscript*(aa.annotation)
if cd_sub0.value is not None and cd_sub0.value.kind() == ast.ASTKind.Name:
cd_vn0: ast.Name* = ast.Name*(cd_sub0.value)
if cd_vn0.id is not None and string.strcmp(cd_vn0.id, "DefineT") == 0:
cd_kind = 2
cd_inner = cd_sub0.slice
if is_ctypedef_ann != 0:
pos = _PyiEmit(buf, buf_size, pos, "typedef ")
pos = _PyiEmit(buf, buf_size, pos, nm.id)
if aa.value is not None:
pos = _PyiEmit(buf, buf_size, pos, " = ")
pos = _PyiTypeStr(buf, buf_size, pos, aa.value)
elif cd_kind != 0:
pos = _PyiEmit(buf, buf_size, pos, "define ")
pos = _PyiEmit(buf, buf_size, pos, nm.id)
if cd_kind == 2 and cd_inner is not None:
pos = _PyiEmit(buf, buf_size, pos, ": ")
pos = _PyiTypeStr(buf, buf_size, pos, cd_inner)
else:
# Storage-class markers become source keywords.
st_kw: str = None
st_payload: ast.AST* = aa.annotation
if _PyiMarkerKind(aa.annotation, "ExportT") != 0:
st_kw = "export "
st_payload = _PyiMarkerPayload(aa.annotation, "ExportT")
elif _PyiMarkerKind(aa.annotation, "State") != 0:
st_kw = "extern "
st_payload = _PyiMarkerPayload(aa.annotation, "State")
elif _PyiMarkerKind(aa.annotation, "StaticT") != 0:
st_kw = "static "
st_payload = _PyiMarkerPayload(aa.annotation, "StaticT")
if st_kw is not None: pos = _PyiEmit(buf, buf_size, pos, st_kw)
pos = _PyiEmit(buf, buf_size, pos, nm.id)
if st_payload is not None:
pos = _PyiEmit(buf, buf_size, pos, ": ")
pos = _PyiTypeStr(buf, buf_size, pos, st_payload)
if is_ctypedef_ann == 0 and aa.value is not None and aa.value.kind() == ast.ASTKind.Constant:
cv: ast.Constant* = ast.Constant*(aa.value)
num_buf: bytes = mb.alloc(32)
if cv.const_kind == ast.CONST_INT and num_buf is not None:
@@ -977,7 +1273,11 @@ def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*, rel_path: str, buf
if num_buf_b is not None:
viperlib.snprintf(num_buf_b, 32, " = %lld", binop_val_av)
pos = _PyiEmit(buf, buf_size, pos, num_buf_b)
elif aa.value is not None and aa.value.kind() == ast.ASTKind.Name:
# Value suffixes (constants / references) are for `define` and plain
# annotated globals only. A typedef's RHS is already a type: without the
# is_ctypedef_ann guard a Name RHS got a second suffix
# (`typedef pid_t = int32 = int32`, which no longer parses as an alias).
elif is_ctypedef_ann == 0 and aa.value is not None and aa.value.kind() == ast.ASTKind.Name:
# Name reference constant (e.g. ALIAS = OTHER_CONST):
# emit the referenced name for the consumer to resolve locally.
name_val: ast.Name* = ast.Name*(aa.value)
+4 -1
View File
@@ -105,7 +105,10 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
def _overload_sig_text(self, pool: memhub.MemBuddy*, fd: ast.FunctionDef*, trans: HT.Translator*, skip_first: int = 0) -> str:
"""返回规范形参签名文本(stdlib 缓冲,调用方 free);失败时已报错并返回 None"""
if pool is None or fd is None or trans is None: return None
return self._overload_sig_text_ctx(pool, fd, trans._imported_modules, trans._from_imports, trans, skip_first)
# hash 与上下文无关:固定传 None(import 表 / translator),只靠全局结构体表解析。
# 这样定义端、vpi 写入端(translator 尚未创建)、vpi 读取端天然算出同一个 hash,
# 不需要把 import 上下文跨阶段搬运;解析不到的类型会 fail-fast,不会静默算出别的 hash。
return self._overload_sig_text_ctx(pool, fd, None, None, None, skip_first)
# _overload_sig_text_ctx - 同上,但显式传入 import 上下文,供没有 translator 的场景复用:
# vpi 生成(写 vpi 时 translator 还没创建)与 vpi 读取(注入 declare 时持有可能为 None 的 trans)。