diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index 2c096f6..45e406a 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -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 diff --git a/App/lib/core/Gen/Class/ClassGenDef.vp b/App/lib/core/Gen/Class/ClassGenDef.vp index 2c27567..758e3f5 100644 --- a/App/lib/core/Gen/Class/ClassGenDef.vp +++ b/App/lib/core/Gen/Class/ClassGenDef.vp @@ -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 diff --git a/App/lib/core/Gen/GenApi.vp b/App/lib/core/Gen/GenApi.vp index 44a49fb..b8a5c53 100644 --- a/App/lib/core/Gen/GenApi.vp +++ b/App/lib/core/Gen/GenApi.vp @@ -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 diff --git a/App/lib/core/Gen/GenTypes.vp b/App/lib/core/Gen/GenTypes.vp index a4ab918..a5c7256 100644 --- a/App/lib/core/Gen/GenTypes.vp +++ b/App/lib/core/Gen/GenTypes.vp @@ -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 # ============================================================ diff --git a/App/lib/core/Gen/Type/TypeGenResolve.vp b/App/lib/core/Gen/Type/TypeGenResolve.vp index 393fb82..68e4a38 100644 --- a/App/lib/core/Gen/Type/TypeGenResolve.vp +++ b/App/lib/core/Gen/Type/TypeGenResolve.vp @@ -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."""