From e650d8dd45c9ede853f35b0c2aad3bd3c07ce9e6 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Wed, 4 Nov 2026 18:13:46 +0800 Subject: [PATCH] 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. --- App/lib/core/Gen/AssignGenMixin.vp | 28 +++++++++++++++++++++++---- App/lib/core/Gen/Stmt/StmtGenMixin.vp | 12 ++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index 0a83584..2c096f6 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -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 diff --git a/App/lib/core/Gen/Stmt/StmtGenMixin.vp b/App/lib/core/Gen/Stmt/StmtGenMixin.vp index afbb48c..80ba5ee 100644 --- a/App/lib/core/Gen/Stmt/StmtGenMixin.vp +++ b/App/lib/core/Gen/Stmt/StmtGenMixin.vp @@ -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