feat(obs): reference fields only accept borrows
`A.n = B` where `n: &U?` is now rejected: a reference field must be initialized from `&value` / another reference (None stays allowed). Fields carry a RefKind, `ref_kind` looks through the nullable wrapper, and nullable checks treat RefT/MutRefT as transparent.
This commit is contained in:
1 parent
e650d8dd45
commit
4ff813f5dc
5 files changed
+49
-6
No files matched your search
@@ -4056,6 +4056,20 @@ novirtual class AssignGenMixin:
|
||||
at_wr: ast.Attribute* = ast.Attribute*(target)
|
||||
if at_wr is not None:
|
||||
if self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, at_wr.value) != 0: vol_fw = 1
|
||||
# OBS reference field: the slot holds a borrow, so only `&value` (or another
|
||||
# reference) may initialize it; an owned pointer is not a reference.
|
||||
fe_rf: GenTypes.FieldEntry* = self.Gen.ExprGenAttr._resolve_attr_field_entry(pool, trans, target)
|
||||
if fe_rf is not None and fe_rf.RefKind != 0 and asgn.value is not None:
|
||||
ref_ok: int = 0
|
||||
if GenApi.is_none_literal(asgn.value) != 0: ref_ok = 1
|
||||
elif asgn.value.kind() == ast.ASTKind.UnaryOp:
|
||||
uo_rf: ast.UnaryOp* = ast.UnaryOp*(asgn.value)
|
||||
if uo_rf.op == ast.OpKind.AddrOf: ref_ok = 1
|
||||
elif asgn.value.kind() == ast.ASTKind.Name:
|
||||
nm_rf: ast.Name* = ast.Name*(asgn.value)
|
||||
if nm_rf.id is not None and self.Gen.VarGen.var_ref_kind(trans.SymTab, nm_rf.id) != 0: ref_ok = 1
|
||||
if ref_ok == 0:
|
||||
self.Gen.TypeGenErr.fatal_error(asgn.value, f"field '.{at_tg.attr}' is a reference: assign a borrow (`&value`), not an owner", "TypeError")
|
||||
mlirlite.build_store(builder, store_val, field_ptr, vol_fw)
|
||||
self._move_out_assigned_source(trans, asgn.value, pool, builder)
|
||||
continue
|
||||
|
||||
@@ -166,10 +166,13 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
|
||||
field_byteorder_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
|
||||
# Field nullable flag buffer (1 = `own T?` / `T?`), parallel to field_volatile_buf.
|
||||
field_nullable_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
|
||||
# Field reference kind buffer (0=none, 1=`&T`, 2=`&mut T` in __obs__ files).
|
||||
field_refkind_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
|
||||
if field_names_buf is None or field_types_buf is None or field_defaults_buf is None: return 0
|
||||
if field_annot_buf is None or field_volatile_buf is None: return 0
|
||||
if field_pointee_volatile_buf is None or field_const_buf is None: return 0
|
||||
if field_byteorder_buf is None or field_nullable_buf is None: return 0
|
||||
if field_refkind_buf is None: return 0
|
||||
string.memset(field_names_buf, 0, 8 * GenTypes.FIELD_MAX)
|
||||
string.memset(field_types_buf, 0, 8 * GenTypes.FIELD_MAX)
|
||||
string.memset(field_defaults_buf, 0, 8 * GenTypes.FIELD_MAX)
|
||||
@@ -179,6 +182,7 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
|
||||
string.memset(field_const_buf, 0, 8 * GenTypes.FIELD_MAX)
|
||||
string.memset(field_byteorder_buf, 0, 8 * GenTypes.FIELD_MAX)
|
||||
string.memset(field_nullable_buf, 0, 8 * GenTypes.FIELD_MAX)
|
||||
string.memset(field_refkind_buf, 0, 8 * GenTypes.FIELD_MAX)
|
||||
|
||||
field_count: int = 0
|
||||
|
||||
@@ -232,6 +236,7 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
|
||||
field_byteorder_buf[field_count] = sizet(pfe.ByteOrder)
|
||||
# Inherit the nullable flag (unchanged after flattening)
|
||||
field_nullable_buf[field_count] = sizet(pfe.Nullable)
|
||||
field_refkind_buf[field_count] = sizet(pfe.RefKind)
|
||||
field_count += 1
|
||||
|
||||
children: list[ast.AST*]* = cd.children
|
||||
@@ -358,6 +363,7 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
|
||||
else: field_byteorder_buf[field_count] = 0
|
||||
# `own T?` / `T?`: direct `.` access is rejected; use `?.` or a None check.
|
||||
field_nullable_buf[field_count] = sizet(self._ann_nullable(aa.annotation))
|
||||
field_refkind_buf[field_count] = sizet(self.Gen.TypeGenResolve.ref_kind(aa.annotation))
|
||||
else:
|
||||
field_annot_buf[field_count] = 0
|
||||
field_volatile_buf[field_count] = 0
|
||||
@@ -365,6 +371,7 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
|
||||
field_const_buf[field_count] = 0
|
||||
field_byteorder_buf[field_count] = 0
|
||||
field_nullable_buf[field_count] = 0
|
||||
field_refkind_buf[field_count] = 0
|
||||
field_count += 1
|
||||
|
||||
if field_count == 0:
|
||||
@@ -550,6 +557,10 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
|
||||
if fidx_add >= 0 and field_nullable_buf[fi] != 0:
|
||||
fe_n: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, fidx_add)
|
||||
if fe_n is not None: fe_n.Nullable = 1
|
||||
# reference field kind (`&T` / `&mut T`)
|
||||
if fidx_add >= 0 and field_refkind_buf[fi] != 0:
|
||||
fe_rk: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, fidx_add)
|
||||
if fe_rk is not None: fe_rk.RefKind = int(field_refkind_buf[fi])
|
||||
|
||||
# Phase 1a declaration mode: only register the struct and OOP flags, no method bodies
|
||||
if trans._declare_only == 1:
|
||||
@@ -572,7 +583,8 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
|
||||
nm: ast.Name* = ast.Name*(sub.value)
|
||||
if nm.id is None: return 0
|
||||
if string.strcmp(nm.id, "OptionT") == 0 or string.strcmp(nm.id, "NullableT") == 0: return 1
|
||||
if string.strcmp(nm.id, "OwnT") == 0:
|
||||
if string.strcmp(nm.id, "OwnT") == 0 or string.strcmp(nm.id, "RefT") == 0 \
|
||||
or string.strcmp(nm.id, "MutRefT") == 0:
|
||||
node = sub.slice
|
||||
depth += 1
|
||||
continue
|
||||
|
||||
@@ -142,7 +142,8 @@ def annotation_is_nullable(ann: ast.AST*) -> int:
|
||||
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, "OwnT") != 0: return 0
|
||||
if string.strcmp(nm.id, "OwnT") != 0 and string.strcmp(nm.id, "RefT") != 0 \
|
||||
and string.strcmp(nm.id, "MutRefT") != 0: return 0
|
||||
node = sb.slice
|
||||
depth += 1
|
||||
return 0
|
||||
|
||||
@@ -135,6 +135,8 @@ class FieldEntry:
|
||||
# 1 when the field annotation carries the nullable `?` suffix (`own U?` / `T?`): direct
|
||||
# `.` access is rejected; use `?.` or check against None first. Appended at the end.
|
||||
Nullable: int
|
||||
# Reference kind in an `__obs__` file: 0=not a reference, 1=`&T` (read-only), 2=`&mut T`.
|
||||
RefKind: int
|
||||
|
||||
|
||||
# ============================================================
|
||||
|
||||
@@ -1389,10 +1389,24 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
||||
if m == 3: return 0
|
||||
return m
|
||||
def ref_kind(self, node: ast.AST*) -> int:
|
||||
"""Reference kind of an annotation: 0=none / 1=`&T` / 2=`&mut T`."""
|
||||
m: int = self._ref_marker_name_mode(node)
|
||||
if m == 1: return 1
|
||||
if m == 3: return 2
|
||||
"""Reference kind of an annotation: 0=none / 1=`&T` / 2=`&mut T` (looks through `?`)."""
|
||||
n: ast.AST* = node
|
||||
depth: int = 0
|
||||
while n is not None and depth < 8:
|
||||
m: int = self._ref_marker_name_mode(n)
|
||||
if m == 1: return 1
|
||||
if m == 3: return 2
|
||||
if n.kind() != ast.ASTKind.Subscript: return 0
|
||||
sub_rk: ast.Subscript* = ast.Subscript*(n)
|
||||
if sub_rk.value is None or sub_rk.value.kind() != ast.ASTKind.Name: return 0
|
||||
nm_rk: ast.Name* = ast.Name*(sub_rk.value)
|
||||
if nm_rk.id is None: return 0
|
||||
if string.strcmp(nm_rk.id, "OptionT") == 0 or string.strcmp(nm_rk.id, "NullableT") == 0 \
|
||||
or string.strcmp(nm_rk.id, "OwnT") == 0:
|
||||
n = sub_rk.slice
|
||||
depth += 1
|
||||
continue
|
||||
return 0
|
||||
return 0
|
||||
def reject_ref_marker_position(self, node: ast.AST*, imported_modules: str, where: str) -> int:
|
||||
"""Fail fast when a reference marker is used where it is not allowed."""
|
||||
|
||||
Reference in new issue
Block a user