feat(option): base?.field optional access and nullable own
Option access: - `base?.field` evaluates to `Option[field]`: a null base yields null. A value field yields its address (`Option[int]` = `int*`); a pointer field is loaded only on the non-null branch and selected with null. Chaining (`n?.next?.a`) works through the pointer result. - The AST `Attribute.optional` flag drives this in translate_attribute. `own` nullable fix: - `n: own U? = None` was rejected because the own-None check ignored the `?` wrapper. The check now skips nullable annotations, so nullable owned bindings accept None; non-null `own T = None` still fails. LSP: - member completion treats `n?.` as `n.` (base `n`, not `n?`), so fields and methods are offered after `?.`; semantic tokens and diagnostics already resolve through the optional attribute. Probe: null `n?.a` prints the None branch; after `n = new U(5)`, `q: Option[int] = n?.a; *q` = 5; `n?.next` null-checks; `U.__sizeof__()` = 16; LSP completion after `n?.` offers `a`/`next` and diagnostics are clean. Self-host 110/110; ListTest, GenericTest, GenericTest3/4, PtrOpProbe, HashLibTest, NewTest, NewLifetimeTest, StructTest and EscapeOkTest pass.
This commit is contained in:
1 parent
3b8dc1377d
commit
faf1f17b22
3 files changed
+61
-5
No files matched your search
@@ -2786,7 +2786,7 @@ novirtual class AssignGenMixin:
|
||||
nm: ast.Name* = ast.Name*(target)
|
||||
if nm.id is None: return 0
|
||||
|
||||
if GenApi.is_none_literal(aa.value) != 0 and GenApi.annotation_is_own(aa.annotation) != 0:
|
||||
if GenApi.is_none_literal(aa.value) != 0 and GenApi.annotation_is_own(aa.annotation) != 0 and GenApi.annotation_is_nullable(aa.annotation) == 0:
|
||||
self.Gen.TypeGenErr.fatal_error(aa.value, f"`{nm.id}` is `own`: an owned binding must hold an object, not `None`; use `{nm.id}: own T?` for a nullable owned reference", "TypeError")
|
||||
return 0
|
||||
if trans.StrictOBS != 0 and GenApi.is_none_literal(aa.value) != 0:
|
||||
@@ -3317,7 +3317,7 @@ novirtual class AssignGenMixin:
|
||||
if annot is None:
|
||||
self.Gen.TypeGenErr.fatal_error(aa, "AnnAssign is missing a type annotation", "SyntaxError")
|
||||
return 0
|
||||
if GenApi.is_none_literal(aa.value) != 0 and GenApi.annotation_is_own(annot) != 0:
|
||||
if GenApi.is_none_literal(aa.value) != 0 and GenApi.annotation_is_own(annot) != 0 and GenApi.annotation_is_nullable(annot) == 0:
|
||||
self.Gen.TypeGenErr.fatal_error(aa.value, "`own` targets must hold an object, not `None`", "TypeError")
|
||||
return 0
|
||||
if trans.StrictOBS != 0 and GenApi.is_none_literal(aa.value) != 0:
|
||||
@@ -3556,7 +3556,7 @@ 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 GenApi.is_none_literal(asgn.value) != 0 and self.Gen.VarGen.var_is_own(trans.SymTab, nm_c.id) != 0:
|
||||
if GenApi.is_none_literal(asgn.value) != 0 and self.Gen.VarGen.var_is_own(trans.SymTab, nm_c.id) != 0 and self.Gen.VarGen.var_is_nullable(trans.SymTab, nm_c.id) == 0:
|
||||
self.Gen.TypeGenErr.fatal_error(asgn.value, f"`{nm_c.id}` is `own`: an owned binding must hold an object, not `None`", "TypeError")
|
||||
return 0
|
||||
if trans.StrictOBS != 0 and GenApi.is_none_literal(asgn.value) != 0:
|
||||
|
||||
@@ -194,6 +194,52 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
|
||||
if inner_uo is not None and self.is_ptr_type(inner_uo.Ty) != 0: return inner_uo
|
||||
return None
|
||||
return None
|
||||
def _optional_field_result(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, trans: HT.Translator*, obj_ptr: mlirlite.Value*, field_ptr: mlirlite.Value*, field_ty: mlirlite.LLVMType*, vol: int) -> mlirlite.Value*:
|
||||
"""Option[field] for `base?.field`: null when the base is null."""
|
||||
if pool is None or builder is None or trans is None: return None
|
||||
if obj_ptr is None or obj_ptr.Ty is None or field_ptr is None or field_ty is None: return None
|
||||
nulp_b: mlirlite.Value* = mlirlite.ConstNull(pool, obj_ptr.Ty, "null")
|
||||
if nulp_b is None: return None
|
||||
cond: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_NE, obj_ptr, nulp_b)
|
||||
if cond is None: return None
|
||||
# Value field: the Option is the field address (no dereference, safe on a null base).
|
||||
if self.is_ptr_type(field_ty) == 0:
|
||||
fpt: mlirlite.LLVMType* = mlirlite.Ptr(pool, field_ty)
|
||||
if fpt is None: return None
|
||||
nulp_f: mlirlite.Value* = mlirlite.ConstNull(pool, fpt, "null")
|
||||
if nulp_f is None: return None
|
||||
return mlirlite.build_select(builder, cond, field_ptr, nulp_f)
|
||||
# Pointer field: load only on the non-null path, then merge through a slot.
|
||||
func: mlirlite.Function* = builder.Func
|
||||
if func is None: return None
|
||||
cnt: int = trans._label_counter
|
||||
trans._label_counter = cnt + 1
|
||||
b1: str = pool.alloc(40)
|
||||
b2: str = pool.alloc(40)
|
||||
b3: str = pool.alloc(40)
|
||||
if b1 is None or b2 is None or b3 is None: return None
|
||||
viperlib.snprintf(b1, 40, "opt.some.%d", cnt)
|
||||
viperlib.snprintf(b2, 40, "opt.none.%d", cnt)
|
||||
viperlib.snprintf(b3, 40, "opt.end.%d", cnt)
|
||||
some_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, b1)
|
||||
none_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, b2)
|
||||
end_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, b3)
|
||||
if some_bb is None or none_bb is None or end_bb is None: return None
|
||||
slot: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, field_ty)
|
||||
if slot is None: return None
|
||||
zero: mlirlite.Value* = mlirlite.ConstZero(pool, field_ty)
|
||||
if zero is None: return None
|
||||
mlirlite.build_store(builder, zero, slot)
|
||||
mlirlite.build_cond_br(builder, cond, some_bb, none_bb)
|
||||
mlirlite.position_at_end(builder, some_bb)
|
||||
v: mlirlite.Value* = mlirlite.build_load(builder, field_ty, field_ptr, vol)
|
||||
if v is not None: mlirlite.build_store(builder, v, slot)
|
||||
mlirlite.build_br(builder, end_bb)
|
||||
mlirlite.position_at_end(builder, none_bb)
|
||||
mlirlite.build_br(builder, end_bb)
|
||||
mlirlite.position_at_end(builder, end_bb)
|
||||
return mlirlite.build_load(builder, field_ty, slot)
|
||||
|
||||
def translate_attribute(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
|
||||
"""Translate an attribute access and return the loaded field value."""
|
||||
if node is None or builder is None: return None
|
||||
@@ -641,6 +687,12 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
|
||||
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, annot_se.Ty)
|
||||
if field_info is not None:
|
||||
field_ty: mlirlite.LLVMType* = field_info.Ty
|
||||
# `base?.field`: Option[field]. A null base yields null; a value field yields
|
||||
# its address, a pointer field yields the guarded loaded pointer.
|
||||
if at.optional != 0:
|
||||
opt_fp: mlirlite.Value* = mlirlite.build_gep_struct(builder, struct_ty, field_ty, obj_ptr, field_info.Index)
|
||||
if opt_fp is None: return None
|
||||
return self._optional_field_result(pool, builder, trans, obj_ptr, opt_fp, field_ty, 0)
|
||||
# t.VolatileT field: loads through the object carry the volatile qualifier
|
||||
field_vol_rd: int = self.Gen.StructGen.field_is_volatile(field_info)
|
||||
# merge "the field's pointee is volatile": when the base is rawptr[t.VolatileT|T],
|
||||
|
||||
@@ -277,7 +277,7 @@ def _MemberCtx(pool: memhub.MemManager*, prefix: str, out_base: str*,
|
||||
i: sizet = n
|
||||
while i > 0:
|
||||
ch: char = prefix[i - 1]
|
||||
if _IsIdentChar(ch) != 0 or ch == '.':
|
||||
if _IsIdentChar(ch) != 0 or ch == '.' or ch == '?':
|
||||
i -= 1
|
||||
continue
|
||||
break
|
||||
@@ -299,10 +299,14 @@ def _MemberCtx(pool: memhub.MemManager*, prefix: str, out_base: str*,
|
||||
if base is None:
|
||||
return 0
|
||||
base[dot] = 0
|
||||
out_filter[0] = LspSymbols._CopyStr(pool, run + dot + 1, 256)
|
||||
# `n?.` / `n?.a`: the receiver is `n`, not `n?`.
|
||||
if dot > 0 and base[dot - 1] == '?':
|
||||
base[dot - 1] = 0
|
||||
dot -= 1
|
||||
if dot == 0 or (base[0] >= '0' and base[0] <= '9'):
|
||||
return 0
|
||||
out_base[0] = str(base)
|
||||
out_filter[0] = LspSymbols._CopyStr(pool, run + dot + 1, 256)
|
||||
return 1
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user