feat(obs): default T to Box[T], explicit Box/RawT/Option, move analysis
Rename the per-file opt-in from `__cwa__` to `from __future__ import __obs__` (internal StrictCWA becomes StrictOBS). OBS type model: - `Box[T]` / `BoxT[T]` is one owned indirection (`Ptr(T)`); a user generic class named Box keeps precedence. - `RawT[T]` is the unboxed value type; primitives, arrays and pointers stay raw. - `Option[T]` is the explicit spelling of the existing `T?` suffix (parser marker renamed to OptionT; NullableT still accepted) and resolves to a nullable pointer. - Declaration annotations (locals, globals, parameters, returns) get an `AutoBoxT[...]` wrapper in OBS files before the vpi pass, so struct/class types become `Box[T]` while primitives stay raw; struct field declarations are left untouched, preserving the layout (`U.__sizeof__()` stays 16). Explicit `Box[...]`/`RawT[...]`/`Option[...]`/`T?` are not wrapped again. Ownership: - an OBS move (`b: T = a` where `a` owns a heap object) marks the source slot as released, so later uses (including `a.field`) fail fast; rebinding clears the state. LSP: `Box`, `RawT` and `Option` are offered as builtin completions. Probe: `_U: U = new U(1,2,3,4); _B: U = _U; print(U.__sizeof__())` prints 16 and `print(_U.a)` fails with LifetimeError; Box[U]/Option[U]/U? declarations work; `Box[U] = None` is rejected; `__cwa__` is rejected and `__obs__` accepted. Self-host 110/110; ListTest, GenericTest, GenericTest3, GenericTest4, PtrOpProbe, HashLibTest, NewTest, NewLifetimeTest, StructTest and EscapeOkTest pass.
This commit is contained in:
1 parent
8c72adde88
commit
1581aa0d84
11 files changed
+163
-50
No files matched your search
@@ -330,9 +330,12 @@ def TranslateFileGetTrans(mb: memhub.MemBuddy*, file_path: str, sha1_val: str, c
|
||||
err_col: int = ast.get_parse_err_col()
|
||||
VLogger.error(f"parse failed: {file_path} (line {err_line} col {err_col})", "PARSE")
|
||||
return None
|
||||
# Per-file strict NAFTAG opt-in (`from __future__ import __cwa__`): detect it before the
|
||||
# Per-file strict NAFTAG opt-in (`from __future__ import __obs__`): detect it before the
|
||||
# vpi pass so stub generation and translation agree on the mode.
|
||||
GenApi.set_strict_cwa(GenApi.detect_strict_cwa(tree))
|
||||
GenApi.set_strict_obs(GenApi.detect_strict_obs(tree))
|
||||
# OBS default boxing: rewrite declaration annotations (`T` -> `AutoBoxT[T]`) before the
|
||||
# vpi pass so stubs and callers agree with the translated layout.
|
||||
if GenApi.strict_obs() != 0: GenApi.apply_obs_autobox(mb, tree)
|
||||
# Generate the .vpi stub from the AST before tr.translate (same as Phase1); it lets
|
||||
# other modules query function signatures and TypedefT definitions.
|
||||
if temp_dir is not None:
|
||||
@@ -363,7 +366,7 @@ def TranslateFileGetTrans(mb: memhub.MemBuddy*, file_path: str, sha1_val: str, c
|
||||
# App pre-registration (Phase A-pre) switch: when 1, import handling + dependency vpi
|
||||
# injection also run in the declare phase. includes pre-registration must pass 0.
|
||||
tr._pre_allow_dep_inject = pre_allow_dep_inject
|
||||
tr.StrictCWA = GenApi.strict_cwa()
|
||||
tr.StrictOBS = GenApi.strict_obs()
|
||||
# P2: reuse the global gen singleton (created by App/main.vp); create it as a fallback
|
||||
# when unregistered (defensive for direct-call paths).
|
||||
gen: GenGenerator.Generator* = GenGenerator._g_gen
|
||||
|
||||
@@ -1741,6 +1741,8 @@ novirtual class AssignGenMixin:
|
||||
self.Gen.VarGen._SlifeNoteStore(pool, builder, tsh, 1)
|
||||
ssh: mlirlite.Value* = self.Gen.VarGen._SlifeShadow(src_slot)
|
||||
if ssh is not None: self.Gen.VarGen._SlifeNoteStore(pool, builder, ssh, 0)
|
||||
# OBS move semantics: the source no longer owns the value.
|
||||
if trans.StrictOBS != 0: self.Gen.VarGen._FreedMark(src_slot)
|
||||
return 1
|
||||
self._SlifeDropOwned(pool, builder, mod, trans, target_slot)
|
||||
return 0
|
||||
@@ -2771,9 +2773,9 @@ novirtual class AssignGenMixin:
|
||||
nm: ast.Name* = ast.Name*(target)
|
||||
if nm.id is None: return 0
|
||||
|
||||
if trans.StrictCWA != 0 and GenApi.is_none_literal(aa.value) != 0:
|
||||
if trans.StrictOBS != 0 and GenApi.is_none_literal(aa.value) != 0:
|
||||
if GenApi.annotation_is_nullable(aa.annotation) == 0:
|
||||
self.Gen.TypeGenErr.fatal_error(aa.value, f"`{nm.id}` is not nullable: in a strict CWA file `None` requires the nullable declaration (`{nm.id}: T?`)", "TypeError")
|
||||
self.Gen.TypeGenErr.fatal_error(aa.value, f"`{nm.id}` is not nullable: in an OBS file `None` requires the nullable declaration (`{nm.id}: T?`)", "TypeError")
|
||||
return 0
|
||||
|
||||
# Pure locals are value copies too: a pointer/array local would let pure code alias or
|
||||
@@ -3297,9 +3299,9 @@ novirtual class AssignGenMixin:
|
||||
if annot is None:
|
||||
self.Gen.TypeGenErr.fatal_error(aa, "AnnAssign is missing a type annotation", "SyntaxError")
|
||||
return 0
|
||||
if trans.StrictCWA != 0 and GenApi.is_none_literal(aa.value) != 0:
|
||||
if trans.StrictOBS != 0 and GenApi.is_none_literal(aa.value) != 0:
|
||||
if GenApi.annotation_is_nullable(annot) == 0:
|
||||
self.Gen.TypeGenErr.fatal_error(aa.value, "the annotated target is not nullable: in a strict CWA file `None` requires a nullable type (`T?`)", "TypeError")
|
||||
self.Gen.TypeGenErr.fatal_error(aa.value, "the annotated target is not nullable: in an OBS file `None` requires a nullable type (`T?`)", "TypeError")
|
||||
return 0
|
||||
# 与 Name 路径同口径解析注解:先特化泛型(避免 list[X] 退化成裸 list*),解析失败即 fail-fast。
|
||||
self.Gen.TypeGenResolve.reject_ref_marker_position(annot, trans._imported_modules, "an annotated assignment to an attribute/subscript")
|
||||
@@ -3533,9 +3535,9 @@ novirtual class AssignGenMixin:
|
||||
nm_c: ast.Name* = ast.Name*(tgt_c)
|
||||
if nm_c.id is None: continue
|
||||
ve_c: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_c.id)
|
||||
if trans.StrictCWA != 0 and GenApi.is_none_literal(asgn.value) != 0:
|
||||
if trans.StrictOBS != 0 and GenApi.is_none_literal(asgn.value) != 0:
|
||||
if self.Gen.VarGen.var_is_nullable(trans.SymTab, nm_c.id) == 0:
|
||||
self.Gen.TypeGenErr.fatal_error(asgn.value, f"`{nm_c.id}` is not nullable: in a strict CWA file `None` requires the nullable declaration (`{nm_c.id}: T?`)", "TypeError")
|
||||
self.Gen.TypeGenErr.fatal_error(asgn.value, f"`{nm_c.id}` is not nullable: in an OBS file `None` requires the nullable declaration (`{nm_c.id}: T?`)", "TypeError")
|
||||
return 0
|
||||
if self.Gen.VarGen.var_is_const(ve_c) != 0:
|
||||
self.const_assign_fail(tgt_c, "assignment", nm_c.id, self.Gen.VarGen.var_annot_class_name(ve_c))
|
||||
|
||||
@@ -527,7 +527,7 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
|
||||
else:
|
||||
obj_ptr = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
|
||||
if self.Gen.VarGen._FreedCheck(obj_ptr) != 0:
|
||||
self.Gen.TypeGenErr.fatal_error(at.value, f"cannot use '{nm.id}': already released (by `del` or a @life(willFree(...)) call)", "LifetimeError")
|
||||
self.Gen.TypeGenErr.fatal_error(at.value, f"cannot use '{nm.id}': already released (by `del`, a move, or a @life(willFree(...)) call)", "LifetimeError")
|
||||
return None
|
||||
|
||||
# non-Name path: translate the object value (which loads)
|
||||
@@ -945,7 +945,7 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
|
||||
else:
|
||||
obj_ptr = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
|
||||
if self.Gen.VarGen._FreedCheck(obj_ptr) != 0:
|
||||
self.Gen.TypeGenErr.fatal_error(at.value, f"cannot use '{nm.id}': already released (by `del` or a @life(willFree(...)) call)", "LifetimeError")
|
||||
self.Gen.TypeGenErr.fatal_error(at.value, f"cannot use '{nm.id}': already released (by `del`, a move, or a @life(willFree(...)) call)", "LifetimeError")
|
||||
return None
|
||||
|
||||
# module attribute access (write path): sys._mbuddy = mb / os._win32._mbuddy = v → store to the module
|
||||
|
||||
@@ -42,7 +42,7 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
|
||||
# 只在**读**路径检查;对同名变量重新赋值会清除失效态(见 AssignGenMixin)。
|
||||
if trans is not None:
|
||||
if self.Gen.VarGen._FreedCheck(self.Gen.VarGen.lookup_var(trans.SymTab, nm_id)) != 0:
|
||||
self.Gen.TypeGenErr.fatal_error(node, f"cannot use '{nm_id}': already released (by `del` or a @life(willFree(...)) call)", "LifetimeError")
|
||||
self.Gen.TypeGenErr.fatal_error(node, f"cannot use '{nm_id}': already released (by `del`, a move, or a @life(willFree(...)) call)", "LifetimeError")
|
||||
# DefineT compile-time constant: return the constant (float -> double, int -> i32).
|
||||
# Existing variables win over DefineT table entries injected from vpi stubs (lookup_var guard).
|
||||
cdef_val_d: double = self.Gen.TypeGenCDef.lookup_cdefine_double(nm_id)
|
||||
@@ -536,7 +536,7 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
|
||||
return None
|
||||
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
|
||||
if self.Gen.VarGen._FreedCheck(alloca) != 0:
|
||||
self.Gen.TypeGenErr.fatal_error(sub.value, f"cannot use '{nm.id}': already released (by `del` or a @life(willFree(...)) call)", "LifetimeError")
|
||||
self.Gen.TypeGenErr.fatal_error(sub.value, f"cannot use '{nm.id}': already released (by `del`, a move, or a @life(willFree(...)) call)", "LifetimeError")
|
||||
return None
|
||||
if alloca is not None and alloca.Ty is not None:
|
||||
if self.is_ptr_type(alloca.Ty) != 0:
|
||||
@@ -848,7 +848,7 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
|
||||
if GenApi.is_nonlocal_name(trans, nm.id) != 0: return self._nonlocal_subscript_ptr(builder, pool, trans, nm.id, idx_val)
|
||||
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
|
||||
if self.Gen.VarGen._FreedCheck(alloca) != 0:
|
||||
self.Gen.TypeGenErr.fatal_error(sub.value, f"cannot use '{nm.id}': already released (by `del` or a @life(willFree(...)) call)", "LifetimeError")
|
||||
self.Gen.TypeGenErr.fatal_error(sub.value, f"cannot use '{nm.id}': already released (by `del`, a move, or a @life(willFree(...)) call)", "LifetimeError")
|
||||
return None
|
||||
if alloca is not None and alloca.Ty is not None:
|
||||
if self.is_ptr_type(alloca.Ty) != 0:
|
||||
|
||||
@@ -918,7 +918,7 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
|
||||
if nm_ao.id is not None:
|
||||
# P2 willFree:对已失效变量取地址 → fail-fast(与读取路径同一口径)
|
||||
if self.Gen.VarGen._FreedCheck(self.Gen.VarGen.lookup_var(trans.SymTab, nm_ao.id)) != 0:
|
||||
self.Gen.TypeGenErr.fatal_error(node, f"cannot take the address of '{nm_ao.id}': already released (by `del` or a @life(willFree(...)) call)", "LifetimeError")
|
||||
self.Gen.TypeGenErr.fatal_error(node, f"cannot take the address of '{nm_ao.id}': already released (by `del`, a move, or a @life(willFree(...)) call)", "LifetimeError")
|
||||
# self/cls object reference: return the object pointer itself (Ptr(Struct)), not the pointer variable's address (Ptr(Ptr(Struct))).
|
||||
if string.strcmp(nm_ao.id, "self") == 0 or string.strcmp(nm_ao.id, "cls") == 0:
|
||||
se_ao: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_ao.id)
|
||||
|
||||
+99
-21
@@ -114,19 +114,19 @@ def get_current_file() -> str:
|
||||
return None
|
||||
|
||||
|
||||
# ---------------- strict NAFTAG mode, per file (`from __future__ import __cwa__`) ----------------
|
||||
# ---------------- OBS mode, per file (`from __future__ import __obs__`) ----------------
|
||||
# The opt-in is per file, so the flag is reset for every translation (BuildPipeline calls
|
||||
# detect_strict_cwa before the vpi pass). Legacy @life annotations are rejected when set.
|
||||
_strict_cwa: int = 0
|
||||
# detect_strict_obs before the vpi pass). Legacy @life annotations are rejected when set.
|
||||
_strict_obs: int = 0
|
||||
|
||||
|
||||
def set_strict_cwa(flag: int) -> None:
|
||||
global _strict_cwa
|
||||
_strict_cwa = flag
|
||||
def set_strict_obs(flag: int) -> None:
|
||||
global _strict_obs
|
||||
_strict_obs = flag
|
||||
|
||||
|
||||
def strict_cwa() -> int:
|
||||
return _strict_cwa
|
||||
def strict_obs() -> int:
|
||||
return _strict_obs
|
||||
|
||||
|
||||
# annotation_is_nullable - 1 when a type annotation carries the `?` suffix (`NullableT[T]`).
|
||||
@@ -136,7 +136,9 @@ def annotation_is_nullable(ann: ast.AST*) -> int:
|
||||
if sb.value is None or sb.value.kind() != ast.ASTKind.Name: return 0
|
||||
nm: ast.Name* = ast.Name*(sb.value)
|
||||
if nm.id is None: return 0
|
||||
if string.strcmp(nm.id, "OptionT") == 0: return 1
|
||||
if string.strcmp(nm.id, "NullableT") == 0: return 1
|
||||
if string.strcmp(nm.id, "Option") == 0: return 1
|
||||
return 0
|
||||
|
||||
|
||||
@@ -149,21 +151,97 @@ def is_none_literal(node: ast.AST*) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
# _future_directive_names_ok - 1 when the from-import names are exactly `__cwa__`.
|
||||
# _obs_wrap_autobox - wrap a bare declaration annotation in AutoBoxT for the OBS default.
|
||||
# Only simple names / module-qualified names are wrapped: pointers, arrays, generics and
|
||||
# `T?` already carry their own shape. DefineT/TypedefT are compile-time markers, not types.
|
||||
def _obs_wrap_autobox(pool: memhub.MemManager*, ann: ast.AST*) -> ast.AST*:
|
||||
if ann is None: return None
|
||||
k: int = ann.kind()
|
||||
if k != ast.ASTKind.Name and k != ast.ASTKind.Attribute: return ann
|
||||
if k == ast.ASTKind.Name:
|
||||
nm: ast.Name* = ast.Name*(ann)
|
||||
if nm.id is None: return ann
|
||||
if string.strcmp(nm.id, "None") == 0 or string.strcmp(nm.id, "void") == 0: return ann
|
||||
if string.strcmp(nm.id, "DefineT") == 0 or string.strcmp(nm.id, "TypedefT") == 0: return ann
|
||||
wn: ast.AST* = ast.Name(pool, "AutoBoxT", ast.ASTCtx.Load)
|
||||
if wn is None: return ann
|
||||
node: ast.AST* = ast.Subscript(pool, wn, ann, ast.ASTCtx.Load)
|
||||
if node is None: return ann
|
||||
node.lineno = ann.lineno
|
||||
node.col_offset = ann.col_offset
|
||||
return node
|
||||
|
||||
|
||||
def _obs_box_arg(pool: memhub.MemManager*, ag: ast.AST*) -> None:
|
||||
if ag is None or ag.kind() != ast.ASTKind.Arg: return
|
||||
a2: ast.Arg* = ast.Arg*(ag)
|
||||
a2.annotation = _obs_wrap_autobox(pool, a2.annotation)
|
||||
|
||||
|
||||
def _obs_box_args(pool: memhub.MemManager*, args_node: ast.AST*) -> None:
|
||||
if args_node is None or args_node.kind() != ast.ASTKind.Arguments: return
|
||||
ar: ast.Arguments* = ast.Arguments*(args_node)
|
||||
i: int = 0
|
||||
if ar.args is not None:
|
||||
while i < int(len(ar.args)):
|
||||
_obs_box_arg(pool, ar.args.get(i))
|
||||
i += 1
|
||||
_obs_box_arg(pool, ar.vararg)
|
||||
_obs_box_arg(pool, ar.kwarg)
|
||||
|
||||
|
||||
# _obs_box_walk - apply the OBS default boxing to declarations (not struct fields).
|
||||
def _obs_box_walk(pool: memhub.MemManager*, node: ast.AST*, in_class: int) -> None:
|
||||
if node is None: return
|
||||
k: int = node.kind()
|
||||
if k == ast.ASTKind.ClassDef:
|
||||
# Direct AnnAssign children are field declarations: their layout stays raw.
|
||||
if node.children is not None:
|
||||
i: int = 0
|
||||
while i < int(len(node.children)):
|
||||
ch: ast.AST* = node.children.get(i)
|
||||
if ch is not None and ch.kind() != ast.ASTKind.AnnAssign: _obs_box_walk(pool, ch, 1)
|
||||
i += 1
|
||||
return
|
||||
if k == ast.ASTKind.AnnAssign:
|
||||
if in_class == 0:
|
||||
aa: ast.AnnAssign* = ast.AnnAssign*(node)
|
||||
aa.annotation = _obs_wrap_autobox(pool, aa.annotation)
|
||||
return
|
||||
if k == ast.ASTKind.FunctionDef:
|
||||
fd: ast.FunctionDef* = ast.FunctionDef*(node)
|
||||
_obs_box_args(pool, fd.args)
|
||||
fd.returns = _obs_wrap_autobox(pool, fd.returns)
|
||||
in_class = 0
|
||||
if node.children is not None:
|
||||
i: int = 0
|
||||
while i < int(len(node.children)):
|
||||
_obs_box_walk(pool, node.children.get(i), in_class)
|
||||
i += 1
|
||||
|
||||
|
||||
# apply_obs_autobox - OBS default boxing: `T` declaration annotations become `Box[T]`.
|
||||
def apply_obs_autobox(pool: memhub.MemManager*, tree: ast.AST*) -> int:
|
||||
if tree is None: return 0
|
||||
_obs_box_walk(pool, tree, 0)
|
||||
return 1
|
||||
|
||||
|
||||
# _future_directive_names_ok - 1 when the from-import names are exactly `__obs__`.
|
||||
def _future_directive_names_ok(impf: ast.ImportFrom*) -> int:
|
||||
if impf is None or impf.names is None or len(impf.names) != 1: return 0
|
||||
al: ast.Alias* = ast.Alias*(impf.names.get(0))
|
||||
if al is None or al.name is None or al.asname is not None: return 0
|
||||
if string.strcmp(al.name, "__cwa__") == 0: return 1
|
||||
if string.strcmp(al.name, "__obs__") == 0: return 1
|
||||
return 0
|
||||
|
||||
|
||||
# detect_strict_cwa - detect the file-level opt-in and publish it to strict_cwa().
|
||||
# detect_strict_obs - detect the file-level opt-in and publish it to strict_obs().
|
||||
# The directive must be the first statement (a module docstring may precede it);
|
||||
# a `from __future__ import ...` anywhere else fails fast.
|
||||
def detect_strict_cwa(tree: ast.AST*) -> int:
|
||||
global _strict_cwa
|
||||
_strict_cwa = 0
|
||||
def detect_strict_obs(tree: ast.AST*) -> int:
|
||||
global _strict_obs
|
||||
_strict_obs = 0
|
||||
if tree is None or tree.children is None: return 0
|
||||
ch: list[ast.AST*]* = tree.children
|
||||
first: int = -1
|
||||
@@ -186,10 +264,10 @@ def detect_strict_cwa(tree: ast.AST*) -> int:
|
||||
fatal_error(child, "`from __future__ import ...` must be the first statement of the file", "SyntaxError")
|
||||
continue
|
||||
if _future_directive_names_ok(impf) == 0:
|
||||
fatal_error(child, "unsupported `__future__` import: only `from __future__ import __cwa__` is known", "SyntaxError")
|
||||
fatal_error(child, "unsupported `__future__` import: only `from __future__ import __obs__` is known", "SyntaxError")
|
||||
continue
|
||||
_strict_cwa = 1
|
||||
return _strict_cwa
|
||||
_strict_obs = 1
|
||||
return _strict_obs
|
||||
|
||||
|
||||
# handle_future_import - 1 when node is the per-file directive, which is not a module import.
|
||||
@@ -198,16 +276,16 @@ def handle_future_import(trans: HT.Translator*, node: ast.AST*) -> int:
|
||||
impf: ast.ImportFrom* = ast.ImportFrom*(node)
|
||||
if impf is None or impf.module is None: return 0
|
||||
if string.strcmp(impf.module, "__future__") != 0: return 0
|
||||
# Not the first statement (detect_strict_cwa would have accepted it) or a nested copy.
|
||||
if trans is not None and trans.StrictCWA == 0:
|
||||
# Not the first statement (detect_strict_obs would have accepted it) or a nested copy.
|
||||
if trans is not None and trans.StrictOBS == 0:
|
||||
fatal_error(node, "`from __future__ import ...` must be the first statement of the file", "SyntaxError")
|
||||
return 1
|
||||
|
||||
|
||||
# _reject_legacy_life - fail fast when a strict file still carries an @life contract.
|
||||
def _reject_legacy_life(node: ast.AST*) -> None:
|
||||
if strict_cwa() == 0: return
|
||||
fatal_error(node, "@life/@willFree is not available in a strict CWA file (`from __future__ import __cwa__`); write the boundary contract at the call site instead", "LifetimeError")
|
||||
if strict_obs() == 0: return
|
||||
fatal_error(node, "@life/@willFree is not available in an OBS file (`from __future__ import __obs__`); write the boundary contract at the call site instead", "LifetimeError")
|
||||
|
||||
|
||||
# ---------------- _is_ctypedef_annotation (delegates to TypeGenCType._is_ctypedef_annotation) ----------------
|
||||
|
||||
@@ -673,7 +673,7 @@ novirtual class ImportsGenMixin:
|
||||
"""Translate the module part of a from-import statement."""
|
||||
impf: ast.ImportFrom* = ast.ImportFrom*(node)
|
||||
if impf is None: return 0
|
||||
# `from __future__ import __cwa__` is a per-file mode switch, not a module import.
|
||||
# `from __future__ import __obs__` is a per-file mode switch, not a module import.
|
||||
if GenApi.handle_future_import(trans, node) != 0: return 0
|
||||
# Resolve the module name: relative imports need the package prefix
|
||||
resolved: str = self._resolve_relative_module(trans.Pool, trans.CurrentPackage, impf.level, impf.module)
|
||||
|
||||
@@ -435,10 +435,10 @@ novirtual class StmtGenCifMixin:
|
||||
builder: mlirlite.IRBuilder* = trans._cur_builder
|
||||
mod: mlirlite.LLVMModule* = trans.Module
|
||||
|
||||
if trans.StrictCWA != 0 and GenApi.is_none_literal(rt.value) != 0:
|
||||
if trans.StrictOBS != 0 and GenApi.is_none_literal(rt.value) != 0:
|
||||
fd_ret: ast.FunctionDef* = ast.FunctionDef*(self.Gen.StructGen.NllCurFunc)
|
||||
if fd_ret is not None and GenApi.annotation_is_nullable(fd_ret.returns) == 0:
|
||||
self.Gen.TypeGenErr.fatal_error(rt.value, "the function's return type is not nullable: in a strict CWA file `None` requires a nullable return type (`-> T?`)", "TypeError")
|
||||
self.Gen.TypeGenErr.fatal_error(rt.value, "the function's return type is not nullable: in an OBS file `None` requires a nullable return type (`-> T?`)", "TypeError")
|
||||
return 0
|
||||
|
||||
val: mlirlite.Value* = None
|
||||
|
||||
@@ -234,10 +234,10 @@ novirtual class Translator:
|
||||
# Phase 1a declare mode: 1=register struct/enum/union only (no body), 0=full translation
|
||||
_declare_only: int
|
||||
|
||||
# Per-file strict NAFTAG mode: 1 when the file opted in with
|
||||
# `from __future__ import __cwa__`. The legacy @life boundary syntax is then
|
||||
# Per-file OBS mode: 1 when the file opted in with
|
||||
# `from __future__ import __obs__`. The legacy @life boundary syntax is then
|
||||
# rejected and only the OBS call-site contracts are accepted.
|
||||
StrictCWA: int
|
||||
StrictOBS: int
|
||||
|
||||
# Expression-statement context: 1 = the surrounding statement discards the call result
|
||||
# (e.g. a bare `VLogger.error(...)` line). A placeholder return type is then harmless,
|
||||
@@ -299,7 +299,7 @@ novirtual class Translator:
|
||||
self.GenericTypeParamNames = None
|
||||
self.GenericTypeArgs = None
|
||||
self._declare_only = 0
|
||||
self.StrictCWA = 0
|
||||
self.StrictOBS = 0
|
||||
self._pre_allow_dep_inject = 0
|
||||
self.SymTab = None
|
||||
# REPL mode context: unset by default (= non-REPL, all Gen-side interception bypassed)
|
||||
@@ -522,7 +522,7 @@ novirtual class Translator:
|
||||
"""Delegate to GenApi.translate_children() to translate module-level statements (trans arg)."""
|
||||
# Per-file strict NAFTAG opt-in: must be known before imports/classes/functions are
|
||||
# translated, so every GenApi check in this file sees the same mode.
|
||||
self.StrictCWA = GenApi.detect_strict_cwa(tree)
|
||||
self.StrictOBS = GenApi.detect_strict_obs(tree)
|
||||
# File-level class-name pre-registration must run in all three modes: it is the only source of
|
||||
# evidence #5 for `_norm_type_arg_name` rule 5.5 and is hoisted above the _declare_only gate.
|
||||
GenApi.register_file_classes(pool, self.ModuleSha1, tree)
|
||||
|
||||
@@ -1584,11 +1584,15 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
||||
if k == ast.ASTKind.Subscript:
|
||||
sub: ast.Subscript* = ast.Subscript*(node)
|
||||
if sub.value is None: return None
|
||||
# `T?` carries no class name of its own: unwrap to the annotated type.
|
||||
# Type wrappers carry no class name of their own: unwrap to the annotated type.
|
||||
if sub.value.kind() == ast.ASTKind.Name:
|
||||
nul_nm2: ast.Name* = ast.Name*(sub.value)
|
||||
if nul_nm2.id is not None and string.strcmp(nul_nm2.id, "NullableT") == 0:
|
||||
return self.extract_class_name_from_annotation(sub.slice, imported_modules, pool, from_imports)
|
||||
if nul_nm2.id is not None:
|
||||
if string.strcmp(nul_nm2.id, "NullableT") == 0 or string.strcmp(nul_nm2.id, "OptionT") == 0 \
|
||||
or string.strcmp(nul_nm2.id, "Option") == 0 or string.strcmp(nul_nm2.id, "Box") == 0 \
|
||||
or string.strcmp(nul_nm2.id, "BoxT") == 0 or string.strcmp(nul_nm2.id, "RawT") == 0 \
|
||||
or string.strcmp(nul_nm2.id, "AutoBoxT") == 0:
|
||||
return self.extract_class_name_from_annotation(sub.slice, imported_modules, pool, from_imports)
|
||||
# `attr.<name>[T]` marker wrapper: the class name lives in the wrapped type.
|
||||
if self._is_cattr_marker(sub.value) != 0:
|
||||
if sub.slice is not None: return self.extract_class_name_from_annotation(sub.slice, imported_modules, pool, from_imports)
|
||||
@@ -2267,14 +2271,37 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
||||
if sub.value.kind() == ast.ASTKind.Name:
|
||||
gen_nm: ast.Name* = ast.Name*(sub.value)
|
||||
if gen_nm.id is not None:
|
||||
# Nullable suffix `T?`: a nullable class/struct value is a pointer; an existing
|
||||
# pointer keeps its layer (the marker only relaxes the null check).
|
||||
if string.strcmp(gen_nm.id, "NullableT") == 0:
|
||||
# `T?` / `Option[T]`: nullable pointer; an existing pointer keeps its layer
|
||||
# (the marker only relaxes the null check).
|
||||
if string.strcmp(gen_nm.id, "OptionT") == 0 or string.strcmp(gen_nm.id, "NullableT") == 0 \
|
||||
or (string.strcmp(gen_nm.id, "Option") == 0
|
||||
and self.Gen.ClassGenGeneric._find_generic_template("Option") is None):
|
||||
nul_inner: mlirlite.LLVMType* = self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1)
|
||||
if nul_inner is None: return None
|
||||
match nul_inner:
|
||||
case mlirlite.LLVMType.Ptr(_np): return nul_inner
|
||||
case _: return mlirlite.Ptr(pool, nul_inner)
|
||||
# `Box[T]`: one owned indirection (explicit form of the OBS default). A user
|
||||
# generic class named Box keeps precedence.
|
||||
if (string.strcmp(gen_nm.id, "Box") == 0 or string.strcmp(gen_nm.id, "BoxT") == 0) \
|
||||
and self.Gen.ClassGenGeneric._find_generic_template(gen_nm.id) is None:
|
||||
bx_inner: mlirlite.LLVMType* = self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1)
|
||||
if bx_inner is None: return None
|
||||
match bx_inner:
|
||||
case mlirlite.LLVMType.Ptr(_bp): return bx_inner
|
||||
case _: return mlirlite.Ptr(pool, bx_inner)
|
||||
# `RawT[T]`: the unboxed value type.
|
||||
if string.strcmp(gen_nm.id, "RawT") == 0 \
|
||||
and self.Gen.ClassGenGeneric._find_generic_template("RawT") is None:
|
||||
return self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1)
|
||||
# Compiler-inserted OBS default: struct/class values get one pointer layer,
|
||||
# scalar/array/pointer types stay raw.
|
||||
if string.strcmp(gen_nm.id, "AutoBoxT") == 0:
|
||||
ab_inner: mlirlite.LLVMType* = self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1)
|
||||
if ab_inner is None: return None
|
||||
match ab_inner:
|
||||
case mlirlite.LLVMType.Struct(_, _, _): return mlirlite.Ptr(pool, ab_inner)
|
||||
case _: return ab_inner
|
||||
# Builtin marker wrappers: descriptor / storage / value-reference markers carry
|
||||
# no LLVM type of their own, so resolve the wrapped type.
|
||||
if string.strcmp(gen_nm.id, "ConstT") == 0 or string.strcmp(gen_nm.id, "VolatileT") == 0 \
|
||||
|
||||
@@ -183,6 +183,9 @@ def _AddBuiltinItems(pool: memhub.MemManager*, out: json.JsonValue*,
|
||||
_AddBuiltin(pool, out, seen, filter, "tuple")
|
||||
_AddBuiltin(pool, out, seen, filter, "malloc")
|
||||
_AddBuiltin(pool, out, seen, filter, "free")
|
||||
_AddBuiltin(pool, out, seen, filter, "Box")
|
||||
_AddBuiltin(pool, out, seen, filter, "RawT")
|
||||
_AddBuiltin(pool, out, seen, filter, "Option")
|
||||
_AddBuiltin(pool, out, seen, filter, "sizeof")
|
||||
_AddBuiltin(pool, out, seen, filter, "memcpy")
|
||||
_AddBuiltin(pool, out, seen, filter, "memset")
|
||||
|
||||
Reference in new issue
Block a user