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 '= ' 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 '= ' 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=`(第 k 位=第 k 个形参在本调用后失效), # 纯声明式、不依赖逃逸扫描的 scratch,所以单独算、独立决定是否落盘。 wf_mask_c: int = GenApi.compute_willfree_mask(fn) esc_v_c: int = 0 ret_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]) if esc_v_c != 0 or ret_v_c != 0 or wf_mask_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) else: viperlib.snprintf(tmp_c, 96, " # wf=%d", wf_mask_c) pos = _PyiEmit(buf, buf_size, pos, tmp_c) stdlib.free(tmp_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