feat(lang): 新增 cif/celif/celse 条件编译与值引用注解 &T/&temp T,assert 改译 testcheck.check
cif 三件套:StmtGenCifMixin 负责常量求值(含 defined(模块宏) 的 Attribute 形式),链式错配与含运行时变量一律 fail-fast;删掉 c.CIf 家族。值引用:TypeGenResolve 让 CRef/CTempRef 与 t.CPtr 走同一解析路径(&void 折叠为 i8*,删除 t.CNeedPtr),CallGenBase 在调用点按形参模式自动取址(ref 只接受左值,右值 fail-fast;temp 物化临时栈),跨模块经 VpiReader/StubMergerBuild 传递模式。assert:StmtGenMixin 合成 testcheck.check(cond, None, msg),Translator 检测到 assert 时自动注入 testcheck 导入(带去重与防环),深递归守卫改用 stdio.printf + stdlib.exit(Translator 无 Gen 成员)。
This commit is contained in:
1 parent
ce50daa87a
commit
2fb648d68a
22 files changed
+556
-85
No files matched your search
@@ -129,7 +129,7 @@ def _RewriteDepsTxt(temp_dir: str, sha1: str, deps_str: str) -> None:
|
||||
|
||||
|
||||
def _BreakContainerCyclesInDeps(temp_dir: str) -> int:
|
||||
"""Remove _list/_dict injection cycle edges after scanning and sync deps.txt"""
|
||||
"""Remove _list/_dict/testcheck injection cycle edges after scanning and sync deps.txt"""
|
||||
if StubMerger._includes_deps_sha1 is None or StubMerger._includes_deps_str is None: return 0
|
||||
cnt: sizet = StubMerger._includes_deps_count
|
||||
ci: sizet
|
||||
@@ -137,7 +137,7 @@ def _BreakContainerCyclesInDeps(temp_dir: str) -> int:
|
||||
sha1_m: str = StubMerger._includes_deps_sha1 + sizet(ci) * 17
|
||||
deps_m: str = StubMerger._includes_deps_str + sizet(ci) * StubMerger.MAX_DEPS_STR_LEN
|
||||
if sha1_m is None or deps_m is None: continue
|
||||
if string.strstr(deps_m, "_list") is None and string.strstr(deps_m, "_dict") is None: continue
|
||||
if string.strstr(deps_m, "_list") is None and string.strstr(deps_m, "_dict") is None and string.strstr(deps_m, "testcheck") is None: continue
|
||||
# Module name (basename of RelPath without extension)
|
||||
rel_m: str = StubMergerStore.LookupSha1RelPath(sha1_m)
|
||||
if rel_m is None: continue
|
||||
@@ -173,6 +173,13 @@ def _BreakContainerCyclesInDeps(temp_dir: str) -> int:
|
||||
dct_deps: str = StubMergerStore.LookupIncludesDeps(dct_sha1)
|
||||
if dct_deps is not None and _DepsContainsName(dct_deps, nm_m) != 0:
|
||||
if _DepsRemoveName(deps_m, "_dict") != 0: _RewriteDepsTxt(temp_dir, sha1_m, deps_m)
|
||||
# testcheck <-> M cycle (assert auto-import)
|
||||
if string.strstr(deps_m, "testcheck") is not None:
|
||||
tc_sha1: str = StubMergerStore.LookupSha1ByModName("testcheck")
|
||||
if tc_sha1 is not None:
|
||||
tc_deps: str = StubMergerStore.LookupIncludesDeps(tc_sha1)
|
||||
if tc_deps is not None and _DepsContainsName(tc_deps, nm_m) != 0:
|
||||
if _DepsRemoveName(deps_m, "testcheck") != 0: _RewriteDepsTxt(temp_dir, sha1_m, deps_m)
|
||||
stdlib.free(nm_m)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -850,6 +850,41 @@ def _vpi_param_annot_seg_is(src_sha1: str, cls_name: str, method_name: str, seg_
|
||||
return 1
|
||||
|
||||
|
||||
# _vpi_param_annot_seg_mode - value-reference mode of a param-annotation segment: 0=none / 1=ref / 2=temp.
|
||||
# The definition side writes `T | t.CRef` / `T | t.CTempRef`; the call side mirrors it (auto-address).
|
||||
def _vpi_param_annot_seg_mode(prm_annots: str, seg_idx: int) -> int:
|
||||
"""Return the value-reference mode encoded in a vpi param-annotation segment."""
|
||||
if prm_annots is None or seg_idx < 0: return 0
|
||||
cur: sizet = 0
|
||||
for seg in range(seg_idx):
|
||||
while prm_annots[cur] != '\0' and prm_annots[cur] != '@': cur += 1
|
||||
if prm_annots[cur] == '\0': return 0
|
||||
cur += 1
|
||||
slen: sizet = 0
|
||||
while prm_annots[cur + slen] != '\0' and prm_annots[cur + slen] != '@': slen += 1
|
||||
if slen == 0: return 0
|
||||
seg_buf: bytes = stdlib.malloc(slen + 1)
|
||||
if seg_buf is None: return 0
|
||||
string.strncpy(seg_buf, prm_annots + cur, slen)
|
||||
seg_buf[slen] = '\0'
|
||||
mode: int = 0
|
||||
if string.strstr(seg_buf, "CTempRef") is not None: mode = 2
|
||||
elif string.strstr(seg_buf, "CRef") is not None: mode = 1
|
||||
elif seg_buf[0] == '&':
|
||||
# The stub may carry the union marker (`T | t.CRef`, written by this compiler's StubGen) or
|
||||
# the source syntax (`&T` / `&temp T`, written by TPC's StubGen). Accept both, else a
|
||||
# cross-module value-reference call would silently lose its auto-address.
|
||||
q: sizet = 0
|
||||
while seg_buf[q] == ' ' or seg_buf[q] == '\t': q += 1
|
||||
if seg_buf[q] == '&':
|
||||
q += 1
|
||||
while seg_buf[q] == ' ' or seg_buf[q] == '\t': q += 1
|
||||
if slen - q >= 4 and string.strncmp(seg_buf + q, "temp", 4) == 0: mode = 2
|
||||
else: mode = 1
|
||||
stdlib.free(seg_buf)
|
||||
return mode
|
||||
|
||||
|
||||
# _ensure_vpi_param_annot_loaded - lazily load a module's vpi stub param-annotation cache.
|
||||
# Mirror of the return variant; reads the .vpi file on miss, idempotent.
|
||||
def _ensure_vpi_param_annot_loaded(src_sha1: str, temp_dir: str) -> int:
|
||||
@@ -1192,6 +1227,7 @@ def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy*, ret_annot: str, i32_ty: m
|
||||
return mlirlite.Void(pool)
|
||||
|
||||
has_cptr: int = 0
|
||||
has_cref: int = 0
|
||||
has_cvoid: int = 0
|
||||
first_ty: mlirlite.LLVMType* = None
|
||||
# Attributes of the segment that produced first_ty (for the has_cptr branch, N12):
|
||||
@@ -1252,7 +1288,9 @@ def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy*, ret_annot: str, i32_ty: m
|
||||
if lvl_cs > cptr_sub_lvl:
|
||||
cptr_sub_lvl = lvl_cs
|
||||
cptr_sub_ty = seg_ty_cs
|
||||
elif string.strcmp(seg_buf, "t.CPtr") == 0 or string.strcmp(seg_buf, "CPtr") == 0: has_cptr = 1
|
||||
elif string.strcmp(seg_buf, "t.CPtr") == 0 or string.strcmp(seg_buf, "CPtr") == 0: has_cptr = 1
|
||||
elif string.strcmp(seg_buf, "t.CRef") == 0 or string.strcmp(seg_buf, "CRef") == 0: has_cref = 1
|
||||
elif string.strcmp(seg_buf, "t.CTempRef") == 0 or string.strcmp(seg_buf, "CTempRef") == 0: has_cref = 1
|
||||
elif string.strcmp(seg_buf, "void") == 0 or string.strcmp(seg_buf, "CVoid") == 0 or string.strcmp(seg_buf, "void") == 0:
|
||||
# Bare void is an alias of t.CVoid and must be handled the same, else the
|
||||
# else branch sets first_td and produces the illegal Ptr(Void) (void* = i8*).
|
||||
@@ -1274,7 +1312,7 @@ def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy*, ret_annot: str, i32_ty: m
|
||||
stdlib.free(seg_buf)
|
||||
pos += 1 # skip '|'
|
||||
|
||||
if has_cptr != 0:
|
||||
if has_cptr != 0 or has_cref != 0:
|
||||
if has_cvoid != 0: return i8_ptr_ty
|
||||
if first_ty is not None:
|
||||
# The result itself is void (e.g. an opaque typedef resolving to void): CPtr merge
|
||||
|
||||
@@ -132,6 +132,8 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str,
|
||||
if ret_a == 0:
|
||||
# Container use detection: inject _list/_dict deps when an App file uses list/dict (bare annotation/ctor)
|
||||
tr._inject_auto_container_visibility(mb, tree)
|
||||
# assert lowers to testcheck.check(...): inject the testcheck dep when used
|
||||
tr._inject_auto_assert_import(mb, tree)
|
||||
# Register the App file in the store (empty incdir) and fill deps; read by
|
||||
# LookupIncludesDeps(app_sha1) when BFS expands its includes deps
|
||||
if e_r.Sha1 is not None:
|
||||
@@ -440,6 +442,8 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str,
|
||||
# Inject containers on demand (append _list/_dict when includes use list/dict generics);
|
||||
# inlined to break the _list/_dict cycle
|
||||
tr_r2._inject_auto_container_visibility(mb, tree_r)
|
||||
# assert lowers to testcheck.check(...): inject the testcheck dep when used
|
||||
tr_r2._inject_auto_assert_import(mb, tree_r)
|
||||
|
||||
# Fill in-memory deps and write deps.txt (for dep-graph expansion and on-demand Phase B loading)
|
||||
deps_r2: str = tr_r2._imported_modules
|
||||
|
||||
@@ -1290,6 +1290,8 @@ novirtual class AssignGenMixin:
|
||||
var_ty: mlirlite.LLVMType* = None
|
||||
annot: ast.AST* = aa.annotation
|
||||
if annot is not None:
|
||||
# Value-reference markers are parameter-only: a local variable `x: &T` fails fast.
|
||||
self.Gen.TypeGenResolve.reject_ref_marker_position(annot, trans._imported_modules, "a local variable declaration")
|
||||
# Specialize generics before resolving so list[X] does not degrade to opaque list*
|
||||
self._prespecialize_generic_annotation(trans, pool, annot)
|
||||
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, annot, trans._imported_modules, trans._from_imports, trans, 0)
|
||||
@@ -1429,6 +1431,8 @@ novirtual class AssignGenMixin:
|
||||
var_ty: mlirlite.LLVMType* = None
|
||||
annot: ast.AST* = aa.annotation
|
||||
if annot is not None:
|
||||
# Value-reference markers are parameter-only: a local variable `x: &T` fails fast.
|
||||
self.Gen.TypeGenResolve.reject_ref_marker_position(annot, trans._imported_modules, "a local variable declaration")
|
||||
# Same as PreScan: specialize generics first so list[X] does not degrade to bare list*
|
||||
self._prespecialize_generic_annotation(trans, pool, annot)
|
||||
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, annot, trans._imported_modules, trans._from_imports, trans, 0)
|
||||
|
||||
@@ -976,6 +976,14 @@ novirtual class AsyncGenMixin(CallGenBase.CallGenBase):
|
||||
def _handle_coro_if(self, trans: HT.Translator*, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, stmt: ast.AST*):
|
||||
ifn: ast.If* = ast.If*(stmt)
|
||||
if ifn is None or ifn.test is None: return
|
||||
# cif/celif (internal c.@CIf marker): compile-time prune, then translate only the taken branch
|
||||
# so awaits inside it still suspend in place. The marker must never be lowered as a runtime value.
|
||||
if self.Gen.StmtGenCif._is_cif_call(ifn.test) == 1:
|
||||
if self.Gen.StmtGenCif._prune_cif_test(pool, trans, ifn.test) != 0:
|
||||
self._handle_segment(trans, builder, pool, mod, ifn.children)
|
||||
else:
|
||||
self._handle_segment(trans, builder, pool, mod, ifn.orelse)
|
||||
return
|
||||
if self._find_expr_await(ifn.test) is not None:
|
||||
err_b: str = pool.alloc(256)
|
||||
if err_b is not None:
|
||||
|
||||
@@ -97,11 +97,85 @@ novirtual class CallGenBase:
|
||||
# _LookupMethodRetTyFromTextLL); key = full mangled name, value = text only.
|
||||
MethodRetTyMap: hashtable.HashTable* = None
|
||||
|
||||
# --- parameter auto-address modes for value-reference params (`&T` / `&temp T`) ---
|
||||
# key = callee symbol name -> byte-per-parameter mode string: 0=none, '1'=ref, '2'=temp
|
||||
# (PermPool-persistent; index = emitted parameter index, self included for methods).
|
||||
AutoAddrModes: hashtable.HashTable* = None
|
||||
|
||||
def InitCallGen(self) -> int:
|
||||
"""Initialize the call mixin fields (inlined by subclass constructors since NoVTable does not forward inherited symbols)"""
|
||||
if self is None: return 1
|
||||
return 0
|
||||
|
||||
# _register_auto_addr_modes - store per-parameter value-reference modes for a callee symbol name.
|
||||
def _register_auto_addr_modes(self, name: str, modes: str, count: int) -> int:
|
||||
"""Store per-parameter auto-address modes (PermPool-persistent) keyed by callee symbol name."""
|
||||
if name is None or modes is None or count <= 0: return 0
|
||||
perm: memhub.MemBuddy* = CompilerFactory.factory.PermPool
|
||||
if perm is None: return 0
|
||||
if self.AutoAddrModes is None:
|
||||
self.AutoAddrModes = hashtable.HashTable(perm)
|
||||
if self.AutoAddrModes is None: return 0
|
||||
key: str = perm.alloc(string.strlen(name) + 1)
|
||||
if key is None: return 0
|
||||
string.strcpy(key, name)
|
||||
val: str = perm.alloc(sizet(count) + 1)
|
||||
if val is None: return 0
|
||||
string.memcpy(val, modes, sizet(count))
|
||||
val[count] = char(0)
|
||||
self.AutoAddrModes[key] = val
|
||||
return 1
|
||||
|
||||
# _auto_addr_mode - look up the value-reference mode of parameter idx (0=none / 1=ref / 2=temp).
|
||||
def _auto_addr_mode(self, name: str, idx: int) -> int:
|
||||
"""Return the value-reference mode for parameter idx of a callee symbol name (0 if none/unknown)."""
|
||||
if self.AutoAddrModes is None or name is None or idx < 0: return 0
|
||||
v: t.CPtr = self.AutoAddrModes.__getitem__(name)
|
||||
if v is None: return 0
|
||||
modes: str = t.CPtr(v)
|
||||
i: int = 0
|
||||
while i < idx and modes[i] != char(0): i += 1
|
||||
if i != idx: return 0
|
||||
code: int = int(modes[idx])
|
||||
if code == 49: return 1 # '1' = ref
|
||||
if code == 50: return 2 # '2' = temp
|
||||
return 0
|
||||
|
||||
# translate_call_arg - translate one call argument, applying ref/temp auto-address when required.
|
||||
def translate_call_arg(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*,
|
||||
trans: HT.Translator*, callee_name: str, arg_idx: int, arg_node: ast.AST*,
|
||||
fpty: mlirlite.LLVMType*, use_ctx: str = "param") -> mlirlite.Value*:
|
||||
"""Translate a call argument; value-reference params take the address (ref) or a temp copy (temp)."""
|
||||
mode: int = self._auto_addr_mode(callee_name, arg_idx)
|
||||
if mode == 0:
|
||||
return self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node, None, 0, trans, fpty, use_ctx)
|
||||
# Value-reference param: translate without the pointer target hint (a char/str literal must
|
||||
# not be turned into a string), then apply the auto-address transform.
|
||||
av: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node, None, 0, trans, None, use_ctx)
|
||||
return self._apply_auto_addr_arg(builder, pool, mod, trans, mode, arg_node, av)
|
||||
|
||||
# _apply_auto_addr_arg - ref: take the lvalue address; temp: copy into a fresh entry-block slot.
|
||||
def _apply_auto_addr_arg(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*,
|
||||
trans: HT.Translator*, mode: int, node: ast.AST*, av: mlirlite.Value*) -> mlirlite.Value*:
|
||||
"""Apply a value-reference mode to a translated argument value."""
|
||||
if av is None: return None
|
||||
if self.Gen.ExprGenBase.is_ptr_type(av.Ty) != 0: return av # already a pointer: pass through
|
||||
if mode == 1:
|
||||
if node is None: return av
|
||||
nk: int = node.kind()
|
||||
if nk == ast.ASTKind.Name or nk == ast.ASTKind.Attribute or nk == ast.ASTKind.Subscript:
|
||||
ao_node: ast.AST* = ast.UnaryOp(pool, ast.OpKind.AddrOf, node)
|
||||
if ao_node is None: return av
|
||||
return self.Gen.ExprGen.translate_value(builder, pool, mod, ao_node, None, 0, trans, None, None)
|
||||
self.Gen.TypeGenErr.fatal_error(node, "value-reference parameter '&T' needs an lvalue argument (variable / field / element) or an existing pointer; a literal or expression result has no address (use '&temp T' to pass a temporary copy)", "CastError")
|
||||
return av
|
||||
if mode == 2:
|
||||
slot: mlirlite.Value* = mlirlite.build_alloca_at_entry(builder, av.Ty)
|
||||
if slot is None: return av
|
||||
mlirlite.build_store(builder, av, slot)
|
||||
return slot
|
||||
return av
|
||||
|
||||
def _lookup_module_sha1(self, mod_name: str) -> str:
|
||||
"""Look up the SHA1 for a module name via StubMerger.LookupSha1ByModName (exact match + package fallback); None if absent."""
|
||||
return StubMergerStore.LookupSha1ByModName(mod_name)
|
||||
|
||||
@@ -985,7 +985,9 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
|
||||
if arg_node_k is not None:
|
||||
# Fetch the param type first (char literal -> str param needs a materialized string).
|
||||
pty_k0: mlirlite.LLVMType* = mlirlite.param_get_ty(cur_param_k)
|
||||
arg_val_k = self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node_k, None, 0, trans, pty_k0)
|
||||
ao_name_k: str = None
|
||||
if found_func is not None: ao_name_k = mlirlite.function_get_name(found_func)
|
||||
arg_val_k = self.Gen.CallGenBase.translate_call_arg(builder, pool, mod, trans, ao_name_k, pi_k, arg_node_k, pty_k0, None)
|
||||
if arg_val_k is None:
|
||||
if arg_node_k is not None:
|
||||
# (a) The arg node exists but translated to None: fail fast;
|
||||
@@ -1026,7 +1028,10 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
|
||||
pp = mlirlite.param_get_next(pp)
|
||||
pi += 1
|
||||
if pp is not None: pty = mlirlite.param_get_ty(pp)
|
||||
arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node, None, 0, trans, pty)
|
||||
# Value-reference params (&T / &temp T): auto-address (self is param 0, arg ai -> param ai+1).
|
||||
ao_name_p: str = None
|
||||
if found_func is not None: ao_name_p = mlirlite.function_get_name(found_func)
|
||||
arg_val: mlirlite.Value* = self.Gen.CallGenBase.translate_call_arg(builder, pool, mod, trans, ao_name_p, int(ai) + 1, arg_node, pty, None)
|
||||
if arg_val is None:
|
||||
# (a) The arg expression translated to None: fail fast, never continue;
|
||||
# skipping would reduce arg_count_eff and get zero-filled later.
|
||||
|
||||
@@ -176,7 +176,8 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
|
||||
fpty: mlirlite.LLVMType* = None
|
||||
if fp is not None: fpty = mlirlite.param_get_ty(fp)
|
||||
if arg is not None:
|
||||
av: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg, None, 0, trans, fpty, "param")
|
||||
# Value-reference params (&T / &temp T): auto-address the argument (specialized callee).
|
||||
av: mlirlite.Value* = self.Gen.CallGenBase.translate_call_arg(builder, pool, mod, trans, spec_name, int(ai), arg, fpty, "param")
|
||||
if av is not None:
|
||||
if fpty is not None: av = self.Gen.ExprGenBase.coerce_to_type(builder, av, fpty, arg, trans)
|
||||
av.Next = None
|
||||
@@ -226,6 +227,15 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
|
||||
# intercept before the c.* attribute branch, since the bare cl.func is a Name.
|
||||
if func_name is not None and cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
|
||||
bare_nm: ast.Name* = ast.Name*(cl.func)
|
||||
if bare_nm.id is not None and string.strcmp(bare_nm.id, "defined") == 0:
|
||||
# defined(NAME) is a compile-time predicate valid only inside a cif/celif condition;
|
||||
# it is evaluated there and never lowered as a runtime call.
|
||||
self.Gen.TypeGenErr.fatal_error(cl, "defined(NAME) is only valid inside a cif/celif condition; it cannot be used in a normal expression", "NameError")
|
||||
return None
|
||||
if bare_nm.id is not None and (string.strcmp(bare_nm.id, "CIf") == 0 or string.strcmp(bare_nm.id, "CIfdef") == 0 or string.strcmp(bare_nm.id, "CIfndef") == 0):
|
||||
# removed spellings (also covered for the bare-name form of c.CIf/c.CIfdef/c.CIfndef)
|
||||
self.Gen.TypeGenErr.fatal_error(cl, "c.CIf/c.CIfdef/c.CIfndef were removed; use the cif/celif/celse statements and the defined(NAME) predicate instead (e.g. `cif defined(NAME):` ... `celse:` ...)", "NameError")
|
||||
return None
|
||||
if bare_nm.id is not None and string.strcmp(bare_nm.id, "__asm__") == 0:
|
||||
# translate_c_asm returns None for void output: set the hit flag so translate_call
|
||||
# does not misread it as a miss and fall through to the external function path.
|
||||
@@ -268,25 +278,11 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
|
||||
elif string.strcmp(func_name, "LInp") == 0 or string.strcmp(func_name, "LOut") == 0:
|
||||
# c.LInp/c.LOut standalone calls return 0 (only meaningful in c.LLVMIR f-strings)
|
||||
return mlirlite.ConstInt(pool, mlirlite.Int32(pool), 0, "0")
|
||||
elif string.strcmp(func_name, "CIf") == 0:
|
||||
# Runtime c.CIf(expr) fallback: compare expr against 0 to get i1
|
||||
# (mirrors TPC HandlesExprCall.py L2434-2444).
|
||||
cif_args: list[ast.AST*]* = cl.args
|
||||
if cif_args is not None and len(cif_args) >= 1:
|
||||
cif_arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cif_args.get(0), funcs_ptr, func_count, trans)
|
||||
if cif_arg_val is not None:
|
||||
cif_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(cif_arg_val.Ty)
|
||||
if cif_bits == 1: return cif_arg_val
|
||||
if self.Gen.ExprGenBase.is_ptr_type(cif_arg_val.Ty) != 0:
|
||||
cif_null: mlirlite.Value* = mlirlite.ConstNull(pool, cif_arg_val.Ty, "null")
|
||||
return mlirlite.build_icmp(builder, mlirlite.ICMP_NE, cif_arg_val, cif_null)
|
||||
cif_zero: mlirlite.Value* = mlirlite.const_int32(pool, 0)
|
||||
return mlirlite.build_icmp(builder, mlirlite.ICMP_NE, cif_arg_val, cif_zero)
|
||||
return mlirlite.ConstInt(pool, mlirlite.Int1(pool), 0, "0")
|
||||
elif string.strcmp(func_name, "CIfdef") == 0 or string.strcmp(func_name, "CIfndef") == 0:
|
||||
# Runtime c.CIfdef/c.CIfndef fallback: always true
|
||||
# (mirrors TPC HandlesExprCall.py L2445-2446).
|
||||
return mlirlite.ConstInt(pool, mlirlite.Int1(pool), 1, "1")
|
||||
elif string.strcmp(func_name, "CIf") == 0 or string.strcmp(func_name, "CIfdef") == 0 or string.strcmp(func_name, "CIfndef") == 0:
|
||||
# c.CIf/c.CIfdef/c.CIfndef were removed: report the replacement spelling.
|
||||
# (There is no runtime fallback: compile-time conditions use cif/celif/celse.)
|
||||
self.Gen.TypeGenErr.fatal_error(cl, "c.CIf/c.CIfdef/c.CIfndef were removed; use the cif/celif/celse statements and the defined(NAME) predicate instead (e.g. `cif defined(NAME):` ... `celse:` ...)", "NameError")
|
||||
return None
|
||||
elif is_coro_mod != 0 and string.strcmp(func_name, "coro_run") == 0:
|
||||
# coro.coro_run(frame): inline translation (resume loop until done, returns the ret slot).
|
||||
# Note: translate_coro_* take (builder, pool, mod, cl, trans), unlike the c.* branches.
|
||||
|
||||
@@ -208,12 +208,18 @@ novirtual class CallGenLib(CallGenBase.CallGenBase):
|
||||
arg_idx: int = idx - self_off
|
||||
arg_node: ast.AST* = None
|
||||
if cargs is not None and arg_idx >= 0 and arg_idx < len(cargs): arg_node = cargs.get(arg_idx)
|
||||
# Value-reference params (&T / &temp T): auto-address the argument (cross-module callee).
|
||||
# The mode comes from the definition-side vpi annotation segment; if the arg is not a
|
||||
# pointer it is addressed (ref: lvalue) or copied into a temp slot (temp).
|
||||
mode_cm: int = VpiReader._vpi_param_annot_seg_mode(prm_annots, idx)
|
||||
if mode_cm != 0 and trans is not None and trans.Module is not None:
|
||||
node_v = self.Gen.CallGenBase._apply_auto_addr_arg(builder, pool, trans.Module, trans, mode_cm, arg_node, node_v)
|
||||
# "str / bytes are not generic pointer wildcards": void* / t.CPtr / rawptr may
|
||||
# convert to any pointer, but str/bytes may not, even though both are i8* at LLVM level.
|
||||
if self.Gen.ExprGenBase._str_rejects_ptr_kind(cur) != 0:
|
||||
if VpiReader._vpi_param_annot_seg_is(sha1_buf, cls_nm, meth_nm, idx, "str") != 0 or VpiReader._vpi_param_annot_seg_is(sha1_buf, cls_nm, meth_nm, idx, "bytes") != 0:
|
||||
self.Gen.ExprGenBase.fail_if_str_annot_agg_ptr(cur, 1, arg_node)
|
||||
node_v = self._coerce_arg_value_to_ty(pool, builder, cur, tgt_ty, arg_node, trans, tpl_src)
|
||||
node_v = self._coerce_arg_value_to_ty(pool, builder, node_v, tgt_ty, arg_node, trans, tpl_src)
|
||||
if new_head is None: new_head = node_v
|
||||
else: new_tail.Next = node_v
|
||||
new_tail = node_v
|
||||
|
||||
@@ -758,7 +758,10 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
fp = mlirlite.param_get_next(fp)
|
||||
fpi += 1
|
||||
if fp is not None: fpty = mlirlite.param_get_ty(fp)
|
||||
arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg, None, 0, trans, fpty, "param")
|
||||
# Value-reference params (&T / &temp T): auto-address the argument (same-module callee).
|
||||
ao_name: str = None
|
||||
if found_func is not None: ao_name = mlirlite.function_get_name(found_func)
|
||||
arg_val: mlirlite.Value* = self.Gen.CallGenBase.translate_call_arg(builder, pool, mod, trans, ao_name, int(ai), arg, fpty, "param")
|
||||
if arg_val is not None:
|
||||
# Arg promotion: when found_func is None (stub not injected), infer the expected type from
|
||||
# the external signature and promote literals (e.g. i32->i64) to avoid declare/call mismatches.
|
||||
|
||||
@@ -272,6 +272,8 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
|
||||
continue
|
||||
|
||||
# Resolve the field type
|
||||
# Value-reference markers are parameter-only: a struct field `x: &T` fails fast.
|
||||
self.Gen.TypeGenResolve.reject_ref_marker_position(aa.annotation, trans._imported_modules, "a struct field")
|
||||
field_ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, aa.annotation, trans._imported_modules, trans._from_imports, trans, 0)
|
||||
# void is not a valid field type (only for `void*` and opaque typedefs); letting
|
||||
# it through builds an illegal layout, so fail-fast at compile time here.
|
||||
|
||||
@@ -114,7 +114,8 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
|
||||
at: ast.Attribute* = ast.Attribute*(node)
|
||||
if at.attr is not None:
|
||||
if string.strcmp(at.attr, "CPtr") == 0: return 1
|
||||
if string.strcmp(at.attr, "CNeedPtr") == 0: return 1
|
||||
if string.strcmp(at.attr, "CRef") == 0: return 1
|
||||
if string.strcmp(at.attr, "CTempRef") == 0: return 1
|
||||
if string.strcmp(at.attr, "CAutoPtr") == 0: return 1
|
||||
if string.strcmp(at.attr, "rawptr") == 0: return 1
|
||||
if string.strcmp(at.attr, "str") == 0: return 1
|
||||
@@ -124,7 +125,8 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
|
||||
nm: ast.Name* = ast.Name*(node)
|
||||
if nm.id is not None:
|
||||
if string.strcmp(nm.id, "CPtr") == 0: return 1
|
||||
if string.strcmp(nm.id, "CNeedPtr") == 0: return 1
|
||||
if string.strcmp(nm.id, "CRef") == 0: return 1
|
||||
if string.strcmp(nm.id, "CTempRef") == 0: return 1
|
||||
if string.strcmp(nm.id, "CAutoPtr") == 0: return 1
|
||||
if string.strcmp(nm.id, "rawptr") == 0: return 1
|
||||
if string.strcmp(nm.id, "str") == 0: return 1
|
||||
@@ -265,8 +267,8 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
|
||||
tgt: ast.Name* = ast.Name*(aa.target)
|
||||
if tgt.id is None: continue
|
||||
|
||||
# [enum spec fail-fast] Members may not be pointer types: t.CPtr / CNeedPtr /
|
||||
# CAutoPtr / rawptr[...] / str / bytes and unions carrying a pointer. Checked before type resolution.
|
||||
# [enum spec fail-fast] Members may not be pointer types: t.CPtr / CRef /
|
||||
# CTempRef / CAutoPtr / rawptr[...] / str / bytes and unions carrying a pointer. Checked before type resolution.
|
||||
if self._enum_member_annotation_is_ptr(aa.annotation) == 1:
|
||||
self.Gen.TypeGenErr.fatal_error(aa, f"enum member '{tgt.id}' cannot be a pointer type (enum values only support integer member types)", "TypeError")
|
||||
return 0 # unreachable (fatal_error already exited)
|
||||
|
||||
@@ -376,7 +376,10 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
|
||||
tpl_file: str = self._find_generic_func_template_file(template_key)
|
||||
if tpl_file is not None: self.Gen.TypeGenResolve.set_current_file(tpl_file)
|
||||
ret_ty: mlirlite.LLVMType* = None
|
||||
if spec_fd.returns is not None: ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, spec_fd.returns, trans._imported_modules, trans._from_imports, trans, 0)
|
||||
if spec_fd.returns is not None:
|
||||
# Value-reference markers are parameter-only: `-> &T` fails fast on a generic too.
|
||||
self.Gen.TypeGenResolve.reject_ref_marker_position(spec_fd.returns, trans._imported_modules, "a function return type")
|
||||
ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, spec_fd.returns, trans._imported_modules, trans._from_imports, trans, 0)
|
||||
if ret_ty is None: ret_ty = i32_ty
|
||||
func: mlirlite.Function* = mlirlite.create_declare(pool, mod, spec_name, ret_ty)
|
||||
if func is not None and spec_fd.args is not None and spec_fd.args.kind() == ast.ASTKind.Arguments:
|
||||
@@ -404,6 +407,25 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
|
||||
pname: str = pool.alloc(32)
|
||||
if pname is not None: viperlib.snprintf(pname, 32, "%%%s", arg.arg)
|
||||
mlirlite.add_param(pool, func, param_ty, pname)
|
||||
# Register value-reference (&T / &temp T) parameter modes for the specialization.
|
||||
mode_buf_g: str = pool.alloc(an + 1)
|
||||
if mode_buf_g is not None:
|
||||
mode_cnt_g: int = 0
|
||||
has_ref_g: int = 0
|
||||
for ai_g in range(an):
|
||||
pa_g: ast.AST* = alist.get(ai_g)
|
||||
if pa_g is None or pa_g.kind() != ast.ASTKind.Arg: continue
|
||||
arg_g: ast.Arg* = ast.Arg*(pa_g)
|
||||
if arg_g.arg is None: continue
|
||||
mm_g: int = 0
|
||||
if arg_g.annotation is not None: mm_g = self.Gen.TypeGenResolve.ref_param_mode(arg_g.annotation, trans._imported_modules)
|
||||
if mm_g == 1: mode_buf_g[mode_cnt_g] = char(49)
|
||||
elif mm_g == 2: mode_buf_g[mode_cnt_g] = char(50)
|
||||
else: mode_buf_g[mode_cnt_g] = char(48)
|
||||
if mm_g != 0: has_ref_g = 1
|
||||
mode_cnt_g += 1
|
||||
mode_buf_g[mode_cnt_g] = char(0)
|
||||
if has_ref_g != 0: self.Gen.CallGenBase._register_auto_addr_modes(spec_name, mode_buf_g, mode_cnt_g)
|
||||
if tpl_file is not None: self.Gen.TypeGenResolve.set_current_file(saved_cur)
|
||||
trans.ModuleSha1 = saved_ms
|
||||
trans._imported_modules = saved_imp
|
||||
|
||||
@@ -470,7 +470,10 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
|
||||
|
||||
# Infer the return type.
|
||||
ret_ty: mlirlite.LLVMType* = None
|
||||
if fd.returns is not None: ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, fd.returns, imported_modules, from_imports, trans, 0)
|
||||
if fd.returns is not None:
|
||||
# Value-reference markers are parameter-only: `-> &T` fails fast (never a silent Ptr(T) return).
|
||||
self.Gen.TypeGenResolve.reject_ref_marker_position(fd.returns, imported_modules, "a method return type")
|
||||
ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, fd.returns, imported_modules, from_imports, trans, 0)
|
||||
if ret_ty is None and fd.returns is not None:
|
||||
# t.State / t.CExport / t.CExtern markers mean Void: such annotations carry no real return
|
||||
# type (written as "<original> | t.<marker>"); only the marker itself reaches here.
|
||||
@@ -583,6 +586,27 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
|
||||
pname: str = pool.alloc(32)
|
||||
if pname is not None: viperlib.snprintf(pname, 32, "%%%s", arg.arg)
|
||||
mlirlite.add_param(pool, func, param_ty, pname)
|
||||
# Register value-reference (&T / &temp T) parameter modes (index 0 = self for non-static methods).
|
||||
mode_buf: str = pool.alloc(an + 1)
|
||||
if mode_buf is not None:
|
||||
mode_cnt: int = 0
|
||||
has_ref_m: int = 0
|
||||
if is_static_m == 0:
|
||||
mode_buf[mode_cnt] = char(48)
|
||||
mode_cnt += 1
|
||||
for ai_m in range(start_idx, an):
|
||||
arg_m: ast.Arg* = ast.Arg*(alist.get(ai_m))
|
||||
mm: int = 0
|
||||
if arg_m is not None and arg_m.annotation is not None:
|
||||
if self.Gen.TypeGenResolve._is_t_attr(arg_m.annotation, "CVoid", imported_modules) == 0:
|
||||
mm = self.Gen.TypeGenResolve.ref_param_mode(arg_m.annotation, imported_modules)
|
||||
if mm == 1: mode_buf[mode_cnt] = char(49)
|
||||
elif mm == 2: mode_buf[mode_cnt] = char(50)
|
||||
else: mode_buf[mode_cnt] = char(48)
|
||||
if mm != 0: has_ref_m = 1
|
||||
mode_cnt += 1
|
||||
mode_buf[mode_cnt] = char(0)
|
||||
if has_ref_m != 0: self.Gen.CallGenBase._register_auto_addr_modes(mangled_name, mode_buf, mode_cnt)
|
||||
|
||||
# OOP iter protocol: __next__ gets a hidden __stop_iter_flag__ (i1*), set to 1 by raise
|
||||
# StopIteration. It is not counted in md_param_count; the for loop passes it explicitly.
|
||||
|
||||
@@ -103,7 +103,8 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
|
||||
if string.strcmp(name, "CArray") == 0: return 1
|
||||
if string.strcmp(name, "CTypedef") == 0: return 1
|
||||
if string.strcmp(name, "CNoVTable") == 0: return 1
|
||||
if string.strcmp(name, "CNeedPtr") == 0: return 1
|
||||
if string.strcmp(name, "CRef") == 0: return 1
|
||||
if string.strcmp(name, "CTempRef") == 0: return 1
|
||||
if string.strcmp(name, "CAutoPtr") == 0: return 1
|
||||
return 0
|
||||
def _attr_module_path_str(self, pool: memhub.MemBuddy*, node: ast.AST*) -> str:
|
||||
|
||||
@@ -267,9 +267,13 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
|
||||
return ref_fi
|
||||
|
||||
# All lookup paths failed: fail-fast (NameError) instead of returning None and crashing
|
||||
# downstream. Exempt keywords/builtins (self/pool/None/_, CIf-like) whose absence is normal.
|
||||
# downstream. Exempt keywords/builtins (self/pool/None/_) whose absence is normal.
|
||||
if nm_id is None: return None
|
||||
if string.strcmp(nm_id, "self") == 0 or string.strcmp(nm_id, "pool") == 0 or string.strcmp(nm_id, "true") == 0 or string.strcmp(nm_id, "false") == 0 or string.strcmp(nm_id, "None") == 0 or string.strcmp(nm_id, "match") == 0 or string.strcmp(nm_id, "_") == 0 or string.strcmp(nm_id, "CIf") == 0 or string.strcmp(nm_id, "CIfdef") == 0 or string.strcmp(nm_id, "CIfndef") == 0:
|
||||
if string.strcmp(nm_id, "CIf") == 0 or string.strcmp(nm_id, "CIfdef") == 0 or string.strcmp(nm_id, "CIfndef") == 0:
|
||||
# The c.CIf/c.CIfdef/c.CIfndef spellings were removed; report the replacement.
|
||||
self.Gen.TypeGenErr.fatal_error(node, "c.CIf/c.CIfdef/c.CIfndef were removed; use the cif/celif/celse statements and the defined(NAME) predicate instead (e.g. `cif defined(NAME):` ... `celse:` ...)", "NameError")
|
||||
return None
|
||||
if string.strcmp(nm_id, "self") == 0 or string.strcmp(nm_id, "pool") == 0 or string.strcmp(nm_id, "true") == 0 or string.strcmp(nm_id, "false") == 0 or string.strcmp(nm_id, "None") == 0 or string.strcmp(nm_id, "match") == 0 or string.strcmp(nm_id, "_") == 0:
|
||||
return None
|
||||
# Diagnostics: report the generic context, since "undefined name 'T'" is often far from the
|
||||
# real cause (0 params = no context; params>0/args 0 = lost args; both>0 = name not a param).
|
||||
|
||||
@@ -21,6 +21,37 @@ import lib.core.Gen.DebugInfoGen as DebugInfoGen
|
||||
novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
"""Function declarations, translate_function_def, and decorator wrappers."""
|
||||
|
||||
# _register_func_auto_addr_modes - record which parameters of fd are value references (&T / &temp T).
|
||||
# Registered under the emitted symbol name so call sites can auto-address the argument.
|
||||
def _register_func_auto_addr_modes(self, trans: HT.Translator*, name: str, fd: ast.FunctionDef*) -> int:
|
||||
"""Register per-parameter value-reference modes for a compiled function definition."""
|
||||
if trans is None or name is None or fd is None or fd.args is None: return 0
|
||||
ags: ast.Arguments* = ast.Arguments*(fd.args)
|
||||
if ags.args is None: return 0
|
||||
alist: list[ast.AST*]* = ags.args
|
||||
an: sizet = len(alist)
|
||||
buf: bytes = stdlib.malloc(an + 1)
|
||||
if buf is None: return 0
|
||||
cnt: int = 0
|
||||
has_ref: int = 0
|
||||
imp: str = trans._imported_modules
|
||||
for i in range(an):
|
||||
arg: ast.Arg* = ast.Arg*(alist.get(i))
|
||||
if arg is None or arg.arg is None: continue
|
||||
# CVoid params are not emitted into the signature, so they must not occupy a mode slot.
|
||||
if arg.annotation is not None and self.Gen.TypeGenResolve._is_t_attr(arg.annotation, "CVoid", imp) != 0: continue
|
||||
m: int = 0
|
||||
if arg.annotation is not None: m = self.Gen.TypeGenResolve.ref_param_mode(arg.annotation, imp)
|
||||
if m == 1: buf[cnt] = char(49)
|
||||
elif m == 2: buf[cnt] = char(50)
|
||||
else: buf[cnt] = char(48)
|
||||
if m != 0: has_ref = 1
|
||||
cnt += 1
|
||||
buf[cnt] = char(0)
|
||||
if has_ref != 0: self.Gen.CallGenBase._register_auto_addr_modes(name, buf, cnt)
|
||||
stdlib.free(buf)
|
||||
return 1
|
||||
|
||||
# forward_declare_functions - pre-scan all top-level FunctionDefs and emit forward declares.
|
||||
# Solves same-module forward references; translate_function_def later reuses the declare via find_func_in_module.
|
||||
def forward_declare_functions(self, trans: HT.Translator*, tree: ast.AST*) -> int:
|
||||
@@ -77,6 +108,8 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
# Infer the return type
|
||||
ret_ty: mlirlite.LLVMType* = None
|
||||
if fd.returns is not None:
|
||||
# Value-reference markers are parameter-only: `-> &T` fails fast (never a silent Ptr(T) return).
|
||||
self.Gen.TypeGenResolve.reject_ref_marker_position(fd.returns, imported_modules, "a function return type")
|
||||
ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, fd.returns, imported_modules, from_imports, trans, 0)
|
||||
# A lone t.CExport return annotation is an export modifier with no real type -> Void
|
||||
# (CExport fell back to i32, forcing the tail check to return i32: mm.vp free root cause).
|
||||
@@ -122,6 +155,9 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
if has_extern != 0 or has_state != 0 or has_export != 0 or string.strcmp(fd.name, "main") == 0: mangled_name: str = fd.name
|
||||
else: mangled_name = self.Gen.FuncGenBase._mangle_func_name(trans, fd.name, 0)
|
||||
|
||||
# Record value-reference (&T / &temp T) parameter modes for call-site auto-address.
|
||||
self._register_func_auto_addr_modes(trans, mangled_name, fd)
|
||||
|
||||
# Skip if the function already exists (e.g. duplicate definition)
|
||||
existing: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, mangled_name)
|
||||
if existing is not None:
|
||||
@@ -242,6 +278,8 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
# Infer the return type: prefer the return annotation
|
||||
ret_ty: mlirlite.LLVMType* = None
|
||||
if fd.returns is not None:
|
||||
# Value-reference markers are parameter-only: `-> &T` fails fast (never a silent Ptr(T) return).
|
||||
self.Gen.TypeGenResolve.reject_ref_marker_position(fd.returns, imported_modules, "a function return type")
|
||||
ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, fd.returns, imported_modules, from_imports, trans, 0)
|
||||
# A lone t.CExport return annotation is an export modifier with no real type -> Void
|
||||
# (CExport fell back to i32, forcing return i32: mm.vp free root cause). Mixed types keep the resolved result.
|
||||
@@ -340,6 +378,9 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
if has_extern != 0 or has_state != 0 or has_export != 0 or string.strcmp(fd.name, "main") == 0: mangled_name: str = fd.name
|
||||
else: mangled_name: str = self.Gen.FuncGenBase._mangle_func_name(trans, fd.name, 0)
|
||||
|
||||
# Record value-reference (&T / &temp T) parameter modes for call-site auto-address.
|
||||
self._register_func_auto_addr_modes(trans, mangled_name, fd)
|
||||
|
||||
# Register the CExport function in the global table (for cross-module calls). t.CExport definitions use bare
|
||||
# names (@strlen); cross-module calls consult the table to use the bare name instead of @{sha1}.func.
|
||||
if (has_export != 0 or has_state != 0) and trans.ModuleSha1 is not None: self.Gen.CallGenBase.register_cexport_func(trans.ModuleSha1, fd.name)
|
||||
|
||||
@@ -42,11 +42,15 @@ novirtual class StmtGenCifMixin:
|
||||
# Cluster B: compile-time CIf macro eval (migrated from HandlesIf.vp)
|
||||
|
||||
def _is_cif_attr(self, attr: str) -> int:
|
||||
"""Check whether the attribute name is a CIf-family name (CIf/CIfdef/CIfndef)."""
|
||||
"""Check the internal cif marker attribute name produced by the parser (c.@CIf family).
|
||||
|
||||
The '@' prefix is internal-only spelling (a leading '@' cannot be lexed as an identifier), so a
|
||||
user-written c.CIf/c.CIfdef/c.CIfndef never matches here and is reported as removed instead.
|
||||
"""
|
||||
if attr is None: return 0
|
||||
if string.strcmp(attr, "CIf") == 0: return 1
|
||||
if string.strcmp(attr, "CIfdef") == 0: return 1
|
||||
if string.strcmp(attr, "CIfndef") == 0: return 1
|
||||
if string.strcmp(attr, "@CIf") == 0: return 1
|
||||
if string.strcmp(attr, "@CIfdef") == 0: return 1
|
||||
if string.strcmp(attr, "@CIfndef") == 0: return 1
|
||||
return 0
|
||||
|
||||
def _is_cif_call(self, node: ast.AST*) -> int:
|
||||
@@ -151,7 +155,8 @@ novirtual class StmtGenCifMixin:
|
||||
k: int = node.kind()
|
||||
if k == ast.ASTKind.Constant:
|
||||
cn: ast.Constant* = ast.Constant*(node)
|
||||
if cn.const_kind == ast.CONST_INT:
|
||||
# integer and boolean literals are compile-time constants (True/False carry 1/0)
|
||||
if cn.const_kind == ast.CONST_INT or cn.const_kind == ast.CONST_BOOL:
|
||||
self.CifEvalOk = 1
|
||||
return cn.int_val
|
||||
self.CifEvalOk = 0
|
||||
@@ -272,13 +277,58 @@ novirtual class StmtGenCifMixin:
|
||||
if pointee is not None and self.Gen.ExprGenBase.is_ptr_type(pointee) != 0: return 1
|
||||
return 0
|
||||
|
||||
def _cif_eval_defined(self, pool: memhub.MemBuddy*, trans: HT.Translator*, cl: ast.Call*) -> int:
|
||||
"""Evaluate the defined(NAME) compile-time predicate (1 = macro defined, 0 = not).
|
||||
|
||||
defined() is always evaluable: an unknown name yields 0. A wrong argument shape is fail-fast.
|
||||
The macro name resolves through the same CDefine / cross-module / platform-macro paths as a
|
||||
bare name in a cif condition.
|
||||
"""
|
||||
cargs: list[ast.AST*]* = cl.args
|
||||
if cargs is None or len(cargs) != 1:
|
||||
self.Gen.TypeGenErr.fatal_error(cl, "defined() takes exactly one macro name, e.g. defined(NAME)", "SyntaxError")
|
||||
return 0
|
||||
arg0: ast.AST* = cargs.get(0)
|
||||
# Dotted macro name (module.NAME, e.g. defined(platmacro.IS_WINDOWS)): resolve it the same
|
||||
# way a bare macro name resolves; a resolvable value means the macro exists.
|
||||
if arg0 is not None and arg0.kind() == ast.ASTKind.Attribute:
|
||||
self._cif_eval_node(pool, trans, arg0)
|
||||
dotted_defined: int = self.CifEvalOk
|
||||
self.CifEvalOk = 1
|
||||
return 1 if dotted_defined != 0 else 0
|
||||
name_arg: str = None
|
||||
if arg0 is not None and arg0.kind() == ast.ASTKind.Name:
|
||||
nm0: ast.Name* = ast.Name*(arg0)
|
||||
name_arg = nm0.id
|
||||
elif arg0 is not None and arg0.kind() == ast.ASTKind.Constant:
|
||||
cn0: ast.Constant* = ast.Constant*(arg0)
|
||||
if cn0.const_kind == ast.CONST_STR: name_arg = cn0.str_val
|
||||
if name_arg is None:
|
||||
self.Gen.TypeGenErr.fatal_error(cl, "defined() requires a bare macro name, e.g. defined(NAME)", "SyntaxError")
|
||||
return 0
|
||||
self._cif_name_value(pool, trans, name_arg)
|
||||
defined: int = self.CifEvalOk
|
||||
# the defined() test itself is evaluable even when the macro is absent (defined = 0)
|
||||
self.CifEvalOk = 1
|
||||
return 1 if defined != 0 else 0
|
||||
|
||||
def _cif_eval_call(self, pool: memhub.MemBuddy*, trans: HT.Translator*, node: ast.AST*) -> int:
|
||||
"""Evaluate the ctraits.isptr(x) compile-time call, returning 1/0; unknown calls return 0."""
|
||||
"""Evaluate a compile-time call inside a cif/celif condition: defined(NAME) or ctraits.isptr(x).
|
||||
|
||||
Unknown / runtime calls return 0 with CifEvalOk=0, so the caller reports "not a compile-time
|
||||
constant" instead of emitting a runtime branch.
|
||||
"""
|
||||
self.CifEvalOk = 0
|
||||
if node is None or trans is None: return 0
|
||||
cl: ast.Call* = ast.Call*(node)
|
||||
if cl is None or cl.func is None: return 0
|
||||
fk: int = cl.func.kind()
|
||||
# defined(NAME): compile-time macro predicate. Valid only inside a cif/celif condition; any
|
||||
# other use is reported by the call generator (bare `defined(...)` name).
|
||||
if fk == ast.ASTKind.Name:
|
||||
dn: ast.Name* = ast.Name*(cl.func)
|
||||
if dn.id is not None and string.strcmp(dn.id, "defined") == 0:
|
||||
return self._cif_eval_defined(pool, trans, cl)
|
||||
if fk != ast.ASTKind.Attribute: return 0
|
||||
at: ast.Attribute* = ast.Attribute*(cl.func)
|
||||
if at.value is None or at.attr is None: return 0
|
||||
@@ -306,7 +356,11 @@ novirtual class StmtGenCifMixin:
|
||||
return r
|
||||
|
||||
def _evaluate_cif_test(self, pool: memhub.MemBuddy*, trans: HT.Translator*, test: ast.AST*) -> int:
|
||||
"""Evaluate a CIf-family call, returning the condition and setting CifEvalOk on success."""
|
||||
"""Evaluate an internal cif marker call (c.@CIf family), returning the condition.
|
||||
|
||||
Sets CifEvalOk=1 when the condition is a compile-time constant, 0 otherwise (the caller then
|
||||
fail-fasts: cif/celif conditions must be compile-time constants, no runtime fallback).
|
||||
"""
|
||||
self.CifEvalOk = 0
|
||||
cl: ast.Call* = ast.Call*(test)
|
||||
if cl is None or cl.func is None: return 0
|
||||
@@ -322,11 +376,11 @@ novirtual class StmtGenCifMixin:
|
||||
cargs: list[ast.AST*]* = cl.args
|
||||
if cargs is None or len(cargs) < 1: return 0
|
||||
arg0: ast.AST* = cargs.get(0)
|
||||
if string.strcmp(kind, "CIf") == 0:
|
||||
if string.strcmp(kind, "@CIf") == 0:
|
||||
v: int = self._cif_eval_node(pool, trans, arg0)
|
||||
if self.CifEvalOk == 0: return 0
|
||||
return 1 if v != 0 else 0
|
||||
# CIfdef / CIfndef: test whether the macro name is defined
|
||||
# @CIfdef / @CIfndef: test whether the macro name is defined
|
||||
defined: int = 0
|
||||
if arg0 is not None and arg0.kind() == ast.ASTKind.Name:
|
||||
nm0: ast.Name* = ast.Name*(arg0)
|
||||
@@ -346,10 +400,24 @@ novirtual class StmtGenCifMixin:
|
||||
else: return 0
|
||||
# the defined-check itself succeeded (an undefined name is not an error; defined=0)
|
||||
self.CifEvalOk = 1
|
||||
if string.strcmp(kind, "CIfdef") == 0: return 1 if defined != 0 else 0
|
||||
# CIfndef
|
||||
if string.strcmp(kind, "@CIfdef") == 0: return 1 if defined != 0 else 0
|
||||
# @CIfndef
|
||||
return 0 if defined != 0 else 1
|
||||
|
||||
def _prune_cif_test(self, pool: memhub.MemBuddy*, trans: HT.Translator*, test: ast.AST*) -> int:
|
||||
"""Return the compile-time value (1/0) of a cif/celif marker condition.
|
||||
|
||||
Fail-fast when the condition is not a compile-time constant: cif/celif never fall back to a
|
||||
runtime branch. Shared by translate_if and the coroutine If handler.
|
||||
"""
|
||||
v: int = self._evaluate_cif_test(pool, trans, test)
|
||||
if self.CifEvalOk == 0:
|
||||
self.Gen.TypeGenErr.fatal_error(test,
|
||||
f"the cif/celif condition is not a compile-time constant (line {test.lineno}):"
|
||||
" cif/celif accept only CDefine constants, integer literals, defined(NAME), and their"
|
||||
" constant arithmetic/comparison/boolean/bitwise combinations", "TypeError")
|
||||
return v
|
||||
|
||||
# translate_return - translate Return (migrated from HandlesReturn.ReturnHandle): emits ret,
|
||||
# a bare return emits the return-type default zero (void→ret void, ptr→null, float→0.0).
|
||||
def translate_return(self, trans: HT.Translator*, node: ast.AST*) -> int:
|
||||
@@ -539,7 +607,8 @@ novirtual class StmtGenCifMixin:
|
||||
return 0
|
||||
|
||||
# translate_if - translate if/elif/else (migrated from HandlesIf.IfHandle.Handle):
|
||||
# compile-time c.CIf/CIfdef/CIfndef branch-prunes when evaluable, else emits a runtime cond br.
|
||||
# a cif/celif condition (internal c.@CIf marker) branch-prunes at compile time; a condition that
|
||||
# is not a compile-time constant is a fail-fast error (no runtime fallback).
|
||||
def translate_if(self, trans: HT.Translator*, node: ast.AST*) -> int:
|
||||
"""Translate an if/elif/else statement."""
|
||||
if node is None: return 0
|
||||
@@ -552,34 +621,34 @@ novirtual class StmtGenCifMixin:
|
||||
|
||||
if_node: ast.If* = ast.If*(node)
|
||||
|
||||
# 0. compile-time macro condition: if c.CIf/CIfdef/CIfndef - if evaluable, compile only
|
||||
# the taken branch (elif recurses); if not, fall back to runtime translation.
|
||||
# 0. compile-time condition (cif/celif desugared to If(test=c.@CIf(EXPR))): evaluate the
|
||||
# constant expression and compile only the taken branch (celif recurses via orelse). A
|
||||
# condition that is not a compile-time constant is fail-fast; there is no runtime fallback.
|
||||
if self._is_cif_call(if_node.test) == 1:
|
||||
cif_val: int = self._evaluate_cif_test(pool, trans, if_node.test)
|
||||
if self.CifEvalOk != 0:
|
||||
if cif_val != 0:
|
||||
body_cif: list[ast.AST*]* = if_node.children
|
||||
if body_cif is not None:
|
||||
bc_cif: sizet = len(body_cif)
|
||||
bi_cif: sizet
|
||||
for bi_cif in range(bc_cif):
|
||||
stmt_cif: ast.AST* = body_cif.get(bi_cif)
|
||||
if stmt_cif is not None: self.Gen.StmtGen.translate_stmt(trans, stmt_cif)
|
||||
else:
|
||||
orelse_cif: list[ast.AST*]* = if_node.orelse
|
||||
if orelse_cif is not None and len(orelse_cif) == 1:
|
||||
sub_cif: ast.AST* = orelse_cif.get(0)
|
||||
if sub_cif is not None and sub_cif.kind() == ast.ASTKind.If:
|
||||
# elif: recurse (the sub-If test again goes through CIf or runtime)
|
||||
self.translate_if(trans, sub_cif)
|
||||
elif sub_cif is not None: self.Gen.StmtGen.translate_stmt(trans, sub_cif)
|
||||
elif orelse_cif is not None:
|
||||
oc_cif: sizet = len(orelse_cif)
|
||||
oi_cif: sizet
|
||||
for oi_cif in range(oc_cif):
|
||||
stmt_cif2: ast.AST* = orelse_cif.get(oi_cif)
|
||||
if stmt_cif2 is not None: self.Gen.StmtGen.translate_stmt(trans, stmt_cif2)
|
||||
return 0
|
||||
cif_val: int = self._prune_cif_test(pool, trans, if_node.test)
|
||||
if cif_val != 0:
|
||||
body_cif: list[ast.AST*]* = if_node.children
|
||||
if body_cif is not None:
|
||||
bc_cif: sizet = len(body_cif)
|
||||
bi_cif: sizet
|
||||
for bi_cif in range(bc_cif):
|
||||
stmt_cif: ast.AST* = body_cif.get(bi_cif)
|
||||
if stmt_cif is not None: self.Gen.StmtGen.translate_stmt(trans, stmt_cif)
|
||||
else:
|
||||
orelse_cif: list[ast.AST*]* = if_node.orelse
|
||||
if orelse_cif is not None and len(orelse_cif) == 1:
|
||||
sub_cif: ast.AST* = orelse_cif.get(0)
|
||||
if sub_cif is not None and sub_cif.kind() == ast.ASTKind.If:
|
||||
# celif: recurse (the sub-If test is another c.@CIf marker)
|
||||
self.translate_if(trans, sub_cif)
|
||||
elif sub_cif is not None: self.Gen.StmtGen.translate_stmt(trans, sub_cif)
|
||||
elif orelse_cif is not None:
|
||||
oc_cif: sizet = len(orelse_cif)
|
||||
oi_cif: sizet
|
||||
for oi_cif in range(oc_cif):
|
||||
stmt_cif2: ast.AST* = orelse_cif.get(oi_cif)
|
||||
if stmt_cif2 is not None: self.Gen.StmtGen.translate_stmt(trans, stmt_cif2)
|
||||
return 0
|
||||
|
||||
# 1. evaluate the condition expression
|
||||
cond_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, if_node.test, trans._funcs, trans._func_count, trans)
|
||||
|
||||
@@ -68,6 +68,7 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
|
||||
elif k == ast.ASTKind.Match: return self.Gen.MatchGen.translate_match(trans, node)
|
||||
elif k == ast.ASTKind.Delete: return self.translate_delete(trans, node)
|
||||
elif k == ast.ASTKind.Raise: return self.translate_raise(trans, node)
|
||||
elif k == ast.ASTKind.Assert: return self.translate_assert(trans, node)
|
||||
elif k == ast.ASTKind.Try: return self.translate_try(trans, node)
|
||||
elif k == ast.ASTKind.With: return self.translate_with(trans, node)
|
||||
# unknown statement type: fail-fast to expose an upstream parse problem
|
||||
@@ -136,6 +137,49 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
|
||||
mlirlite.build_call(builder, dl_fn_name, dl_self, 2, dl_ret, 0)
|
||||
return 0
|
||||
|
||||
# translate_assert - translate `assert cond[, msg]` into a single testcheck.check(...) call:
|
||||
# a true condition is silent (ok_msg=None), a false one reports FAIL via the standard path
|
||||
# (no printf/exit/exception; the checker prints nothing when the condition holds).
|
||||
def translate_assert(self, trans: HT.Translator*, node: ast.AST*) -> int:
|
||||
"""Translate an assert statement; returns 0"""
|
||||
if trans is None or node is None: return 0
|
||||
asn: ast.Assert* = ast.Assert*(node)
|
||||
if asn is None or asn.test is None: return 0
|
||||
pool: memhub.MemBuddy* = trans.Pool
|
||||
builder: mlirlite.IRBuilder* = trans._cur_builder
|
||||
mod: mlirlite.LLVMModule* = trans.Module
|
||||
if pool is None or builder is None or mod is None: return 0
|
||||
|
||||
# Resolve the message argument: omitted -> "assertion failed". A non-string literal is
|
||||
# rejected here (clear report); a non-string expression is caught by the call's int->ptr rule.
|
||||
msg_node: ast.AST* = asn.msg
|
||||
if msg_node is None:
|
||||
msg_node = ast.Constant(pool, ast.CONST_STR, 0, 0.0, "assertion failed", node.lineno, node.col_offset)
|
||||
if msg_node is None: return 0
|
||||
elif msg_node.kind() == ast.ASTKind.Constant:
|
||||
msg_c: ast.Constant* = ast.Constant*(msg_node)
|
||||
if msg_c is None or msg_c.const_kind != ast.CONST_STR:
|
||||
self.Gen.TypeGenErr.fatal_error(msg_node, "assert message must be a string (str)", "TypeError")
|
||||
return 0
|
||||
|
||||
# Synthesize `testcheck.check(<test>, None, <msg>)` and reuse the normal call translation
|
||||
# (cross-module name resolution + argument coercion stay in one place).
|
||||
none_node: ast.AST* = ast.Constant(pool, ast.CONST_NONE, 0, 0.0, None, node.lineno, node.col_offset)
|
||||
if none_node is None: return 0
|
||||
args_list: list[ast.AST*]* = list[ast.AST*](pool, 8)
|
||||
if args_list is None: return 0
|
||||
args_list.append(asn.test)
|
||||
args_list.append(none_node)
|
||||
args_list.append(msg_node)
|
||||
mod_name_node: ast.AST* = ast.Name(pool, "testcheck", ast.ASTCtx.Load)
|
||||
if mod_name_node is None: return 0
|
||||
check_attr: ast.AST* = ast.Attribute(pool, mod_name_node, "check", ast.ASTCtx.Load)
|
||||
if check_attr is None: return 0
|
||||
call_node: ast.AST* = ast.Call(pool, check_attr, args_list, None)
|
||||
if call_node is None: return 0
|
||||
self.Gen.ExprGen.translate_value(builder, pool, mod, call_node, trans._funcs, trans._func_count, trans)
|
||||
return 0
|
||||
|
||||
# translate_try - try/except translation. Exception = (i32 code, i8* msg);
|
||||
# except X statically expands X and all descendant exception codes.
|
||||
def translate_try(self, trans: HT.Translator*, node: ast.AST*) -> int:
|
||||
|
||||
@@ -501,6 +501,9 @@ novirtual class Translator:
|
||||
# Mirror TPC _inject_auto_imports: detect list/dict container usage (bare annotations,
|
||||
# constructors, subscripts) and mark the container classes visible so bare names resolve.
|
||||
self._inject_auto_container_visibility(pool, tree)
|
||||
# A module using `assert` behaves as if it had `import testcheck`: inject the module
|
||||
# before dependency-vpi loading so testcheck.check resolves across modules.
|
||||
self._inject_auto_assert_import(pool, tree)
|
||||
# CTypedef declarations are registered by Phase1 text scanning (AST string tokens are unstable).
|
||||
# Cross-module .vpi injection loads dep .vpi declares when full-translating or during App pre-registration.
|
||||
if self._imported_modules is not None:
|
||||
@@ -689,6 +692,58 @@ novirtual class Translator:
|
||||
stdlib.free(need_list_box)
|
||||
stdlib.free(need_dict_box)
|
||||
|
||||
# Auto assert-import injection: a module that uses `assert` is equivalent to having
|
||||
# `import testcheck` at the top, since assert lowers to testcheck.check(...). Injected
|
||||
# after real imports (so de-duplication works) and before dependency-vpi loading.
|
||||
def _inject_auto_assert_import(self, pool: memhub.MemBuddy*, tree: ast.AST*):
|
||||
"""Ensure `testcheck` is imported when the tree contains an assert statement."""
|
||||
if tree is None: return
|
||||
if self._tree_has_assert(tree, 0) == 0: return
|
||||
if self._imported_modules is not None and GenApi.is_module_imported(self._imported_modules, "testcheck") != 0: return
|
||||
# Break cycles: skip when testcheck already depends on the current module (mirrors the
|
||||
# _list/_dict guard; the residual case is cleaned up by Phase1 _BreakContainerCyclesInDeps).
|
||||
if self._ContainerInjectionCreatesCycle("testcheck") != 0: return
|
||||
self._imported_modules = GenApi.add_imported_module(pool, self._imported_modules, "testcheck")
|
||||
|
||||
# _tree_has_assert - scan statement blocks for an `assert` node (depth-guarded).
|
||||
def _tree_has_assert(self, node: ast.AST*, depth: int) -> int:
|
||||
"""Return 1 when the subtree contains an assert statement, else 0."""
|
||||
if node is None: return 0
|
||||
# Deep-recursion guard: Translator has no Gen facade, so report through stdio/stdlib
|
||||
# (same idiom as Generator.__init__'s allocation failures).
|
||||
if depth > 512:
|
||||
stdio.printf("FATAL: assert-scan recursion too deep (AST cycle or nesting over 512 levels)\n")
|
||||
stdlib.exit(1)
|
||||
k_a: int = node.kind()
|
||||
if k_a == ast.ASTKind.Assert: return 1
|
||||
# Try: handlers/orelse/finalbody are separate lists, not part of children.
|
||||
if k_a == ast.ASTKind.Try:
|
||||
tr_a: ast.Try* = ast.Try*(node)
|
||||
if tr_a is not None:
|
||||
if self._list_has_assert(tr_a.handlers, depth) != 0: return 1
|
||||
if self._list_has_assert(tr_a.orelse, depth) != 0: return 1
|
||||
if self._list_has_assert(tr_a.finalbody, depth) != 0: return 1
|
||||
# Match: the case bodies live in cases, not children.
|
||||
elif k_a == ast.ASTKind.Match:
|
||||
mt_a: ast.Match* = ast.Match*(node)
|
||||
if mt_a is not None and self._list_has_assert(mt_a.cases, depth) != 0: return 1
|
||||
# Recurse into this node's statement block (module/function/class/if/for/while/with/except bodies).
|
||||
if node.children is not None:
|
||||
ch_a: list[ast.AST*]* = node.children
|
||||
n_a: sizet = len(ch_a)
|
||||
for bi_a in range(n_a):
|
||||
if self._tree_has_assert(ch_a.get(bi_a), depth + 1) != 0: return 1
|
||||
return 0
|
||||
|
||||
# _list_has_assert - apply _tree_has_assert over a statement list.
|
||||
def _list_has_assert(self, lst: list[ast.AST*]*, depth: int) -> int:
|
||||
"""Return 1 when any node in the statement list contains an assert statement."""
|
||||
if lst is None: return 0
|
||||
n_l: sizet = len(lst)
|
||||
for i_l in range(n_l):
|
||||
if self._tree_has_assert(lst.get(i_l), depth + 1) != 0: return 1
|
||||
return 0
|
||||
|
||||
def _ContainerInjectionCreatesCycle(self, mod_name: str) -> int:
|
||||
"""Return 1 if injecting mod_name (_list/_dict) would create a dependency cycle."""
|
||||
if mod_name is None or self.ModuleSha1 is None: return 0
|
||||
|
||||
@@ -531,6 +531,8 @@ novirtual class TranslatorGenMixin:
|
||||
if self.Gen.CallGenLeafKit.register_func_ptr_sig(pool, var_name, ml_callable_ret, aa) != 0: return 0
|
||||
# Resolve the type
|
||||
if aa.annotation is not None:
|
||||
# Value-reference markers are parameter-only: a module variable `x: &T` fails fast.
|
||||
self.Gen.TypeGenResolve.reject_ref_marker_position(aa.annotation, trans._imported_modules, "a module-level variable declaration")
|
||||
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, aa.annotation, trans._imported_modules, trans._from_imports, trans, 0)
|
||||
if resolved is not None:
|
||||
# Object-class module vars (@t.Object/@t.CVTable: object-as-pointer) wrap the
|
||||
|
||||
@@ -1416,9 +1416,8 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
||||
# CDefine: compile-time constant marker, backed by i32 (TPC-aligned). Global/field
|
||||
# CDefine annotations are short-circuited earlier, so only return-position reaches here.
|
||||
elif string.strcmp(type_name, "CDefine") == 0: return mlirlite.Int32(pool)
|
||||
# CNeedPtr/CAutoPtr: auto address-of parameter markers; both map to i8* at the LLVM
|
||||
# level (TPV does not implement call-site auto address-of).
|
||||
elif string.strcmp(type_name, "CNeedPtr") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||||
# CAutoPtr: unconditional-address-of parameter marker; maps to i8* at the LLVM level
|
||||
# (t.CNeedPtr was removed in favor of the `&T` / `&temp T` parameter annotation).
|
||||
elif string.strcmp(type_name, "CAutoPtr") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||||
# Unsigned modifiers
|
||||
elif string.strcmp(type_name, "CUnsigned") == 0: return mlirlite.Int32(pool)
|
||||
@@ -1477,6 +1476,41 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
||||
nm: ast.Name* = ast.Name*(node)
|
||||
if nm.id is not None and string.strcmp(nm.id, "CPtr") == 0: return 1
|
||||
return 0
|
||||
def _is_ref_marker(self, node: ast.AST*, imported_modules: str) -> int:
|
||||
"""Whether a node is a CRef/CTempRef value-reference marker: 1=yes / 0=no."""
|
||||
if self._is_t_attr(node, "CRef", imported_modules) != 0: return 1
|
||||
if self._is_t_attr(node, "CTempRef", imported_modules) != 0: return 1
|
||||
return 0
|
||||
def _ref_marker_name_mode(self, node: ast.AST*) -> int:
|
||||
"""Syntactic value-reference marker test (no import context): 0=none / 1=CRef / 2=CTempRef."""
|
||||
if node is None: return 0
|
||||
nm: str = None
|
||||
if node.kind() == ast.ASTKind.Attribute:
|
||||
at: ast.Attribute* = ast.Attribute*(node)
|
||||
if at.value is not None and at.value.kind() == ast.ASTKind.Name:
|
||||
at_vn: ast.Name* = ast.Name*(at.value)
|
||||
if at_vn.id is not None and string.strcmp(at_vn.id, "t") == 0: nm = at.attr
|
||||
elif node.kind() == ast.ASTKind.Name:
|
||||
nm = (ast.Name*(node)).id
|
||||
if nm is None: return 0
|
||||
if string.strcmp(nm, "CRef") == 0: return 1
|
||||
if string.strcmp(nm, "CTempRef") == 0: return 2
|
||||
return 0
|
||||
def ref_param_mode(self, node: ast.AST*, imported_modules: str) -> int:
|
||||
"""Value-reference mode of a top-level union annotation: 0=none / 1=ref (&T) / 2=temp (&temp T)."""
|
||||
if node is None or node.kind() != ast.ASTKind.BinOp: return 0
|
||||
bo: ast.BinOp* = ast.BinOp*(node)
|
||||
if bo.op != ast.OpKind.BitOr: return 0
|
||||
if self._is_t_attr(bo.left, "CTempRef", imported_modules) != 0: return 2
|
||||
if self._is_t_attr(bo.right, "CTempRef", imported_modules) != 0: return 2
|
||||
if self._is_t_attr(bo.left, "CRef", imported_modules) != 0: return 1
|
||||
if self._is_t_attr(bo.right, "CRef", imported_modules) != 0: return 1
|
||||
return 0
|
||||
def reject_ref_marker_position(self, node: ast.AST*, imported_modules: str, where: str) -> int:
|
||||
"""Fail fast when a value-reference marker (&T / &temp T) is used outside a parameter position."""
|
||||
if self.ref_param_mode(node, imported_modules) == 0: return 0
|
||||
self.Gen.TypeGenErr.fatal_error(node, f"value-reference parameter annotation '&T' / '&temp T' is only allowed in function parameter position, not for {where}", "TypeError")
|
||||
return 1
|
||||
def _is_cptr_subscript(self, node: ast.AST*) -> int:
|
||||
"""Whether a node is a t.CPtr[T] subscript pointer type (e.g. t.CPtr[t.CPtr])."""
|
||||
if node is None or node.kind() != ast.ASTKind.Subscript: return 0
|
||||
@@ -1557,9 +1591,31 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
||||
return buf2
|
||||
return "<subscript>"
|
||||
|
||||
# BinOp node: A | B (union type)
|
||||
# BinOp node: A | B (union type); value-reference params render as `&T` / `&temp T`
|
||||
if k == ast.ASTKind.BinOp:
|
||||
bo: ast.BinOp* = ast.BinOp*(annot)
|
||||
if bo.op == ast.OpKind.BitOr:
|
||||
m_left: int = self._ref_marker_name_mode(bo.left)
|
||||
m_right: int = self._ref_marker_name_mode(bo.right)
|
||||
if m_left != 0 or m_right != 0:
|
||||
other_rs: ast.AST* = bo.left
|
||||
m_rs: int = m_left
|
||||
if m_left != 0:
|
||||
other_rs = bo.right
|
||||
else:
|
||||
m_rs = m_right
|
||||
inner_rs: str = self.annotation_to_str(other_rs)
|
||||
if inner_rs is not None:
|
||||
if m_rs == 2:
|
||||
buf_tr: str = stdlib.malloc(string.strlen(inner_rs) + 7)
|
||||
if buf_tr is not None:
|
||||
viperlib.snprintf(buf_tr, string.strlen(inner_rs) + 7, "&temp %s", inner_rs)
|
||||
return buf_tr
|
||||
else:
|
||||
buf_rr: str = stdlib.malloc(string.strlen(inner_rs) + 2)
|
||||
if buf_rr is not None:
|
||||
viperlib.snprintf(buf_rr, string.strlen(inner_rs) + 2, "&%s", inner_rs)
|
||||
return buf_rr
|
||||
left_s: str = self.annotation_to_str(bo.left)
|
||||
right_s: str = self.annotation_to_str(bo.right)
|
||||
if left_s is not None and right_s is not None:
|
||||
@@ -2271,14 +2327,18 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
||||
if k == ast.ASTKind.BinOp:
|
||||
bop: ast.BinOp* = ast.BinOp*(node)
|
||||
if bop.op == ast.OpKind.BitOr:
|
||||
# Detect the t.CPtr marker (supports t.CPtr and the bare from t import CPtr name)
|
||||
# Detect the t.CPtr marker (supports t.CPtr and the bare from t import CPtr name).
|
||||
# CRef/CTempRef (value-reference parameter markers) resolve identically here: the
|
||||
# parameter ABI is Ptr(other); the ref/temp distinction only drives call-site auto-address.
|
||||
left_is_cptr: int = self._is_cptr_marker(bop.left, imported_modules)
|
||||
right_is_cptr: int = self._is_cptr_marker(bop.right, imported_modules)
|
||||
if left_is_cptr != 0 or right_is_cptr != 0:
|
||||
# CPtr present: resolve the actual type on the other side and wrap it in Ptr
|
||||
# (left CPtr -> other is right; right CPtr -> other is left).
|
||||
left_is_ref: int = self._is_ref_marker(bop.left, imported_modules)
|
||||
right_is_ref: int = self._is_ref_marker(bop.right, imported_modules)
|
||||
if left_is_cptr != 0 or right_is_cptr != 0 or left_is_ref != 0 or right_is_ref != 0:
|
||||
# CPtr/ref present: resolve the actual type on the other side and wrap it in Ptr
|
||||
# (left marker -> other is right; right marker -> other is left).
|
||||
other_node: ast.AST* = bop.left
|
||||
if left_is_cptr != 0: other_node = bop.right
|
||||
if left_is_cptr != 0 or left_is_ref != 0: other_node = bop.right
|
||||
# Detect whether the other side is t.CVoid (void* = i8*), strictly validating
|
||||
# the full form (Attribute with value Name("t"), or bare CVoid with t imported).
|
||||
other_is_cvoid: int = 0
|
||||
|
||||
Reference in new issue
Block a user