From d4c1e3f5354330af73d061988309d461bf0f3345 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Thu, 5 Nov 2026 17:43:08 +0800 Subject: [PATCH] fix(obs): resolve method/module-qualified return annotations and static returns `ret_annot_ref_kind` now resolves plain calls (current module, from-imports, re-exports), method calls (receiver class from its annotation, `.vpi` method entry), and module-qualified calls (`mod.fn` through the module alias). A reference binding or a reference return now requires a verified `-> &T` / `-> &mut T` callee: an unresolvable callee is reported instead of being silently accepted, and `return ` first checks the callee's return kind, then its borrowed arguments. Module globals may be returned as `&g` (returning the value is rejected with that hint), since static storage outlives the frame. The per-file OBS flag is passed explicitly to `reject_ref_marker_position`, so a `-> &T` in one file is no longer rejected when another file's stub has reset the module-level flag. `&temp T` participates in borrow tracking, `obj.f = obj.f` no longer frees the value it stores back, and own-field chains release up to 32 levels. The LSP mirrors the method/module return checks and the static-return rules. --- App/lib/core/Gen/AssignGenMixin.vp | 57 ++++++++++++--- App/lib/core/Gen/Call/CallGenBase.vp | 93 +++++++++++++++++++++---- App/lib/core/Gen/Class/ClassGenDef.vp | 2 +- App/lib/core/Gen/Func/FuncGenBase.vp | 2 +- App/lib/core/Gen/TranslatorGenMixin.vp | 2 +- App/lib/core/Gen/Type/TypeGenResolve.vp | 11 ++- App/lib/lsp/LspChecks.vp | 28 +++++++- 7 files changed, 167 insertions(+), 28 deletions(-) diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index e2c2227..ad3c3a6 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -1759,6 +1759,14 @@ novirtual class AssignGenMixin: """Structural borrow-elision check for reference-returning OBS functions.""" if trans is None or fd is None or value is None: return 0 if value.kind() == ast.ASTKind.Call: + # The call itself must return a reference, otherwise the function hands out a value. + call_rk: int = self.Gen.CallGenBase.ret_annot_ref_kind(trans, ast.Call*(value)) + if call_rk == 0: + self.Gen.TypeGenErr.fatal_error(value, "cannot return this call from a reference-returning function: the callee returns a value/pointer, not `&T` / `&mut T`", "LifetimeError") + return 0 + if call_rk < 0: + self.Gen.TypeGenErr.fatal_error(value, "cannot return this call from a reference-returning function: the callee's return type cannot be verified (no resolvable `.vpi` entry)", "LifetimeError") + return 0 # The callee returns a reference to one of its borrowed arguments: that argument must # outlive this frame (a reference parameter / `self` / a heap-owned value). cargs: list[ast.AST*]* = (ast.Call*)(value).args @@ -1782,6 +1790,14 @@ novirtual class AssignGenMixin: if par is not None and self.Gen.TypeGenResolve.ref_kind(par.annotation) != 0: return 0 self.Gen.TypeGenErr.fatal_error(value, f"cannot return `{nm.id}` from a reference-returning function: parameter '{nm.id}' is stored by value in this frame, so the result would dangle. Declare the parameter as `&T` / `&mut T`", "LifetimeError") return 0 + if self._IsStaticName(trans, nm.id) != 0: + # A static pointer value may be returned as-is; a static value needs its address. + gv: mlirlite.GlobalVariable* = self.Gen.ExprGenAttr.find_global_in_module(trans.Module, nm.id) + if gv is not None and gv.Ty is not None and gv.Ty.Pointee is not None \ + and self.Gen.ExprGenBase.is_ptr_type(gv.Ty.Pointee) != 0: + return 0 + self.Gen.TypeGenErr.fatal_error(value, f"`{nm.id}` is a module global: return `&{nm.id}` to borrow it", "LifetimeError") + return 0 # A reference binding: allowed when the object it borrows outlives this frame. if self.Gen.VarGen.var_ref_kind(trans.SymTab, nm.id) != 0: own_r: str = self.Gen.StmtGen._BorrowOwnerOf(trans, nm.id) @@ -1803,6 +1819,13 @@ novirtual class AssignGenMixin: bn: ast.Name* = ast.Name*(at_r.value) if bn.id is not None and string.strcmp(bn.id, "self") == 0: return 0 if value.kind() == ast.ASTKind.UnaryOp: + uo_r: ast.UnaryOp* = ast.UnaryOp*(value) + if (uo_r.op == ast.OpKind.AddrOf or uo_r.op == ast.OpKind.AddrOfMut) \ + and uo_r.operand is not None and uo_r.operand.kind() == ast.ASTKind.Name: + onm_r: ast.Name* = ast.Name*(uo_r.operand) + if onm_r.id is not None and self._IsStaticName(trans, onm_r.id) != 0: + # `&moduleGlobal` points at static storage, which outlives the frame. + return 0 self.Gen.TypeGenErr.fatal_error(value, "cannot return the address of a frame binding from a reference-returning function: return the reference parameter itself (`return p`) or the stored reference (`return self.field`)", "LifetimeError") return 0 self.Gen.TypeGenErr.fatal_error(value, "cannot return this expression from a reference-returning function: a reference result must borrow a reference parameter, `self`, or another reference", "LifetimeError") @@ -1848,6 +1871,10 @@ novirtual class AssignGenMixin: def _OwnerOutlivesFrame(self, trans: HT.Translator*, fd: ast.FunctionDef*, name: str) -> int: if name is None: return -1 if string.strcmp(name, "self") == 0: return 1 + # Module globals / statics live for the whole program, so a borrow of one may escape. + if GenApi.is_global_name(trans, name) != 0: return 1 + if self._is_module_scope_var(trans, name) != 0: return 1 + if GenApi.is_nonlocal_name(trans, name) != 0: return 1 pi: int = self._ParamIndexByName(fd, name) if pi >= 0: par: ast.Arg* = self._ParamAt(fd, pi) @@ -1859,6 +1886,14 @@ novirtual class AssignGenMixin: return 1 return 0 + # _IsStaticName - 1 for module globals / nonlocal storage (lifetime = whole program). + def _IsStaticName(self, trans: HT.Translator*, name: str) -> int: + if trans is None or name is None: return 0 + if GenApi.is_global_name(trans, name) != 0: return 1 + if self._is_module_scope_var(trans, name) != 0: return 1 + if GenApi.is_nonlocal_name(trans, name) != 0: return 1 + return 0 + # _ParamAt - parameter node at a 0-based position (None when out of range). def _ParamAt(self, fd: ast.FunctionDef*, idx: int) -> ast.Arg*: if fd is None or idx < 0: return None @@ -2925,7 +2960,7 @@ novirtual class AssignGenMixin: annot: ast.AST* = aa.annotation if annot is not None: # Value-reference markers are parameter-only: a local variable `x: &T` fails fast. - self.Gen.TypeGenResolve.reject_ref_marker_position(annot, trans._imported_modules, "a local variable declaration") + self.Gen.TypeGenResolve.reject_ref_marker_position(annot, trans._imported_modules, "a local variable declaration", trans.StrictOBS) # Specialize generics before resolving so list[X] does not degrade to opaque list* self._prespecialize_generic_annotation(trans, pool, annot) resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, annot, trans._imported_modules, trans._from_imports, trans, 0) @@ -2970,9 +3005,7 @@ novirtual class AssignGenMixin: # whose return annotation is `&T` / `&mut T` may initialize one. call_rk: int = self.Gen.CallGenBase.ret_annot_ref_kind(trans, ast.Call*(aa.value)) if call_rk < 0: - # Unknown callee (foreign/extern): keep the permissive rule. - ok_ref = 1 - src_rk = rk_decl + self.Gen.TypeGenErr.fatal_error(aa.value, f"`{nm.id}` is a reference: cannot verify that this call returns a reference (the callee has no resolvable `.vpi` entry); only a function/method declared `-> &T` / `-> &mut T` can initialize it", "TypeError") elif call_rk == 0: self.Gen.TypeGenErr.fatal_error(aa.value, f"`{nm.id}` is a reference: the call returns a value/pointer, not a reference; only a function returning `&T` / `&mut T` can initialize it", "TypeError") else: @@ -3162,7 +3195,7 @@ novirtual class AssignGenMixin: annot: ast.AST* = aa.annotation if annot is not None: # Value-reference markers are parameter-only: a local variable `x: &T` fails fast. - self.Gen.TypeGenResolve.reject_ref_marker_position(annot, trans._imported_modules, "a local variable declaration") + self.Gen.TypeGenResolve.reject_ref_marker_position(annot, trans._imported_modules, "a local variable declaration", trans.StrictOBS) # Same as PreScan: specialize generics first so list[X] does not degrade to bare list* self._prespecialize_generic_annotation(trans, pool, annot) resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, annot, trans._imported_modules, trans._from_imports, trans, 0) @@ -3646,7 +3679,7 @@ novirtual class AssignGenMixin: self.Gen.TypeGenErr.fatal_error(aa.value, "the annotated target is not nullable: in an OBS file `None` requires a nullable type (`T?`)", "TypeError") return 0 # 与 Name 路径同口径解析注解:先特化泛型(避免 list[X] 退化成裸 list*),解析失败即 fail-fast。 - self.Gen.TypeGenResolve.reject_ref_marker_position(annot, trans._imported_modules, "an annotated assignment to an attribute/subscript") + self.Gen.TypeGenResolve.reject_ref_marker_position(annot, trans._imported_modules, "an annotated assignment to an attribute/subscript", trans.StrictOBS) self._prespecialize_generic_annotation(trans, pool, annot) ann_ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, annot, trans._imported_modules, trans._from_imports, trans, 0) if ann_ty is None: @@ -4346,9 +4379,17 @@ novirtual class AssignGenMixin: else: self.Gen.StmtGen._BorrowAdd(trans, own_bf, brw_bf, src_rf, asgn.value) # Overwriting an `own` field releases the value it held. Inside a - # constructor the memory is still uninitialized, so nothing is released. + # constructor the memory is still uninitialized, so nothing is released; + # `obj.f = obj.f` is a no-op and must not free the value it stores back. if fe_rf is not None and fe_rf.IsOwn != 0 and self._in_ctor(trans) == 0: - self._release_own_slot(pool, builder, mod, trans, field_ptr) + tgt_p: str = self.Gen.StmtGen._BorrowPathName(pool, target) + src_p: str = self.Gen.StmtGen._BorrowPathName(pool, asgn.value) + same_p: int = 0 + if tgt_p is not None and src_p is not None \ + and string.strcmp(tgt_p, src_p) == 0: + same_p = 1 + if same_p == 0: + self._release_own_slot(pool, builder, mod, trans, field_ptr) mlirlite.build_store(builder, store_val, field_ptr, vol_fw) own_tgt: int = 0 if fe_rf is not None and fe_rf.IsOwn != 0: own_tgt = 1 diff --git a/App/lib/core/Gen/Call/CallGenBase.vp b/App/lib/core/Gen/Call/CallGenBase.vp index f5c4224..cac7028 100644 --- a/App/lib/core/Gen/Call/CallGenBase.vp +++ b/App/lib/core/Gen/Call/CallGenBase.vp @@ -1320,18 +1320,8 @@ novirtual class CallGenBase: if ret_ty is None: ret_ty = self._resolve_generic_annot_str(trans, pool, annot) return ret_ty - # ret_annot_ref_kind - reference kind of a call's return annotation: 0 = value/pointer, - # 1 = `&T`, 2 = `&mut T`, -1 = callee not resolvable (foreign/extern/unknown). - def ret_annot_ref_kind(self, trans: HT.Translator*, call: ast.Call*) -> int: - if trans is None or call is None or call.func is None: return -1 - if call.func.kind() != ast.ASTKind.Name: return -1 - fname: str = (ast.Name*)(call.func).id - if fname is None: return -1 - sha1: char* = trans.ModuleSha1 - if sha1 is None: return -1 - if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0: - VpiReader._ensure_vpi_ret_annot_loaded(sha1, self.Gen.CallGenBase.TempDir) - annot: char* = VpiReader._lookup_vpi_ret_annot(sha1, None, fname) + # _ret_annot_kind_text - `&T` / `&mut T` / other for a vpi return annotation. + def _ret_annot_kind_text(self, annot: char*) -> int: if annot is None: return -1 s: str = annot while string.strlen(s) > 0 and s[0] == ' ': s += 1 @@ -1340,6 +1330,85 @@ novirtual class CallGenBase: if string.strncmp(s, "&mut ", 5) == 0: return 2 return 1 + # _lookup_ret_annot_any - return annotation of `fname` in `sha1`, then a from-import module, + # then a re-export facade; None when no stub knows the name. + def _lookup_ret_annot_any(self, trans: HT.Translator*, sha1: char*, fname: char*) -> char*: + if trans is None or fname is None: return None + td: str = self.Gen.CallGenBase.TempDir + if sha1 is not None and td is not None and self.Gen.CallGenBase.TempDirLen > 0: + VpiReader._ensure_vpi_ret_annot_loaded(sha1, td) + hit: char* = VpiReader._lookup_vpi_ret_annot(sha1, None, fname) + if hit is not None: return hit + # `from mod import f`: the annotation lives in the defining module's stub. + if trans._from_imports is not None: + mod: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, fname) + if mod is not None: + m_sha: str = self.Gen.StructGen._resolve_module_sha1(mod, trans._from_imports) + if m_sha is not None: + if td is not None and self.Gen.CallGenBase.TempDirLen > 0: + VpiReader._ensure_vpi_ret_annot_loaded(m_sha, td) + hit2: char* = VpiReader._lookup_vpi_ret_annot(m_sha, None, fname) + if hit2 is not None: return hit2 + # Re-export facade (`io.vp` forwards `_io`): follow the registered source module. + if sha1 is not None: + src_mod: str = self.Gen.ImportsGen.lookup_reexport_call(sha1, fname) + if src_mod is not None: + s_sha: str = self.Gen.StructGen._resolve_module_sha1(src_mod, trans._from_imports) + if s_sha is not None: + if td is not None and self.Gen.CallGenBase.TempDirLen > 0: + VpiReader._ensure_vpi_ret_annot_loaded(s_sha, td) + hit3: char* = VpiReader._lookup_vpi_ret_annot(s_sha, None, fname) + if hit3 is not None: return hit3 + return None + + # _receiver_class - class name of a method receiver expression (`c`, `self`, `this`). + def _receiver_class(self, trans: HT.Translator*, expr: ast.AST*) -> str: + if trans is None or expr is None or expr.kind() != ast.ASTKind.Name: return None + nm: ast.Name* = ast.Name*(expr) + if nm.id is None: return None + ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id) + if ve is None: return None + cn: str = self.Gen.VarGen.var_annot_class_name(ve) + if cn is not None: return cn + slot: mlirlite.Value* = ve.Alloca + if slot is None or slot.Ty is None or slot.Ty.Pointee == 0: return None + t: mlirlite.LLVMType* = slot.Ty.Pointee + if self.Gen.ExprGenBase.is_ptr_type(t) != 0: t = t.Pointee + if t is None or t.kind() != mlirlite.LLVMType.Struct: return None + return self.Gen.StructGen.get_class_name_by_type(trans.Pool, t) + + # ret_annot_ref_kind - reference kind of a call's return annotation: 0 = value/pointer, + # 1 = `&T`, 2 = `&mut T`, -1 = callee not resolvable (foreign/extern/unknown). + def ret_annot_ref_kind(self, trans: HT.Translator*, call: ast.Call*) -> int: + if trans is None or call is None or call.func is None: return -1 + td: str = self.Gen.CallGenBase.TempDir + f: ast.AST* = call.func + if f.kind() == ast.ASTKind.Name: + fname: str = (ast.Name*)(f).id + return self._ret_annot_kind_text(self._lookup_ret_annot_any(trans, trans.ModuleSha1, fname)) + if f.kind() == ast.ASTKind.Attribute: + at: ast.Attribute* = ast.Attribute*(f) + if at.attr is None: return -1 + fname2: str = at.attr + base: ast.AST* = at.value + if base is not None and base.kind() == ast.ASTKind.Name: + bnm: ast.Name* = ast.Name*(base) + if bnm.id is not None: + # Module-qualified call `mod.fn(...)`. + m_sha: str = self.Gen.StructGen._resolve_module_sha1(bnm.id, trans._from_imports) + if m_sha is not None: + return self._ret_annot_kind_text(self._lookup_ret_annot_any(trans, m_sha, fname2)) + # Method call: resolve the receiver's class, then its stub. + cls: str = self._receiver_class(trans, base) + if cls is None: return -1 + c_sha: str = self.Gen.StructGen.get_struct_sha1(cls) + if c_sha is None: return -1 + if td is not None and self.Gen.CallGenBase.TempDirLen > 0: + VpiReader._ensure_vpi_ret_annot_loaded(c_sha, td) + return self._ret_annot_kind_text(VpiReader._lookup_vpi_ret_annot(c_sha, cls, fname2)) + return -1 + return -1 + def _vpi_module_defines(self, mod_sha1: char*, func_name: char*) -> int: """Return 1 when the module's .vpi declares a module-level function with this name.""" if mod_sha1 is None or func_name is None: return 0 diff --git a/App/lib/core/Gen/Class/ClassGenDef.vp b/App/lib/core/Gen/Class/ClassGenDef.vp index 0528ba0..d6f7584 100644 --- a/App/lib/core/Gen/Class/ClassGenDef.vp +++ b/App/lib/core/Gen/Class/ClassGenDef.vp @@ -294,7 +294,7 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase): # Resolve the field type # Value-reference markers are parameter-only: a struct field `x: &T` fails fast. - self.Gen.TypeGenResolve.reject_ref_marker_position(aa.annotation, trans._imported_modules, "a struct field") + self.Gen.TypeGenResolve.reject_ref_marker_position(aa.annotation, trans._imported_modules, "a struct field", trans.StrictOBS) field_ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, aa.annotation, trans._imported_modules, trans._from_imports, trans, 0) # void is not a valid field type (only for `void*` and opaque typedefs); letting # it through builds an illegal layout, so fail-fast at compile time here. diff --git a/App/lib/core/Gen/Func/FuncGenBase.vp b/App/lib/core/Gen/Func/FuncGenBase.vp index 4a203f6..8e6ef7a 100644 --- a/App/lib/core/Gen/Func/FuncGenBase.vp +++ b/App/lib/core/Gen/Func/FuncGenBase.vp @@ -307,7 +307,7 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase): ret_ty: mlirlite.LLVMType* = None if fd.returns is not None: # Value-reference markers are parameter-only: `-> &T` fails fast (never a silent Ptr(T) return). - self.Gen.TypeGenResolve.reject_ref_marker_position(fd.returns, imported_modules, "a function return type") + self.Gen.TypeGenResolve.reject_ref_marker_position(fd.returns, imported_modules, "a function return type", trans.StrictOBS) ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, fd.returns, imported_modules, from_imports, trans, 0) # A lone ExportT annotation is an export modifier with no real type -> Void. if fd.returns.kind() == ast.ASTKind.Name: diff --git a/App/lib/core/Gen/TranslatorGenMixin.vp b/App/lib/core/Gen/TranslatorGenMixin.vp index aa0c73b..8b73dba 100644 --- a/App/lib/core/Gen/TranslatorGenMixin.vp +++ b/App/lib/core/Gen/TranslatorGenMixin.vp @@ -570,7 +570,7 @@ novirtual class TranslatorGenMixin: # Resolve the type if aa.annotation is not None: # Value-reference markers are parameter-only: a module variable `x: &T` fails fast. - self.Gen.TypeGenResolve.reject_ref_marker_position(aa.annotation, trans._imported_modules, "a module-level variable declaration") + self.Gen.TypeGenResolve.reject_ref_marker_position(aa.annotation, trans._imported_modules, "a module-level variable declaration", trans.StrictOBS) resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, aa.annotation, trans._imported_modules, trans._from_imports, trans, 0) if resolved is not None: # Object-class module vars (@t.Object/@t.VTableT: object-as-pointer) wrap the diff --git a/App/lib/core/Gen/Type/TypeGenResolve.vp b/App/lib/core/Gen/Type/TypeGenResolve.vp index 4394548..dbc178b 100644 --- a/App/lib/core/Gen/Type/TypeGenResolve.vp +++ b/App/lib/core/Gen/Type/TypeGenResolve.vp @@ -1384,6 +1384,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): while n is not None and depth < 8: m: int = self._ref_marker_name_mode(n) if m == 1: return 1 + if m == 2: return 1 if m == 3: return 2 if n.kind() != ast.ASTKind.Subscript: return 0 sub_rk: ast.Subscript* = ast.Subscript*(n) @@ -1404,12 +1405,16 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if uo_re.op == ast.OpKind.AddrOf: return 1 if uo_re.op == ast.OpKind.AddrOfMut: return 2 return 0 - def reject_ref_marker_position(self, node: ast.AST*, imported_modules: str, where: str) -> int: + def reject_ref_marker_position(self, node: ast.AST*, imported_modules: str, where: str, + obs_mode: int = -1) -> int: """Fail fast when a reference marker is used where it is not allowed.""" mode: int = self._ref_marker_name_mode(node) if mode == 0: return 0 - # OBS references are ordinary types in every annotation position. - if GenApi.strict_obs() != 0: return 0 + # OBS references are ordinary types in every annotation position. The caller passes the + # translator's per-file flag: the module-level one can be clobbered by another file's stub. + obs: int = obs_mode + if obs < 0: obs = GenApi.strict_obs() + if obs != 0: return 0 if mode == 3: self.Gen.TypeGenErr.fatal_error(node, "mutable references (`&mut T`) require `from __future__ import __obs__`", "TypeError") return 1 diff --git a/App/lib/lsp/LspChecks.vp b/App/lib/lsp/LspChecks.vp index 25baf4a..ac2d273 100644 --- a/App/lib/lsp/LspChecks.vp +++ b/App/lib/lsp/LspChecks.vp @@ -1656,6 +1656,14 @@ def _ParamRefFlags(pool: memhub.MemManager*, detail: str) -> str: return str(buf) +def _IsModuleScopeName(pool: memhub.MemManager*, a: LspSymbols.LspAnalysis*, node: ast.AST*) -> int: + """1 when `node` names a module-level binding (static storage).""" + if node is None or node.kind() != ast.ASTKind.Name: return 0 + occ: LspSymbols.LspOccurrence* = _OccAtExpr(a, node) + if occ is None or occ.sym is None or occ.sym.scope is None: return 0 + return 1 if occ.sym.scope.kind == LspSymbols.LSP_SCOPE_MODULE else 0 + + def _CallReturnsReference(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*, a: LspSymbols.LspAnalysis*, ws: LspWorkspace.LspWorkspace*, table_pool: memhub.MemManager*, call: ast.AST*) -> int: @@ -1672,12 +1680,18 @@ def _CallReturnsReference(pool: memhub.MemManager*, doc: LspDocuments.LspDocumen def _CheckRefReturn(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*, - a: LspSymbols.LspAnalysis*, ret_ann: str, value: ast.AST*, + a: LspSymbols.LspAnalysis*, ws: LspWorkspace.LspWorkspace*, + table_pool: memhub.MemManager*, ret_ann: str, value: ast.AST*, errors: list[ast.ParseError*]*) -> None: """A `-> &T` result may only borrow a reference binding, `self`, or a call.""" if value is None: return k: int = value.kind() if value.kind() == ast.ASTKind.Call: + cr: int = _CallReturnsReference(pool, doc, a, ws, table_pool, value) + if cr == 0: + _EmitTypeError(pool, doc, value, + "cannot return this call from a reference-returning function: the callee returns a value/pointer, not `&T` / `&mut T`", errors) + return args: list[ast.AST*]* = (ast.Call*)(value).args if args is not None: ai: int = 0 @@ -1698,6 +1712,12 @@ def _CheckRefReturn(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*, nm: ast.Name* = ast.Name*(value) if nm.id is None: return if string.strcmp(nm.id, "self") == 0: return + if _IsModuleScopeName(pool, a, value) != 0: + det_g: str = _SymDetailAt(pool, a, value) + if det_g is not None and string.strchr(det_g, '*') is not None: return + _EmitTypeError(pool, doc, value, + f"`{nm.id}` is a module global: return `&{nm.id}` to borrow it", errors) + return det: str = _SymDetailAt(pool, a, value) if _RefDetail(det) != 0 or _DetailIsOwn(det) != 0: return _EmitTypeError(pool, doc, value, @@ -1711,6 +1731,10 @@ def _CheckRefReturn(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*, "cannot return a field of a frame-local value from a reference-returning function", errors) return if k == ast.ASTKind.UnaryOp: + uo_r: ast.UnaryOp* = ast.UnaryOp*(value) + if (uo_r.op == ast.OpKind.AddrOf or uo_r.op == ast.OpKind.AddrOfMut) \ + and uo_r.operand is not None and _IsModuleScopeName(pool, a, uo_r.operand) != 0: + return _EmitTypeError(pool, doc, value, "cannot return the address of a frame binding from a reference-returning function: return the reference parameter itself (`return p`)", errors) @@ -2206,7 +2230,7 @@ def _WalkChecks(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*, if k == ast.ASTKind.Return: rt: ast.Return* = ast.Return*(s) if rt.value is not None and _RefKindOfText(ret_ann) != 0: - _CheckRefReturn(pool, doc, a, ret_ann, rt.value, errors) + _CheckRefReturn(pool, doc, a, ws, table_pool, ret_ann, rt.value, errors) if rt.value is not None and ret_ann is not None \ and _NullableDetail(ret_ann) == 0 \ and _NullableDetail(_SymDetailAt(pool, a, rt.value)) != 0: