Files
ViperC/App/lib/Stub/StubGen/Converter.vp
T
TermiNexus 6da8e2e095 feat(gen): 落地 @life(ret(...)) 返回值生命期契约(A2)
- GenApi:新增 compute_life_anno / _RetKindFromTag 与 RET_KIND_* 编码,
  提供 get_ret_kind / get_ret_kind_method / lookup_ret_kind_via_vpi;
  _WillFreeMergeTag 不再拦截 ret(改由 compute_life_anno 收集校验)。
- StructGenMixin:EscDirTable 由 3 槽扩到 4 槽(新增 ret_kind 列),
  compute_escape_summary 把契约落在 box[5] 并登记。
- Stub:.vpi 行尾新增 escr=<ret_kind>(Converter 写、VpiReader 读 _g_vpi_esd_r)。
- AssignGenMixin.life_of_expr(Call):@life(ret(...)) 声明优先于任何推断;
  frame/heap/static/borrows=none 直接映射,borrows=x 取对应实参生命期,
  方法调用按接收者静态类型解析类名。
- StmtGenCifMixin:同批移除定义端 return <本帧地址> 的 fail-fast,
  返回值去向改由调用点按被调者契约判定。
2026-10-09 22:58:33 +08:00

1014 lines
56 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import lib.core.Gen.GenApi as GenApi
import lib.core.VLogger as VLogger
import t, c
from stdint import *
import ast
import string
import memhub
import viperlib
import stdlib
import sys
# ============================================================
# StubGen.Converter - .vpi stub file generator
# _eval_binop_int / _eval_binop_valid - recursively evaluate BinOp constant values.
# Handles BitOr/BitAnd/Add/Sub and nested Name refs to registered CDefine 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
k: int = node.kind()
if k == ast.ASTKind.Constant:
cn: ast.Constant* = ast.Constant*(node)
if cn.const_kind == ast.CONST_INT: return 1
return 0
if k == ast.ASTKind.UnaryOp:
uop: ast.UnaryOp* = ast.UnaryOp*(node)
if uop.operand is not None: return _eval_binop_valid(uop.operand)
return 0
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if bop.left is not None and bop.right is not None:
if _eval_binop_valid(bop.left) != 0:
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
nm: ast.Name* = ast.Name*(node)
if nm.id is not None:
# Look up in the local CDefine table
val: int = _lookup_cdefine_for_pyi(nm.id)
if _pyi_cdefine_found() != 0: return 1
return 0
return 0
def _eval_binop_int(node: ast.AST*) -> int64:
"""Recursively evaluate a BinOp node as a 64-bit integer value"""
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.Constant:
cn: ast.Constant* = ast.Constant*(node)
if cn.const_kind == ast.CONST_INT: return cn.int_val
return 0
if k == ast.ASTKind.UnaryOp:
uop: ast.UnaryOp* = ast.UnaryOp*(node)
if uop.op == ast.OpKind.USub and uop.operand is not None: return -_eval_binop_int(uop.operand)
if uop.op == ast.OpKind.UAdd and uop.operand is not None: return _eval_binop_int(uop.operand)
if uop.op == ast.OpKind.Invert and uop.operand is not None: return ~_eval_binop_int(uop.operand)
return 0
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if bop.left is None or bop.right is None: return 0
lv: int64 = _eval_binop_int(bop.left)
rv: int64 = _eval_binop_int(bop.right)
if bop.op == ast.OpKind.BitOr: return lv | rv
if bop.op == ast.OpKind.BitAnd: return lv & rv
if bop.op == ast.OpKind.BitXor: return lv ^ rv
if bop.op == ast.OpKind.Add: return lv + rv
if bop.op == ast.OpKind.Sub: return lv - rv
if bop.op == ast.OpKind.Mult: return lv * rv
if bop.op == ast.OpKind.Div: return lv // rv
if bop.op == ast.OpKind.FloorDiv: return lv // rv
if bop.op == ast.OpKind.Mod: return lv % rv
if bop.op == ast.OpKind.LShift: return lv << rv
if bop.op == ast.OpKind.RShift: return lv >> rv
return 0
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is not None: return _lookup_cdefine_for_pyi(nm.id)
return 0
return 0
# Forward declaration for CDefine 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
# _GeneratePyiFromAst registers in order; C arrays dodge list.get misdispatch.
_g_pyi_cdef_names: str* = None
_g_pyi_cdef_vals: int64* = None
_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"""
global _g_pyi_cdefine_found
# Look up this file's CDefine 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).
pn: sizet = sizet(_g_pyi_cdef_count)
for pi in range(pn):
entry_pc: str* = (str*)(uint64(_g_pyi_cdef_names) + pi * 8)
pnm: str = entry_pc[0]
if pnm is not None and string.strcmp(pnm, name) == 0:
_g_pyi_cdefine_found = 1
return _g_pyi_cdef_vals[pi]
val: int64 = GenApi.lookup_cdefine_constant(name)
if GenApi.is_cdefine_found() != 0:
_g_pyi_cdefine_found = 1
return val
_g_pyi_cdefine_found = 0
return 0
def _eval_cdefine_value_cache(node: ast.AST*) -> int64:
"""Evaluate a CDefine value for preprocessing registration (Constant/UnaryOp/BinOp/Call/Name)"""
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.Constant:
cn_c: ast.Constant* = ast.Constant*(node)
if cn_c.const_kind == ast.CONST_INT: return cn_c.int_val
if cn_c.const_kind == ast.CONST_BOOL: return cn_c.int_val
# STR/FLOAT/NONE do not participate in integer BinOp refs (registering 0 is harmless)
return 0
if k == ast.ASTKind.UnaryOp: return _eval_binop_int(node)
if k == ast.ASTKind.BinOp: return _eval_binop_int(node)
if k == ast.ASTKind.Call:
cl_c: ast.Call* = ast.Call*(node)
if cl_c.args is not None and len(cl_c.args) > 0: return _eval_cdefine_value_cache(cl_c.args.get(0))
return 0
if k == ast.ASTKind.Name:
nm_c: ast.Name* = ast.Name*(node)
if nm_c.id is not None: return _lookup_cdefine_for_pyi(nm_c.id)
return 0
return 0
def _pyi_cdefine_found() -> int:
"""Return the result status of the last _lookup_cdefine_for_pyi call"""
return _g_pyi_cdefine_found
# ============================================================
# _PyiEmit - append text to buf at pos, returning the new pos (truncates on overflow)
def _PyiEmit(buf: bytes, buf_size: sizet, pos: sizet, text: str) -> sizet:
"""Append text to buf at pos and return the new pos"""
if buf is None or text is None or pos >= buf_size: return pos
tlen: sizet = string.strlen(text)
if tlen == 0: return pos
remain: sizet = buf_size - pos
if tlen >= remain: tlen = remain - 1
if tlen > 0: string.memcpy(buf + pos, text, tlen)
return pos + tlen
# ============================================================
# _PyiIndent - append level*4 spaces to buf at pos
def _PyiIndent(buf: bytes, buf_size: sizet, pos: sizet, level: int) -> sizet:
"""Append level*4 spaces to buf at pos and return the new pos"""
for i in range(level):
pos = _PyiEmit(buf, buf_size, pos, " ")
return pos
# _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:
"""Emit the " OP " text for a binary operator; unknown operators fail fast"""
if op == ast.OpKind.BitOr: return _PyiEmit(buf, buf_size, pos, " | ")
if op == ast.OpKind.Add: return _PyiEmit(buf, buf_size, pos, " + ")
if op == ast.OpKind.Sub: return _PyiEmit(buf, buf_size, pos, " - ")
if op == ast.OpKind.Mult: return _PyiEmit(buf, buf_size, pos, " * ")
if op == ast.OpKind.Div: return _PyiEmit(buf, buf_size, pos, " / ")
if op == ast.OpKind.Mod: return _PyiEmit(buf, buf_size, pos, " % ")
if op == ast.OpKind.BitAnd: return _PyiEmit(buf, buf_size, pos, " & ")
if op == ast.OpKind.RShift: return _PyiEmit(buf, buf_size, pos, " >> ")
GenApi.fatal_error(node, "unknown vpi type-annotation node type", "TypeError")
return pos # unreachable (fatal_error already exited)
# _PyiTypeStr - recursively render an AST annotation node into buf at pos.
# Support set matches TPC; TPV additionally accepts a bare Tuple.
def _PyiTypeStr(buf: bytes, buf_size: sizet, pos: sizet, annot: ast.AST*, depth: int = 0) -> sizet:
"""Recursively render a type annotation node into buf and return the new pos"""
# Recursion guard: a self-referential AST would recurse until stack exhaustion.
# Fail fast instead of silently falling back.
if depth > 64:
VLogger.error("vpi type annotation recursion too deep (AST cycle or nesting over 64 levels)", "StubGen")
sys.exit(1)
if annot is None: return _PyiEmit(buf, buf_size, pos, "int")
k: int = annot.kind()
# 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)
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)
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)
return pos
# Subscript: subscripted type (e.g. list[int], t.CArray[elem, count])
if k == ast.ASTKind.Subscript:
sub: ast.Subscript* = ast.Subscript*(annot)
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,
# 2 * ZHUFF_MAX_CODES); operator text via _PyiBinOpOpText, byte-identical to TPC.
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(annot)
pos = _PyiTypeStr(buf, buf_size, pos, bop.left, depth + 1)
pos = _PyiBinOpOpText(buf, buf_size, pos, bop.op, annot)
pos = _PyiTypeStr(buf, buf_size, pos, bop.right, depth + 1)
return pos
# UnaryOp: count expressions (-1 / ~0) and macro args (not ct.isptr(x)).
# Not/Invert/USub match TPC; other unary ops fail fast.
if k == ast.ASTKind.UnaryOp:
uop_t: ast.UnaryOp* = ast.UnaryOp*(annot)
if uop_t.op == ast.OpKind.Not: pos = _PyiEmit(buf, buf_size, pos, "not ")
elif uop_t.op == ast.OpKind.Invert: pos = _PyiEmit(buf, buf_size, pos, "~")
elif uop_t.op == ast.OpKind.USub: pos = _PyiEmit(buf, buf_size, pos, "-")
else:
GenApi.fatal_error(annot, "unknown vpi type-annotation node type", "TypeError")
return pos # unreachable (fatal_error already exited)
return _PyiTypeStr(buf, buf_size, pos, uop_t.operand, depth + 1)
# Compare: comparison expressions (MAX_SIZE > 512 / val == 1).
# == != < <= > >= match TPC; unknown ops emit " ? ".
if k == ast.ASTKind.Compare:
cmp_t: ast.Compare* = ast.Compare*(annot)
pos = _PyiTypeStr(buf, buf_size, pos, cmp_t.left, depth + 1)
ops_ct: list[int]* = (list[int]*)(cmp_t.ops)
cmps_ct: list[ast.AST*]* = cmp_t.comparators
if ops_ct is not None:
on_ct: sizet = len(ops_ct)
for oi_ct in range(on_ct):
oid_ct: int = ops_ct.get(oi_ct)
if oid_ct == ast.OpKind.Eq: pos = _PyiEmit(buf, buf_size, pos, " == ")
elif oid_ct == ast.OpKind.Ne: pos = _PyiEmit(buf, buf_size, pos, " != ")
elif oid_ct == ast.OpKind.Lt: pos = _PyiEmit(buf, buf_size, pos, " < ")
elif oid_ct == ast.OpKind.Le: pos = _PyiEmit(buf, buf_size, pos, " <= ")
elif oid_ct == ast.OpKind.Gt: pos = _PyiEmit(buf, buf_size, pos, " > ")
elif oid_ct == ast.OpKind.Ge: pos = _PyiEmit(buf, buf_size, pos, " >= ")
else: pos = _PyiEmit(buf, buf_size, pos, " ? ")
# TPC indexes comparators[i] by ops; guard the length here so a
# malformed AST (unequal ops/comparators) cannot overrun.
if cmps_ct is not None and oi_ct < len(cmps_ct): pos = _PyiTypeStr(buf, buf_size, pos, cmps_ct.get(oi_ct), depth + 1)
return pos
# BoolOp: boolean ops (ENABLED and MAX_SIZE > 0 / A or B).
# Matches TPC: and -> " and ", or -> " or ".
if k == ast.ASTKind.BoolOp:
bool_t: ast.BoolOp* = ast.BoolOp*(annot)
vals_bt: list[ast.AST*]* = bool_t.values
if vals_bt is not None:
vn_bt: sizet = len(vals_bt)
for vi_bt in range(vn_bt):
if vi_bt > 0:
if bool_t.op == ast.OpKind.Or: pos = _PyiEmit(buf, buf_size, pos, " or ")
else: pos = _PyiEmit(buf, buf_size, pos, " and ")
pos = _PyiTypeStr(buf, buf_size, pos, vals_bt.get(vi_bt), depth + 1)
return pos
# Constant: string forward refs (e.g. 'MyClass'), None or numeric constants
if k == ast.ASTKind.Constant:
cn: ast.Constant* = ast.Constant*(annot)
if cn.const_kind == ast.CONST_STR:
pos = _PyiEmit(buf, buf_size, pos, "'")
if cn.str_val is not None: pos = _PyiEmit(buf, buf_size, pos, cn.str_val)
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
# 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)
if num_buf_ct is not None:
viperlib.snprintf(num_buf_ct, 24, "%d", cn.int_val)
pos = _PyiEmit(buf, buf_size, pos, num_buf_ct)
stdlib.free(num_buf_ct)
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.
if k == ast.ASTKind.List:
lst: ast.List* = ast.List*(annot)
pos = _PyiEmit(buf, buf_size, pos, "[")
if lst.elts is not None:
lst_elts: list[ast.AST*]* = lst.elts
ln: sizet = len(lst_elts)
for li in range(ln):
if li > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
le: ast.AST* = lst_elts.get(li)
pos = _PyiTypeStr(buf, buf_size, pos, le, depth + 1)
pos = _PyiEmit(buf, buf_size, pos, "]")
return pos
# Tuple: multi-argument subscript (e.g. Dict[str, int])
if k == ast.ASTKind.Tuple:
tup: ast.Tuple* = ast.Tuple*(annot)
if tup.elts is not None:
tup_elts: list[ast.AST*]* = tup.elts
tn: sizet = len(tup_elts)
for ti in range(tn):
if ti > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
el: ast.AST* = tup_elts.get(ti)
pos = _PyiTypeStr(buf, buf_size, pos, el, depth + 1)
return pos
# Call: call expression (used by decorators such as @t.NoVTable())
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(annot)
pos = _PyiTypeStr(buf, buf_size, pos, cl.func, depth + 1)
pos = _PyiEmit(buf, buf_size, pos, "(")
if cl.args is not None:
cl_args: list[ast.AST*]* = cl.args
can: sizet = len(cl_args)
for cai in range(can):
if cai > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
ca: ast.AST* = cl_args.get(cai)
pos = _PyiTypeStr(buf, buf_size, pos, ca, depth + 1)
pos = _PyiEmit(buf, buf_size, pos, ")")
return pos
# Unknown node type: fail fast rather than silently writing "int" and producing a wrong signature.
GenApi.fatal_error(annot, "unknown vpi type-annotation node type", "TypeError")
return _PyiEmit(buf, buf_size, pos, "int") # unreachable (fatal_error already exited)
# _PyiDefaultStr - render a default-value expression for '= <default>' in vpi.
# Stubs must keep defaults so VpiReader can register the default-parameter table.
def _PyiDefaultStr(buf: bytes, buf_size: sizet, pos: sizet, node: ast.AST*, depth: int = 0) -> sizet:
"""Render a default-value expression into buf and return the new pos"""
# Recursion guard: fail fast on a self-referential AST (same as _PyiTypeStr)
if depth > 64:
VLogger.error("vpi default-value expression recursion too deep (AST cycle or nesting over 64 levels)", "StubGen")
sys.exit(1)
if node is None: return pos
k: int = node.kind()
# Constant: emit numeric/string/bool/None literals directly
if k == ast.ASTKind.Constant:
cn: ast.Constant* = ast.Constant*(node)
if cn.const_kind == ast.CONST_INT or cn.const_kind == ast.CONST_CHAR:
num_buf_dv: str = stdlib.malloc(24)
if num_buf_dv is not None:
viperlib.snprintf(num_buf_dv, 24, "%d", cn.int_val)
pos = _PyiEmit(buf, buf_size, pos, num_buf_dv)
stdlib.free(num_buf_dv)
return pos
if cn.const_kind == ast.CONST_STR:
pos = _PyiEmit(buf, buf_size, pos, "\"")
if cn.str_val is not None: pos = _PyiEmit(buf, buf_size, pos, cn.str_val)
return _PyiEmit(buf, buf_size, pos, "\"")
if cn.const_kind == ast.CONST_BOOL:
if cn.int_val != 0: return _PyiEmit(buf, buf_size, pos, "True")
return _PyiEmit(buf, buf_size, pos, "False")
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)
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)
return pos
# UnaryOp: unary operation (e.g. -1, ~0)
if k == ast.ASTKind.UnaryOp:
uop: ast.UnaryOp* = ast.UnaryOp*(node)
if uop.op == ast.OpKind.USub: pos = _PyiEmit(buf, buf_size, pos, "-")
elif uop.op == ast.OpKind.UAdd: pos = _PyiEmit(buf, buf_size, pos, "+")
elif uop.op == ast.OpKind.Invert: pos = _PyiEmit(buf, buf_size, pos, "~")
else: return pos
if uop.operand is not None: return _PyiDefaultStr(buf, buf_size, pos, uop.operand, depth + 1)
return pos
# Other complex expressions (BinOp etc.): emit nothing; caller enforces fail-fast
return pos
# _PyiRejectCReturnDecorator - fail if @c.CReturn appears in the decorator list.
# The decorator was removed; a stale stub must be regenerated. 0 = none found.
def _PyiRejectCReturnDecorator(node: ast.AST*, decorators: list[ast.AST*]*) -> int:
"""Fail fast on a @c.CReturn decorator (returns 0 when absent)"""
if decorators is None: return 0
dn: sizet = len(decorators)
di: sizet = 0
while di < dn:
dnode: ast.AST* = decorators.get(di)
di += 1
if dnode is None: continue
# Handles both @c.CReturn(...) (Call with func=Attribute) and bare @c.CReturn
dattr: ast.Attribute* = None
if dnode.kind() == ast.ASTKind.Call:
dcall: ast.Call* = ast.Call*(dnode)
if dcall.func is not None and dcall.func.kind() == ast.ASTKind.Attribute: dattr = ast.Attribute*(dcall.func)
elif dnode.kind() == ast.ASTKind.Attribute: dattr = ast.Attribute*(dnode)
if dattr is None or dattr.attr is None: continue
if string.strcmp(dattr.attr, "CReturn") == 0:
GenApi.fatal_error(node, "vpi still contains `@c.CReturn` (removed): regenerate the .vpi using a return annotation `-> tuple[ty1, ty2, ...]` (you may add --clean)", "TypeError")
return 1
return 0
# ============================================================
# 逃逸摘要计算用的复用临时区(74 槽:gset[0]=global 计数、gset[1..65)=名哈希、box=gset+65)。
# 只分配一次,避免每个 def 都分配;布局与 StructGen 的 EscScratch 完全一致。
_g_pyi_esc_scratch: sizet* = None
def _PyiEnsureEscScratch() -> int:
"""惰性分配摘要临时区;1=可用 / 0=失败"""
# 必须声明 global:否则赋值会落到**局部**,调用方读到的仍是 NULL 全局,
# box 指针算成 0+520 → ASan: WRITE on 0x208(本文件它处同款写法见 _g_pyi_cdefine_found)。
global _g_pyi_esc_scratch
if _g_pyi_esc_scratch is not None: return 1
_g_pyi_esc_scratch = sizet*(stdlib.malloc(74 * 8))
if _g_pyi_esc_scratch is None: return 0
string.memset(_g_pyi_esc_scratch, 0, 74 * 8)
return 1
# _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:
"""Emit a function signature into buf and return the new pos"""
# Decorators
if fn.decorator_list is not None:
fn_decorator_list: list[ast.AST*]* = fn.decorator_list
dn: sizet = len(fn_decorator_list)
for di in range(dn):
dec: ast.AST* = fn_decorator_list.get(di)
if dec is not None:
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 fn.name is not None: pos = _PyiEmit(buf, buf_size, pos, fn.name)
pos = _PyiEmit(buf, buf_size, pos, "(")
# Parameter list
args_node: ast.AST* = fn.args
if args_node is not None:
args: ast.Arguments* = ast.Arguments*(args_node)
first: int = 1
# Positional parameters
if args.args is not None:
args_args: list[ast.AST*]* = args.args
an: sizet = len(args_args)
# Defaults right-align to args tail: defaults[i] maps to args[an - dn + i];
# emit '= <default>' and declare arg_defaults inline to avoid .get misdispatch.
arg_defaults: list[ast.AST*]* = args.defaults
dn: sizet = 0
if arg_defaults is not None: dn = len(arg_defaults)
ai: sizet = 0
while ai < an:
if first == 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
first = 0
ag: ast.Arg* = ast.Arg*(args_args.get(ai))
if ag.arg is not None: pos = _PyiEmit(buf, buf_size, pos, ag.arg)
if ag.annotation is not None:
pos = _PyiEmit(buf, buf_size, pos, ": ")
pos = _PyiTypeStr(buf, buf_size, pos, ag.annotation)
if arg_defaults is not None and an - dn <= ai:
di: sizet = ai - (an - dn)
dnode: ast.AST* = arg_defaults.get(di)
if dnode is not None:
pos = _PyiEmit(buf, buf_size, pos, " = ")
pos = _PyiDefaultStr(buf, buf_size, pos, dnode)
ai += 1
# *args
if args.vararg is not None:
if first == 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
first = 0
pos = _PyiEmit(buf, buf_size, pos, "*")
va: ast.Arg* = ast.Arg*(args.vararg)
if va.arg is not None: pos = _PyiEmit(buf, buf_size, pos, va.arg)
# **kwargs
if args.kwarg is not None:
if first == 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
first = 0
pos = _PyiEmit(buf, buf_size, pos, "**")
kw: ast.Arg* = ast.Arg*(args.kwarg)
if kw.arg is not None: pos = _PyiEmit(buf, buf_size, pos, kw.arg)
pos = _PyiEmit(buf, buf_size, pos, ")")
# Return type
if fn.returns is not None:
pos = _PyiEmit(buf, buf_size, pos, " -> ")
pos = _PyiTypeStr(buf, buf_size, pos, fn.returns)
else:
# Without a `->` annotation, @c.CReturn is removed: fail fast to regenerate vpi
_PyiRejectCReturnDecorator(fn, fn.decorator_list)
pos = _PyiEmit(buf, buf_size, pos, ": pass")
# v3 跨模块逃逸摘要:把形参逃逸掩码与返回借用位写成**同一行**的行尾注释,供 VpiReader
# 解析后在别的模块的调用点查询(跨模块摘要表按文件重置,只能靠 vpi 传递)。
# 只在真有逃逸/返回借用时写,保持 vpi 干净:无标记 = 无摘要 = 调用点保守放行。
# P2 willFree:同行走 `# ... wf=<mask>`(第 k 位=第 k 个形参在本调用后失效),
# 纯声明式、不依赖逃逸扫描的 scratch,所以单独算、独立决定是否落盘。
wf_mask_c: int = GenApi.compute_willfree_mask(fn)
esc_v_c: int = 0
ret_v_c: int = 0
ef_v_c: int = 0
ne_v_c: int = 0
rk_v_c: int = 0
if _PyiEnsureEscScratch() != 0:
esc_box_c: sizet* = sizet*(uint64(_g_pyi_esc_scratch) + 520)
if GenApi.compute_escape_summary(None, fn, _g_pyi_esc_scratch, esc_box_c) == 0:
esc_v_c = int(esc_box_c[0])
ret_v_c = int(esc_box_c[1])
# 声明式指令(@life(escape(...)) / @life(noescape(...))):compute_escape_summary 把
# 契约掩码落在 box[3]/box[4],随同行走 esce=/escn= 落盘,跨模块调用点才能最后套用。
ef_v_c = int(esc_box_c[3])
ne_v_c = int(esc_box_c[4])
# A2(§14)`@life(ret(...))`:返回值生命期契约落 box[5],随同行走 escr= 落盘。
rk_v_c = int(esc_box_c[5])
# 跨模块常量特化:算"哪些常量绑定能清掉哪些逃逸位"(每条 = 打包的 p|k|m),随同行走 escg= 落盘。
# 只在真有逃逸位时才有意义;算出来 0 条就不写(保持 vpi 干净)。
clears_c: sizet* = None
n_clears_c: int = 0
if esc_v_c != 0:
clears_c = sizet*(stdlib.malloc(4 * 8))
if clears_c is not None:
n_clears_c = GenApi.compute_escape_clears(fn, clears_c, 4)
if esc_v_c != 0 or ret_v_c != 0 or wf_mask_c != 0 or n_clears_c > 0 or ef_v_c != 0 or ne_v_c != 0 or rk_v_c != 0:
tmp_c: bytes = stdlib.malloc(96)
if tmp_c is not None:
if esc_v_c != 0 or ret_v_c != 0:
if wf_mask_c != 0: viperlib.snprintf(tmp_c, 96, " # esc=%d ret=%d wf=%d", esc_v_c, ret_v_c, wf_mask_c)
else: viperlib.snprintf(tmp_c, 96, " # esc=%d ret=%d", esc_v_c, ret_v_c)
elif wf_mask_c != 0: viperlib.snprintf(tmp_c, 96, " # wf=%d", wf_mask_c)
# 只有声明式指令(既无逃逸/借用也无 willFree)时也要落一个 '#' 注释段:
# 读侧是"见到 '#' 才去找 esce=/escn=",没有 '#' 就整行都不看。
else: viperlib.snprintf(tmp_c, 96, " # esc=0 ret=0")
pos = _PyiEmit(buf, buf_size, pos, tmp_c)
# 跨模块常量特化:每条"常量守卫"落一个 escg= 字段(打包 p|k|m,见
# StructGenMixin._PackEscapeClear)。同一函数多条 → 重复该字段,读侧按同键全部收集。
if clears_c is not None:
ci_c: int = 0
while ci_c < n_clears_c:
viperlib.snprintf(tmp_c, 96, " escg=%d", int(clears_c[ci_c]))
pos = _PyiEmit(buf, buf_size, pos, tmp_c)
ci_c += 1
# 声明式指令契约:escape(...) → esce=,noescape(...) → escn=(各自独立字段,
# 避免和 esc=/ret=/wf= 挤在同一个 96 字节 snprintf 里)。
if ef_v_c != 0:
viperlib.snprintf(tmp_c, 96, " esce=%d", ef_v_c)
pos = _PyiEmit(buf, buf_size, pos, tmp_c)
if ne_v_c != 0:
viperlib.snprintf(tmp_c, 96, " escn=%d", ne_v_c)
pos = _PyiEmit(buf, buf_size, pos, tmp_c)
# A2(§14):`@life(ret(...))` 的返回值生命期契约 → escr=(编码见 GenApi.RET_KIND_*)。
if rk_v_c != 0:
viperlib.snprintf(tmp_c, 96, " escr=%d", rk_v_c)
pos = _PyiEmit(buf, buf_size, pos, tmp_c)
stdlib.free(tmp_c)
if clears_c is not None: stdlib.free(clears_c)
pos = _PyiEmit(buf, buf_size, pos, "\n")
return pos
# _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"""
# Decorators
if cls.decorator_list is not None:
cls_decorator_list: list[ast.AST*]* = cls.decorator_list
dcn: sizet = len(cls_decorator_list)
for dci in range(dcn):
ddec: ast.AST* = cls_decorator_list.get(dci)
if ddec is not None:
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 cls.name is not None: pos = _PyiEmit(buf, buf_size, pos, cls.name)
# PEP 695 type parameters (class Foo[T]:)
if cls.type_params is not None:
# ClassDef.type_params is really rawptr[list[str]] (a list of name strings),
# not list[rawptr[AST]] (that is decorator_list's type); align with the rest of the repo.
cls_type_params: list[str]* = cls.type_params
tpn: sizet = len(cls_type_params)
if tpn > 0:
pos = _PyiEmit(buf, buf_size, pos, "[")
for tpi in range(tpn):
if tpi > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
tpname: str = cls_type_params.get(tpi)
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:
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(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, ")")
pos = _PyiEmit(buf, buf_size, pos, ":\n")
# Class members
has_member: int = 0
if cls.children is not None:
cls_children: list[ast.AST*]* = cls.children
cn: sizet = len(cls_children)
ci: sizet = 0
while ci < cn:
stmt: ast.AST* = cls_children.get(ci)
if stmt is not None:
sk: int = stmt.kind()
# Methods
if sk == ast.ASTKind.FunctionDef:
mfn: ast.FunctionDef* = ast.FunctionDef*(stmt)
# __init__ method: first extract self.x: Type attributes
if mfn.name is not None and string.strcmp(mfn.name, "__init__") == 0:
if mfn.children is not None:
mfn_children: list[ast.AST*]* = mfn.children
icn: sizet = len(mfn_children)
ici: sizet = 0
while ici < icn:
ibody: ast.AST* = mfn_children.get(ici)
if ibody is not None and ibody.kind() == ast.ASTKind.AnnAssign:
iaa: ast.AnnAssign* = ast.AnnAssign*(ibody)
if iaa.target is not None and iaa.target.kind() == ast.ASTKind.Attribute:
iat: ast.Attribute* = ast.Attribute*(iaa.target)
# Check whether target.value is Name("self")
if iat.value is not None and iat.value.kind() == ast.ASTKind.Name:
iavn: ast.Name* = ast.Name*(iat.value)
if iavn.id is None or string.strcmp(iavn.id, "self") != 0:
ici += 1
continue
pos = _PyiIndent(buf, buf_size, pos, indent_level + 1)
if iat.attr is not None: pos = _PyiEmit(buf, buf_size, pos, iat.attr)
pos = _PyiEmit(buf, buf_size, pos, ": ")
pos = _PyiTypeStr(buf, buf_size, pos, iaa.annotation)
pos = _PyiEmit(buf, buf_size, pos, "\n")
has_member = 1
ici += 1
pos = _PyiFunction(buf, buf_size, pos, mfn, indent_level + 1, cls.name)
has_member = 1
# Annotated attributes
elif sk == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
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 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)
pos = _PyiEmit(buf, buf_size, pos, "\n")
has_member = 1
# Nested classes
elif sk == ast.ASTKind.ClassDef:
ncls: ast.ClassDef* = ast.ClassDef*(stmt)
pos = _PyiClass(buf, buf_size, pos, ncls, indent_level + 1)
has_member = 1
ci += 1
# Emit pass for an empty class
if has_member == 0:
pos = _PyiIndent(buf, buf_size, pos, indent_level + 1)
pos = _PyiEmit(buf, buf_size, pos, "pass\n")
return pos
# _GeneratePyiFromAst - walk the AST Module and emit .vpi stub content into buf.
# Returns bytes written (0 on failure); rel_path should be absolute for error attribution.
def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*, rel_path: str, buf: bytes, buf_size: sizet) -> sizet:
"""Generate .vpi stub content from an AST into buf"""
if tree is None or buf is None or buf_size == 0: return 0
# Error attribution: point "current file" at the file being stubbed. Callers must pass
# an absolute path (fatal_error opens it directly). Set once at entry to cover all branches.
if rel_path is not None: GenApi.set_current_file_type(rel_path)
pos: sizet = 0
# Pre-pass: register this file's CDefines into the _g_pyi cache in declaration order
# (vpi runs before the translate phase; without this, BinOp Name refs degrade to 0).
global _g_pyi_cdef_names, _g_pyi_cdef_vals, _g_pyi_cdef_count
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)
_g_pyi_cdef_count = 0
for pci0 in range(pcn):
stmt_pc0: ast.AST* = pre_children.get(pci0)
if stmt_pc0 is not None and stmt_pc0.kind() == ast.ASTKind.AnnAssign:
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 _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))
# Pass 2: register in declaration order (BinOp Name refs depend on earlier entries)
pci: sizet = 0
cdi: int = 0
for pci in range(pcn):
stmt_pc: ast.AST* = pre_children.get(pci)
if stmt_pc is not None and stmt_pc.kind() == ast.ASTKind.AnnAssign:
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
# File header
pos = _PyiEmit(buf, buf_size, pos, "\"\"\"Auto-generated Python stub file\"\"\"\n\n")
if tree.children is None:
if pos < buf_size: buf[pos] = 0
return pos
tree_children: list[ast.AST*]* = tree.children
n: sizet = len(tree_children)
# Pass 1: detect import t/c
has_import_t: int = 0
has_import_c: int = 0
for i in range(n):
stmt: ast.AST* = tree_children.get(i)
if stmt is not None and stmt.kind() == ast.ASTKind.Import:
imp: ast.Import* = ast.Import*(stmt)
if imp.names is not None:
imp_names: list[ast.AST*]* = imp.names
an: sizet = len(imp_names)
for ai in range(an):
al: ast.Alias* = ast.Alias*(imp_names.get(ai))
if al is not None and al.name is not None:
if string.strcmp(al.name, "t") == 0: has_import_t = 1
elif string.strcmp(al.name, "c") == 0: has_import_c = 1
# Add missing import t/c
if has_import_t == 0 and has_import_c == 0: pos = _PyiEmit(buf, buf_size, pos, "import t, c\n\n")
elif has_import_t == 0: pos = _PyiEmit(buf, buf_size, pos, "import t\n\n")
elif has_import_c == 0: pos = _PyiEmit(buf, buf_size, pos, "import c\n\n")
else: pos = _PyiEmit(buf, buf_size, pos, "\n")
# Pass 2: emit declarations
for i in range(n):
stmt = tree_children.get(i)
if stmt is not None:
k: int = stmt.kind()
# Import statement
if k == ast.ASTKind.Import:
imp2: ast.Import* = ast.Import*(stmt)
pos = _PyiEmit(buf, buf_size, pos, "import ")
# fail-fast: an empty alias list would write a broken `import ` line and
# lose all names, breaking package re-export registration.
if imp2.names is None or len(imp2.names) == 0:
GenApi.fatal_error(stmt, "vpi generation: the import statement alias list is empty (expected >=1 alias) -- AST construction/parse anomaly", "NameError")
if imp2.names is not None:
imp2_names: list[ast.AST*]* = imp2.names
an2: sizet = len(imp2_names)
for ai2 in range(an2):
if ai2 > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
al2: ast.Alias* = ast.Alias*(imp2_names.get(ai2))
# fail-fast: never silently write "import , ". Import names are the
# only input for package re-export registration. Report the two causes separately.
if al2 is None:
GenApi.fatal_error(stmt, "vpi generation: import alias **node pointer is null** (Alias(...) construction produced no object: " "pool.alloc failed or __init__ was not run) -- losing the import name breaks package re-export registration", "NameError")
if al2.name is None or al2.name[0] == '\0':
GenApi.fatal_error(stmt, "vpi generation: the import alias node exists but **name is empty** (_copy_str returned None / " "__init__ did not set name) -- losing the import name breaks package re-export registration", "NameError")
pos = _PyiEmit(buf, buf_size, pos, al2.name)
if al2.asname is not None and string.strlen(al2.asname) > 0:
pos = _PyiEmit(buf, buf_size, pos, " as ")
pos = _PyiEmit(buf, buf_size, pos, al2.asname)
pos = _PyiEmit(buf, buf_size, pos, "\n")
# ImportFrom statement
elif k == ast.ASTKind.ImportFrom:
ifr: ast.ImportFrom* = ast.ImportFrom*(stmt)
pos = _PyiEmit(buf, buf_size, pos, "from ")
lvl: int = ifr.level
while lvl > 0:
pos = _PyiEmit(buf, buf_size, pos, ".")
lvl -= 1
if ifr.module is not None: pos = _PyiEmit(buf, buf_size, pos, ifr.module)
pos = _PyiEmit(buf, buf_size, pos, " import ")
# fail-fast: same as Import; `from X import *` uses Alias("*"). An empty
# list or empty name means a broken AST and would break re-export registration.
if ifr.names is None or len(ifr.names) == 0:
GenApi.fatal_error(stmt, "vpi generation: the from-import statement alias list is empty (expected >=1 alias, including star)" "-- AST construction/parse anomaly", "NameError")
if ifr.names is not None:
ifr_names: list[ast.AST*]* = ifr.names
an3: sizet = len(ifr_names)
for ai3 in range(an3):
if ai3 > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
al3: ast.Alias* = ast.Alias*(ifr_names.get(ai3))
# fail-fast: same as Import (report the two causes separately).
if al3 is None:
GenApi.fatal_error(stmt, "vpi generation: from-import alias **node pointer is null** (Alias(...) construction produced no object: " "pool.alloc failed or __init__ was not run) -- package re-export registration will therefore fail", "NameError")
if al3.name is None or al3.name[0] == '\0':
GenApi.fatal_error(stmt, "vpi generation: the from-import alias node exists but **name is empty** (_copy_str returned None / " "__init__ did not set name) -- package re-export registration will therefore fail", "NameError")
pos = _PyiEmit(buf, buf_size, pos, al3.name)
if al3.asname is not None and string.strlen(al3.asname) > 0:
pos = _PyiEmit(buf, buf_size, pos, " as ")
pos = _PyiEmit(buf, buf_size, pos, al3.asname)
pos = _PyiEmit(buf, buf_size, pos, "\n")
# Function definition (_PyiFunction already ends with a newline)
elif k == ast.ASTKind.FunctionDef:
fn: ast.FunctionDef* = ast.FunctionDef*(stmt)
# 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, "")
# Class definition
elif k == ast.ASTKind.ClassDef:
cls: ast.ClassDef* = ast.ClassDef*(stmt)
pos = _PyiClass(buf, buf_size, pos, cls, 0)
# Annotated variable declaration
elif k == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
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.
is_ctypedef_ann: int = 0
if aa.annotation is not None and aa.annotation.kind() == ast.ASTKind.Attribute:
ctd_at: ast.Attribute* = ast.Attribute*(aa.annotation)
if ctd_at.attr is not None and string.strcmp(ctd_at.attr, "CTypedef") == 0:
if ctd_at.value is not None and ctd_at.value.kind() == ast.ASTKind.Name:
ctd_mod: ast.Name* = ast.Name*(ctd_at.value)
if ctd_mod.id is not None and string.strcmp(ctd_mod.id, "t") == 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:
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:
viperlib.snprintf(num_buf, 32, " = %lld", cv.int_val)
pos = _PyiEmit(buf, buf_size, pos, num_buf)
elif cv.const_kind == ast.CONST_FLOAT:
# Float CDefines (e.g. numpy pi/e) must emit a value or read-back
# degrades to a global pointer; %.17g round-trips double exactly.
fbuf_c: bytes = mb.alloc(64)
if fbuf_c is not None:
viperlib.snprintf(fbuf_c, 64, " = %.17g", cv.float_val)
pos = _PyiEmit(buf, buf_size, pos, fbuf_c)
elif cv.const_kind == ast.CONST_BOOL:
if cv.int_val != 0: pos = _PyiEmit(buf, buf_size, pos, " = True")
else: pos = _PyiEmit(buf, buf_size, pos, " = False")
elif cv.const_kind == ast.CONST_STR:
# String CDefines (e.g. pyzlib.ZLIB_VERSION) must emit a value so the stub is self-contained
sbuf_c: bytes = mb.alloc(string.strlen(cv.str_val) + 6)
if sbuf_c is not None:
viperlib.snprintf(sbuf_c, string.strlen(cv.str_val) + 6, " = \"%s\"", cv.str_val)
pos = _PyiEmit(buf, buf_size, pos, sbuf_c)
elif cv.const_kind == ast.CONST_NONE: pos = _PyiEmit(buf, buf_size, pos, " = None")
elif aa.value is not None and aa.value.kind() == ast.ASTKind.UnaryOp:
# Negative constant (e.g. INVALID_HANDLE_VALUE = -1):
# UnaryOp(USub, Constant(1)) -> -1
uop_cv: ast.UnaryOp* = ast.UnaryOp*(aa.value)
num_buf_u: bytes = mb.alloc(32)
if uop_cv.op == ast.OpKind.USub and uop_cv.operand is not None and uop_cv.operand.kind() == ast.ASTKind.Constant:
uop_const: ast.Constant* = ast.Constant*(uop_cv.operand)
if uop_const.const_kind == ast.CONST_INT and num_buf_u is not None:
neg_val: int64 = -uop_const.int_val
viperlib.snprintf(num_buf_u, 32, " = %lld", neg_val)
pos = _PyiEmit(buf, buf_size, pos, num_buf_u)
elif aa.value is not None and aa.value.kind() == ast.ASTKind.Call:
# Type-constructor constant (e.g. t.CUnsignedLong(-11)):
# extract and emit the integer value of the first argument.
call_cv: ast.Call* = ast.Call*(aa.value)
if call_cv.args is not None:
call_args_cv: list[ast.AST*]* = call_cv.args
if len(call_args_cv) > 0:
first_arg: ast.AST* = call_args_cv.get(0)
if first_arg is not None:
arg_kind: int = first_arg.kind()
if arg_kind == ast.ASTKind.Constant:
ca_cv: ast.Constant* = ast.Constant*(first_arg)
num_buf_c: bytes = mb.alloc(32)
if ca_cv.const_kind == ast.CONST_INT and num_buf_c is not None:
viperlib.snprintf(num_buf_c, 32, " = %lld", ca_cv.int_val)
pos = _PyiEmit(buf, buf_size, pos, num_buf_c)
elif ca_cv.const_kind == ast.CONST_FLOAT:
# Call-wrapped float constant (e.g. float(3.14159...)): emit the
# value or read-back degrades to 0.0; %.17g round-trips double.
fbuf_cc: bytes = mb.alloc(64)
if fbuf_cc is not None:
viperlib.snprintf(fbuf_cc, 64, " = %.17g", ca_cv.float_val)
pos = _PyiEmit(buf, buf_size, pos, fbuf_cc)
elif arg_kind == ast.ASTKind.UnaryOp:
uop_ca: ast.UnaryOp* = ast.UnaryOp*(first_arg)
num_buf_cu: bytes = mb.alloc(32)
if uop_ca.op == ast.OpKind.USub and uop_ca.operand is not None and uop_ca.operand.kind() == ast.ASTKind.Constant:
uoc: ast.Constant* = ast.Constant*(uop_ca.operand)
if uoc.const_kind == ast.CONST_INT and num_buf_cu is not None:
neg_c_val: int64 = -uoc.int_val
viperlib.snprintf(num_buf_cu, 32, " = %lld", neg_c_val)
pos = _PyiEmit(buf, buf_size, pos, num_buf_cu)
elif uoc.const_kind == ast.CONST_FLOAT:
# Negative Call-wrapped float (e.g. float(-1.5)):
# same as CONST_FLOAT, %.17g round-trips exactly.
neg_cf: double = -uoc.float_val
fbuf_cu: bytes = mb.alloc(64)
if fbuf_cu is not None:
viperlib.snprintf(fbuf_cu, 64, " = %.17g", neg_cf)
pos = _PyiEmit(buf, buf_size, pos, fbuf_cu)
elif arg_kind == ast.ASTKind.BinOp:
# BinOp inside a Call (e.g. t.CUnsignedLong(A | B)):
# recursively evaluate the BinOp value.
binop_val_bo: int64 = _eval_binop_int(first_arg)
num_buf_bo: bytes = mb.alloc(32)
if (binop_val_bo != 0 or _eval_binop_valid(first_arg) != 0) and num_buf_bo is not None:
viperlib.snprintf(num_buf_bo, 32, " = %lld", binop_val_bo)
pos = _PyiEmit(buf, buf_size, pos, num_buf_bo)
elif aa.value is not None and aa.value.kind() == ast.ASTKind.BinOp:
# BinOp constant (e.g. FOREGROUND_COMBO = FOREGROUND_RED | FOREGROUND_GREEN):
# recursively evaluate and emit it.
binop_val_av: int64 = _eval_binop_int(aa.value)
if binop_val_av != 0 or _eval_binop_valid(aa.value) != 0:
num_buf_b: bytes = mb.alloc(32)
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:
# 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)
if name_val.id is not None:
pos = _PyiEmit(buf, buf_size, pos, " = ")
pos = _PyiEmit(buf, buf_size, pos, name_val.id)
pos = _PyiEmit(buf, buf_size, pos, "\n")
# Global assignment (no annotation) - infer type from the Constant value
elif k == ast.ASTKind.Assign:
asg: ast.Assign* = ast.Assign*(stmt)
if asg.targets is not None and asg.value is not None:
asg_targets: list[ast.AST*]* = asg.targets
atn: sizet = len(asg_targets)
if atn == 1:
atg: ast.AST* = asg_targets.get(0)
if atg is not None and atg.kind() == ast.ASTKind.Name:
agn: ast.Name* = ast.Name*(atg)
aval: ast.AST* = asg.value
avk: int = aval.kind()
inferred: int = 0
if avk == ast.ASTKind.Constant:
acv: ast.Constant* = ast.Constant*(aval)
if acv.const_kind == ast.CONST_INT: inferred = 1
elif acv.const_kind == ast.CONST_FLOAT: inferred = 2
elif acv.const_kind == ast.CONST_STR: inferred = 3
elif acv.const_kind == ast.CONST_BOOL: inferred = 4
elif acv.const_kind == ast.CONST_NONE: inferred = 5
if inferred > 0 and agn.id is not None:
pos = _PyiEmit(buf, buf_size, pos, agn.id)
pos = _PyiEmit(buf, buf_size, pos, ": ")
if inferred == 1: pos = _PyiEmit(buf, buf_size, pos, "int")
elif inferred == 2: pos = _PyiEmit(buf, buf_size, pos, "double")
elif inferred == 3: pos = _PyiEmit(buf, buf_size, pos, "str")
elif inferred == 4: pos = _PyiEmit(buf, buf_size, pos, "bool")
elif inferred == 5: pos = _PyiEmit(buf, buf_size, pos, "None")
pos = _PyiEmit(buf, buf_size, pos, "\n")
# NUL terminator
if pos < buf_size: buf[pos] = 0
else: buf[buf_size - 1] = 0
return pos