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 <call>` 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.
This commit is contained in:
1 parent
5c6c6c9619
commit
d4c1e3f535
7 files changed
+167
-28
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user