feat(obs): require an explicit borrow to create a reference

`b: &U = a` now fails with a borrow hint; only `&value` (or `&ownedPtr`) / another reference may initialize a reference. Copying a reference extends the original borrow to the new binding.
This commit is contained in:
TermiNexus committed 2026-11-04 18:13:46 +08:00
1 parent 6f45fc016b
commit e650d8dd45
2 files changed
+36 -4

No files matched your search

+24 -4
View File
@@ -1064,15 +1064,16 @@ novirtual class AssignGenMixin:
return self.Gen.TypeGenResolve.ref_kind(annot)
def _BorrowOwnerName(self, expr: ast.AST*) -> str:
"""Borrowed variable of `r: &T = x` / `r: &T = &x`; None when the source is not a binding."""
"""Borrowed variable of `r: &T = &x` / `r: &T = &*p`; None when the source is not a binding."""
if expr is None: return None
if expr.kind() == ast.ASTKind.Name:
return (ast.Name*)(expr).id
if expr.kind() == ast.ASTKind.UnaryOp:
uo_b: ast.UnaryOp* = ast.UnaryOp*(expr)
if uo_b.op == ast.OpKind.AddrOf and uo_b.operand is not None \
and uo_b.operand.kind() == ast.ASTKind.Name:
return (ast.Name*)(uo_b.operand).id
if uo_b.op == ast.OpKind.AddrOf:
return self._BorrowOwnerName(uo_b.operand)
if uo_b.op == ast.OpKind.Deref:
return self._BorrowOwnerName(uo_b.operand)
return None
def _move_out_assigned_source(self, trans: HT.Translator*, rhs: ast.AST*,
@@ -2746,7 +2747,26 @@ novirtual class AssignGenMixin:
rk_decl: int = self._RefKindOf(annot)
if rk_decl != 0:
self.Gen.VarGen.set_var_ref_kind(trans.SymTab, nm.id, rk_decl)
# A reference must be created from an explicit borrow (`&value` / `&*ptr`) or
# another reference; `b: &U = a` must not silently take the address.
ok_ref: int = 0
if aa.value is not None:
if aa.value.kind() == ast.ASTKind.UnaryOp:
uo_i: ast.UnaryOp* = ast.UnaryOp*(aa.value)
if uo_i.op == ast.OpKind.AddrOf: ok_ref = 1
elif aa.value.kind() == ast.ASTKind.Name:
nm_i: ast.Name* = ast.Name*(aa.value)
if nm_i.id is not None and self.Gen.VarGen.var_ref_kind(trans.SymTab, nm_i.id) != 0:
ok_ref = 1
if ok_ref == 0:
self.Gen.TypeGenErr.fatal_error(aa.value, f"a reference must be initialized from a borrow: write `{nm.id}: &T = &value` (or `&ownedPtr`)", "TypeError")
owner_b: str = self._BorrowOwnerName(aa.value)
if aa.value is not None and aa.value.kind() == ast.ASTKind.Name:
# Copying a reference extends the original borrow to the new binding.
nm_r: ast.Name* = ast.Name*(aa.value)
if nm_r.id is not None:
o_ref: str = self.Gen.StmtGen._BorrowOwnerOf(trans, nm_r.id)
if o_ref is not None: owner_b = o_ref
if owner_b is not None:
self.Gen.StmtGen._BorrowAdd(trans, owner_b, nm.id)
# Store the annotation's class name; pass pool so generic Subscript yields the mangled name
+12
View File
@@ -85,6 +85,18 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
if trans is None: return 0
return _BorrowTableDrop(trans._cur_func, borrower)
def _BorrowOwnerOf(self, trans: HT.Translator*, borrower: str) -> str:
"""Owner borrowed by a reference variable (None when it holds no registered borrow)."""
global _g_borrow_count
if trans is None or borrower is None or _g_borrow_funcs is None: return None
i: int = 0
while i < _g_borrow_count:
if _g_borrow_funcs[i] == sizet(trans._cur_func) and _g_borrow_borrowers[i] is not None \
and string.strcmp(_g_borrow_borrowers[i], borrower) == 0:
return _g_borrow_owners[i]
i += 1
return None
def _BorrowLiveBorrower(self, trans: HT.Translator*, owner: str) -> str:
"""Name of a still-declared reference that borrows `owner` (None when none)."""
global _g_borrow_count