From f54c365b20159fcbf6c06404759b29f5246aa71a Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 24 Oct 2026 17:29:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(overload):=20vpi=20=E5=86=99=E5=85=A5?= =?UTF-8?q?=E7=AB=AF=E6=8A=8A=E7=AD=BE=E5=90=8D=E5=90=8E=E7=BC=80=E5=86=99?= =?UTF-8?q?=E8=BF=9B=20def=20=E5=90=8D=EF=BC=8C=E7=AD=BE=E5=90=8D=20hash?= =?UTF-8?q?=20=E4=B8=8E=E4=B8=8A=E4=B8=8B=E6=96=87=E6=97=A0=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _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 拼符号,自动与定义端一致,返回/形参注解缓存也按后缀分组不再互相覆盖。 --- App/lib/Stub/StubGen/Converter.vp | 416 ++++++++++++++++++++++++---- App/lib/core/Gen/Func/FuncGenDef.vp | 5 +- 2 files changed, 362 insertions(+), 59 deletions(-) diff --git a/App/lib/Stub/StubGen/Converter.vp b/App/lib/Stub/StubGen/Converter.vp index 8c60975..1a5a8a4 100644 --- a/App/lib/Stub/StubGen/Converter.vp +++ b/App/lib/Stub/StubGen/Converter.vp @@ -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 " = " 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) diff --git a/App/lib/core/Gen/Func/FuncGenDef.vp b/App/lib/core/Gen/Func/FuncGenDef.vp index 8c796d8..71d77a5 100644 --- a/App/lib/core/Gen/Func/FuncGenDef.vp +++ b/App/lib/core/Gen/Func/FuncGenDef.vp @@ -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)。