fix(obs): close reference-return and ownership soundness holes
Reference returns are checked structurally instead of relying on the frame tag, so `return &x` and `return x` for a by-value parameter are rejected while a reference parameter, `self`, and a call still pass. Borrows created by a call result now register the borrowed arguments, so the owner cannot be deleted while the reference lives. `own` rejects `new memgr(...)` storage (pool memory is not freed by `free`) and raw-pointer adoption; `own` arguments reject memgr too. Overwriting an `own` field releases the value it held (not during construction), and `del` of an `own` field is rejected. Reference parameters registered under their source name as well, so passing a borrow to an `&T` parameter no longer trips the stack-escape check. The LSP mirrors the new return and adoption rules.
This commit is contained in:
1 parent
a2a04f567e
commit
1d668497a5
5 files changed
+187
-33
No files matched your search
@@ -1430,14 +1430,12 @@ novirtual class AssignGenMixin:
|
||||
if GenApi.is_nonlocal_name(trans, onm.id) != 0: return GenTypes.LIFE_UNKNOWN
|
||||
slot_lo: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, onm.id)
|
||||
if slot_lo is not None:
|
||||
# `&ownedPtr` yields the heap object pointer (one load), not the frame slot.
|
||||
# `&ownedPtr` / `&heapPtr` yields the heap object pointer (one load),
|
||||
# not the frame slot; a plain value/parameter is still frame-local.
|
||||
if trans.StrictOBS != 0 and slot_lo.Ty is not None:
|
||||
match slot_lo.Ty:
|
||||
case mlirlite.LLVMType.Ptr(lo1):
|
||||
match lo1:
|
||||
case mlirlite.LLVMType.Ptr(_): return GenTypes.LIFE_HEAP
|
||||
case _: pass
|
||||
case _: pass
|
||||
if self.Gen.VarGen._SlifeOwned(slot_lo) != 0 \
|
||||
or self.Gen.VarGen.get_var_life(trans.SymTab, onm.id) == GenTypes.LIFE_HEAP:
|
||||
return GenTypes.LIFE_HEAP
|
||||
return GenTypes.LIFE_FRAME
|
||||
return GenTypes.LIFE_STATIC
|
||||
return GenTypes.LIFE_UNKNOWN
|
||||
@@ -1644,8 +1642,9 @@ novirtual class AssignGenMixin:
|
||||
fd: ast.AST* = self.Gen.StructGen.NllCurFunc
|
||||
if fd is None: return 0
|
||||
# OBS has no `@life` contracts: a returned borrow must come from a reference parameter or
|
||||
# `self`, which is the elided lifetime rule for reference return types.
|
||||
if GenApi.strict_obs() != 0:
|
||||
# `self`, which is the elided lifetime rule for reference return types. Use the translator's
|
||||
# per-file flag: the module-level one can be clobbered by another file's vpi pass.
|
||||
if trans.StrictOBS != 0:
|
||||
return self._check_return_borrow_obs(trans, ast.FunctionDef*(fd), value)
|
||||
rv_ty: mlirlite.LLVMType* = None
|
||||
if trans._cur_func is not None: rv_ty = mlirlite.function_get_ret_ty(trans._cur_func)
|
||||
@@ -1709,6 +1708,9 @@ novirtual class AssignGenMixin:
|
||||
def _check_return_borrow_obs(self, trans: HT.Translator*, fd: ast.FunctionDef*, value: ast.AST*) -> int:
|
||||
"""Reject a frame-local return in an OBS file; borrow elision replaces `@life`."""
|
||||
if trans is None or fd is None or value is None: return 0
|
||||
ret_rk: int = self.Gen.TypeGenResolve.ref_kind(fd.returns)
|
||||
if ret_rk != 0:
|
||||
return self._check_ref_return_obs(trans, fd, value)
|
||||
rv_ty: mlirlite.LLVMType* = None
|
||||
if trans._cur_func is not None: rv_ty = mlirlite.function_get_ret_ty(trans._cur_func)
|
||||
life_r: int = self.life_of_expr(trans, value, rv_ty)
|
||||
@@ -1727,30 +1729,62 @@ novirtual class AssignGenMixin:
|
||||
if n_pure is not None and n_pure.id is not None: nm_pure = n_pure.id
|
||||
self.Gen.TypeGenErr.fatal_error(value, f"pure value escapes: `{nm_pure}` is borrowed from the caller; pure results must be owned value copies. Return a newly allocated value instead", "LifetimeError")
|
||||
return 0
|
||||
ret_rk: int = self.Gen.TypeGenResolve.ref_kind(fd.returns)
|
||||
pi: int = self.Gen.StructGen._ParamIndex(fd, value)
|
||||
nm_txt: str = "expression"
|
||||
nm_pre: str = ""
|
||||
self.Gen.TypeGenErr.fatal_error(value, "stack address escapes: this value is frame-local and OBS files have no `@life(ret(...))` contracts. Return a heap allocation (`new ...`), a reference parameter, or a value copy", "LifetimeError")
|
||||
return 0
|
||||
|
||||
# _ParamIndexByName - parameter position by name (None when not a parameter).
|
||||
def _ParamIndexByName(self, fd: ast.FunctionDef*, name: str) -> int:
|
||||
if fd is None or name is None: return -1
|
||||
agr: ast.Arguments* = ast.Arguments*(fd.args)
|
||||
if agr is None or agr.args is None: return -1
|
||||
i: int = 0
|
||||
while i < int(len(agr.args)):
|
||||
pa: ast.AST* = agr.args.get(sizet(i))
|
||||
if pa is not None and pa.kind() == ast.ASTKind.Arg:
|
||||
ar: ast.Arg* = ast.Arg*(pa)
|
||||
if ar.arg is not None and string.strcmp(ar.arg, name) == 0: return i
|
||||
i += 1
|
||||
return -1
|
||||
|
||||
# _check_ref_return_obs - a `-> &T` result may only borrow a reference parameter or `self`.
|
||||
def _check_ref_return_obs(self, trans: HT.Translator*, fd: ast.FunctionDef*, value: ast.AST*) -> int:
|
||||
"""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: return 0
|
||||
if value.kind() == ast.ASTKind.Name:
|
||||
n2: ast.Name* = ast.Name*(value)
|
||||
if n2.id is not None: nm_txt = n2.id
|
||||
elif value.kind() == ast.ASTKind.UnaryOp:
|
||||
u2: ast.UnaryOp* = ast.UnaryOp*(value)
|
||||
if u2.operand is not None and u2.operand.kind() == ast.ASTKind.Name:
|
||||
n3: ast.Name* = ast.Name*(u2.operand)
|
||||
if n3.id is not None:
|
||||
nm_txt = n3.id
|
||||
nm_pre = "&"
|
||||
if ret_rk != 0 and pi >= 0:
|
||||
# Borrow elision: returning a reference parameter borrows the caller's value.
|
||||
par: ast.Arg* = self._ParamAt(fd, pi)
|
||||
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_txt}` from a reference-returning function: parameter '{nm_txt}' is stored by value in this frame, so the result would dangle. Declare the parameter as `&T` / `&mut T`", "LifetimeError")
|
||||
nm: ast.Name* = ast.Name*(value)
|
||||
if nm.id is None: return 0
|
||||
if string.strcmp(nm.id, "self") == 0: return 0
|
||||
pi: int = self._ParamIndexByName(fd, nm.id)
|
||||
if pi >= 0:
|
||||
par: ast.Arg* = self._ParamAt(fd, pi)
|
||||
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
|
||||
# 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)
|
||||
if own_r is not None:
|
||||
pi_o: int = self._ParamIndexByName(fd, own_r)
|
||||
if pi_o >= 0:
|
||||
par_o: ast.Arg* = self._ParamAt(fd, pi_o)
|
||||
if par_o is not None and self.Gen.TypeGenResolve.ref_kind(par_o.annotation) != 0: return 0
|
||||
else:
|
||||
slot_o: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, own_r)
|
||||
if slot_o is not None and (self.Gen.VarGen._SlifeOwned(slot_o) != 0 \
|
||||
or self.Gen.VarGen.get_var_life(trans.SymTab, own_r) == GenTypes.LIFE_HEAP):
|
||||
return 0
|
||||
self.Gen.TypeGenErr.fatal_error(value, f"cannot return `{nm.id}`: it lives in this frame, and a reference result may only borrow a reference parameter or `self` (borrow elision)", "LifetimeError")
|
||||
return 0
|
||||
if ret_rk != 0:
|
||||
self.Gen.TypeGenErr.fatal_error(value, f"cannot return `{nm_pre}{nm_txt}`: it lives in this frame, and a reference result may only borrow a reference parameter or `self` (borrow elision). Allocate the value with `new` and return an owned binding instead", "LifetimeError")
|
||||
if value.kind() == ast.ASTKind.Attribute:
|
||||
at_r: ast.Attribute* = ast.Attribute*(value)
|
||||
if at_r.value is not None and at_r.value.kind() == ast.ASTKind.Name:
|
||||
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:
|
||||
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, f"stack address escapes: `{nm_pre}{nm_txt}` is frame-local and OBS files have no `@life(ret(...))` contracts. Return a heap allocation (`new ...`), a reference parameter, or a value copy", "LifetimeError")
|
||||
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")
|
||||
return 0
|
||||
|
||||
# _ParamAt - parameter node at a 0-based position (None when out of range).
|
||||
@@ -1772,6 +1806,38 @@ novirtual class AssignGenMixin:
|
||||
self.Gen.VarGen._SlifeNoteStore(pool, builder, sh, 0)
|
||||
return 1
|
||||
|
||||
# _in_ctor - 1 when the current function is a constructor (`__init__` / `__before_init__`).
|
||||
def _in_ctor(self, trans: HT.Translator*) -> int:
|
||||
if trans is None or trans._cur_func is None: return 0
|
||||
fname: str = mlirlite.function_get_name(trans._cur_func)
|
||||
if fname is None: return 0
|
||||
if string.strstr(fname, ".__init__") is not None: return 1
|
||||
if string.strstr(fname, ".__before_init__") is not None: return 1
|
||||
return 0
|
||||
|
||||
# _release_own_slot - free the value an `own` field holds before the field is overwritten.
|
||||
def _release_own_slot(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*,
|
||||
mod: mlirlite.LLVMModule*, trans: HT.Translator*,
|
||||
field_ptr: mlirlite.Value*) -> int:
|
||||
if pool is None or builder is None or mod is None or trans is None or field_ptr is None: return 0
|
||||
if field_ptr.Ty is None or field_ptr.Ty.Pointee == 0: return 0
|
||||
fty: mlirlite.LLVMType* = field_ptr.Ty.Pointee
|
||||
if self.Gen.ExprGenBase.is_ptr_type(fty) == 0: return 0
|
||||
cur: mlirlite.Value* = mlirlite.build_load(builder, fty, field_ptr)
|
||||
if cur is None: return 0
|
||||
nulp: mlirlite.Value* = mlirlite.ConstNull(pool, fty, "null")
|
||||
if nulp is None: return 0
|
||||
live: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_NE, cur, nulp)
|
||||
if live is None: return 0
|
||||
one: mlirlite.Value* = mlirlite.const_int(pool, 8, 1)
|
||||
zero: mlirlite.Value* = mlirlite.const_int(pool, 8, 0)
|
||||
if one is None or zero is None: return 0
|
||||
flag: mlirlite.Value* = mlirlite.build_select(builder, live, one, zero)
|
||||
sh: mlirlite.Value* = mlirlite.build_alloca(builder, mlirlite.Int8(pool))
|
||||
if flag is None or sh is None: return 0
|
||||
mlirlite.build_store(builder, flag, sh)
|
||||
return self.Gen.VarGen._SlifeEmitGuardedFree(pool, builder, mod, trans, field_ptr, sh)
|
||||
|
||||
# _slife_note_assign - M3 赋值处的所有权记账(§4.2 RAII / §4.3 编译器独占释放)。
|
||||
# 只在**本地 Name 目标**的赋值落地处调用;目标槽 target_slot 即该变量的 alloca。
|
||||
# ① RHS = `new heap slife T(...)`:登记该槽(首次建影子标志)→ 覆盖释放旧拥有对象 → 影子置 1;
|
||||
@@ -1781,6 +1847,20 @@ novirtual class AssignGenMixin:
|
||||
def _slife_note_assign(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, target_slot: mlirlite.Value*, rhs_node: ast.AST*, target_name: str = None) -> int:
|
||||
"""M3:赋值处的 slife 所有权记账;返回 1=该槽此刻是编译器拥有的 slife 绑定"""
|
||||
if target_slot is None or rhs_node is None or trans is None: return 0
|
||||
# An `own` target only takes an owned value: a move from another `own` binding, `new ...`,
|
||||
# or a call that returns ownership. A raw pointer does not transfer ownership, so adopting
|
||||
# it would free memory the program still treats as borrowed/aliased.
|
||||
if target_name is not None and self.Gen.VarGen.var_is_own(trans.SymTab, target_name) != 0 \
|
||||
and rhs_node.kind() == ast.ASTKind.Name \
|
||||
and GenApi.is_none_literal(rhs_node) == 0:
|
||||
rn_own: ast.Name* = ast.Name*(rhs_node)
|
||||
if rn_own.id is not None:
|
||||
src_own: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, rn_own.id)
|
||||
src_is_own: int = self.Gen.VarGen.var_is_own(trans.SymTab, rn_own.id)
|
||||
if src_own is not None and self.Gen.VarGen._SlifeOwned(src_own) != 0: src_is_own = 1
|
||||
if src_is_own == 0:
|
||||
self.Gen.TypeGenErr.fatal_error(rhs_node, f"`{target_name}` is `own`: it can only take an owned value (move another `own` binding, allocate with `new heap hlife ...`, or call a function returning `own T`); a raw `{rn_own.id}` does not transfer ownership", "LifetimeError")
|
||||
return 0
|
||||
# pure_str: the owned resource is the payload field; ownership uses the same
|
||||
# shadow mechanism, but release goes through _pure_free.
|
||||
if self._pure_str_slot(pool, target_slot) != 0:
|
||||
@@ -1818,7 +1898,12 @@ novirtual class AssignGenMixin:
|
||||
# `own T`: explicit exclusive ownership (independent of the NLL demotion predicate).
|
||||
if target_name is not None and self.Gen.VarGen.var_is_own(trans.SymTab, target_name) != 0:
|
||||
if nn.Source == ast.NEW_SRC_STACK:
|
||||
self.Gen.TypeGenErr.fatal_error(rhs_node, "`own T` stores an exclusively owned heap object; `new stack` dies with the frame, use `new` (heap) or `new memgr(...)`", "LifetimeError")
|
||||
self.Gen.TypeGenErr.fatal_error(rhs_node, "`own T` stores an exclusively owned heap object; `new stack` dies with the frame, use `new heap hlife ...`", "LifetimeError")
|
||||
return 0
|
||||
if nn.Source == ast.NEW_SRC_MEMGR:
|
||||
# Pool memory is reclaimed by its manager, not by `free`; ownership would
|
||||
# release it through the wrong allocator.
|
||||
self.Gen.TypeGenErr.fatal_error(rhs_node, "`own T` cannot take a `new memgr(...)` allocation: that storage is reclaimed by the pool manager, not by `free`. Use `new heap hlife ...`, or keep the pool pointer as a plain `T*`", "LifetimeError")
|
||||
return 0
|
||||
owned = 1
|
||||
if owned != 0:
|
||||
@@ -2826,6 +2911,24 @@ novirtual class AssignGenMixin:
|
||||
if o_ref is not None: owner_b = o_ref
|
||||
if owner_b is not None and src_rk != 0:
|
||||
self.Gen.StmtGen._BorrowAdd(trans, owner_b, nm.id, src_rk, aa.value)
|
||||
# A call returning `&T` borrows one of its arguments; until a precise
|
||||
# return-borrow summary exists, every borrowed argument is treated as borrowed
|
||||
# by the result (conservative: `del` of any of them is blocked).
|
||||
if aa.value is not None and aa.value.kind() == ast.ASTKind.Call \
|
||||
and self.Gen.TypeGenResolve.ref_expr_kind(aa.value) == 0:
|
||||
c_args: list[ast.AST*]* = (ast.Call*)(aa.value).args
|
||||
if c_args is not None:
|
||||
ai_b: int = 0
|
||||
while ai_b < int(len(c_args)):
|
||||
arg_b: ast.AST* = c_args.get(sizet(ai_b))
|
||||
ai_b += 1
|
||||
ob: str = self._BorrowOwnerName(arg_b)
|
||||
if ob is None and arg_b is not None and arg_b.kind() == ast.ASTKind.Name:
|
||||
an_b: ast.Name* = ast.Name*(arg_b)
|
||||
if an_b.id is not None and self.Gen.VarGen.var_ref_kind(trans.SymTab, an_b.id) != 0:
|
||||
ob = self.Gen.StmtGen._BorrowOwnerOf(trans, an_b.id)
|
||||
if ob is not None:
|
||||
self.Gen.StmtGen._BorrowAdd(trans, ob, nm.id, src_rk, arg_b)
|
||||
# A call returning `own T` transfers ownership to this binding.
|
||||
if GenApi.annotation_is_own(annot) != 0 and aa.value is not None \
|
||||
and aa.value.kind() == ast.ASTKind.Call:
|
||||
@@ -4162,6 +4265,10 @@ novirtual class AssignGenMixin:
|
||||
self.Gen.TypeGenErr.fatal_error(target, "assign a reference field through a named object (`obj.field = &value`); a reference held by a temporary cannot be tracked", "TypeError")
|
||||
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.
|
||||
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)
|
||||
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
|
||||
|
||||
@@ -200,6 +200,10 @@ novirtual class CallGenLib(CallGenBase.CallGenBase):
|
||||
self.Gen.VarGen._FreedMark(src_o)
|
||||
continue
|
||||
self.Gen.TypeGenErr.fatal_error(an_o, f"cannot pass '{nm_o.id}' to an `own` parameter: it is not owned here; move an `own` binding or pass `new ...`", "LifetimeError")
|
||||
elif an_o.kind() == ast.ASTKind.New:
|
||||
nw_o: ast.New* = ast.New*(an_o)
|
||||
if nw_o is not None and nw_o.Source == ast.NEW_SRC_MEMGR:
|
||||
self.Gen.TypeGenErr.fatal_error(an_o, "cannot pass a `new memgr(...)` allocation to an `own` parameter: pool memory is reclaimed by its manager, not by `free`", "LifetimeError")
|
||||
elif an_o.kind() == ast.ASTKind.UnaryOp:
|
||||
self.Gen.TypeGenErr.fatal_error(an_o, "cannot pass a borrow to an `own` parameter: the callee takes ownership", "LifetimeError")
|
||||
# Reference parameters: a mutable reference only accepts `&mut value` (or a `&mut`
|
||||
|
||||
@@ -103,6 +103,9 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
rc_n += 1
|
||||
ref_buf[rc_n] = char(0)
|
||||
self.Gen.CallGenBase._register_ref_params(name, ref_buf, rc_n)
|
||||
# Bare source name too: some call-site checks resolve the callee by its written name.
|
||||
if fd.name is not None and string.strcmp(fd.name, name) != 0:
|
||||
self.Gen.CallGenBase._register_ref_params(fd.name, ref_buf, rc_n)
|
||||
stdlib.free(ref_buf)
|
||||
return 1
|
||||
|
||||
|
||||
@@ -389,6 +389,12 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin):
|
||||
elif tgt.kind() == ast.ASTKind.Attribute:
|
||||
del_at: ast.Attribute* = ast.Attribute*(tgt)
|
||||
if del_at is not None:
|
||||
# Compiler-managed `own` fields are released with their object; a manual `del`
|
||||
# would free them twice.
|
||||
fe_del: GenTypes.FieldEntry* = self.Gen.ExprGenAttr._resolve_attr_field_entry(pool, trans, del_at)
|
||||
if fe_del is not None and fe_del.IsOwn != 0:
|
||||
self.Gen.TypeGenErr.fatal_error(del_at, f"cannot `del` the `own` field '.{del_at.attr}': it is released together with its object; assign a new owned value instead", "LifetimeError")
|
||||
continue
|
||||
del_prop: int = self.Gen.CallGenProp._try_property_deleter(pool, builder, mod, del_at, trans)
|
||||
if del_prop == 0:
|
||||
del_av: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, del_at, None, 0, trans)
|
||||
|
||||
@@ -1652,6 +1652,34 @@ def _ParamRefFlags(pool: memhub.MemManager*, detail: str) -> str:
|
||||
return str(buf)
|
||||
|
||||
|
||||
def _CheckRefReturn(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*,
|
||||
a: LspSymbols.LspAnalysis*, 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 k == ast.ASTKind.Call: return
|
||||
if k == ast.ASTKind.Name:
|
||||
nm: ast.Name* = ast.Name*(value)
|
||||
if nm.id is None: return
|
||||
if string.strcmp(nm.id, "self") == 0: return
|
||||
det: str = _SymDetailAt(pool, a, value)
|
||||
if _RefDetail(det) != 0 or _DetailIsOwn(det) != 0: return
|
||||
_EmitTypeError(pool, doc, value,
|
||||
f"cannot return `{nm.id}` from a reference-returning function: it lives in this frame; return a reference parameter, `self`, or a call", errors)
|
||||
return
|
||||
if k == ast.ASTKind.Attribute:
|
||||
at: ast.Attribute* = ast.Attribute*(value)
|
||||
if at.value is not None and at.value.kind() == ast.ASTKind.Name \
|
||||
and string.strcmp((ast.Name*)(at.value).id, "self") == 0: return
|
||||
_EmitTypeError(pool, doc, value,
|
||||
"cannot return a field of a frame-local value from a reference-returning function", errors)
|
||||
return
|
||||
if k == ast.ASTKind.UnaryOp:
|
||||
_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)
|
||||
|
||||
|
||||
def _ClassSymbolByName(a: LspSymbols.LspAnalysis*, name: str) -> LspSymbols.LspSymbol*:
|
||||
"""Class symbol (with its own scope) declared in this document, if any."""
|
||||
if a is None or name is None or a.symbols is None:
|
||||
@@ -1919,8 +1947,12 @@ def _WalkChecks(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*,
|
||||
if own_t != 0: _OwnAdd(pool, tn.id)
|
||||
if own_t != 0 and an.value is not None and an.value.kind() == ast.ASTKind.Name:
|
||||
sn: ast.Name* = ast.Name*(an.value)
|
||||
if sn.id is not None and _IsOwnBinding(pool, a, an.value) != 0:
|
||||
_MovedAdd(pool, sn.id)
|
||||
if sn.id is not None:
|
||||
if _IsOwnBinding(pool, a, an.value) != 0:
|
||||
_MovedAdd(pool, sn.id)
|
||||
else:
|
||||
_EmitTypeError(pool, doc, an.value,
|
||||
f"`{tn.id}` is `own`: it can only take an owned value (move another `own` binding, `new heap hlife ...`, or a call returning `own T`); a raw pointer does not transfer ownership", errors)
|
||||
_MovedClear(tn.id)
|
||||
# `r: &T = &x`: the binding holds a borrow, so `x` must outlive it.
|
||||
_BorrowDrop(tn.id)
|
||||
@@ -2132,6 +2164,8 @@ def _WalkChecks(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*,
|
||||
continue
|
||||
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)
|
||||
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