feat(obs): check reference argument kinds

A read-only borrow (or a `&T` binding) cannot fill a `&mut T` parameter; the call site now reports it, and the LSP mirrors the same rule.
This commit is contained in:
TermiNexus committed 2026-11-05 15:56:09 +08:00
1 parent 38a8d338c1
commit a2a04f567e
2 files changed
+90

No files matched your search

+21
View File
@@ -202,6 +202,27 @@ novirtual class CallGenLib(CallGenBase.CallGenBase):
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.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`
# binding); a read-only borrow may fill a read-only parameter.
if trans is not None and cargs is not None and trans.StrictOBS != 0 \
and self.Gen.CallGenBase._param_ref_known(call_name) != 0:
arg_n_r: int = int(len(cargs))
ai_r: int = 0
while ai_r < arg_n_r:
an_r: ast.AST* = cargs.get(sizet(ai_r))
ai_r += 1
if an_r is None: continue
pidx_r: int = ai_r - 1 + self_off
pk_r: int = self.Gen.CallGenBase._param_ref_kind(call_name, pidx_r)
if pk_r != 2: continue
sk_r: int = self.Gen.TypeGenResolve.ref_expr_kind(an_r)
if sk_r == 0 and an_r.kind() == ast.ASTKind.Name:
nm_r2: ast.Name* = ast.Name*(an_r)
if nm_r2.id is not None:
sk_r = self.Gen.VarGen.var_ref_kind(trans.SymTab, nm_r2.id)
if sk_r == 0: continue
if sk_r == 1:
self.Gen.TypeGenErr.fatal_error(an_r, "cannot pass a read-only borrow to a `&mut` parameter: create it with `&mut value`", "LifetimeError")
# Strict OBS: a nullable binding/field may be None and must not flow into a non-nullable
# parameter. Same-module callees use the nullable registry, cross-module ones use their
# vpi param annotations; an unknown callee signature is left alone.
+69
View File
@@ -1593,6 +1593,65 @@ def _ParamOwnFlags(pool: memhub.MemManager*, detail: str) -> str:
return str(buf)
def _ParamRefFlags(pool: memhub.MemManager*, detail: str) -> str:
"""Per-parameter reference kind from a `def f(a: &T, b: &mut U)` header.
'0' = not a reference, '1' = `&T`, '2' = `&mut T`, '3' = no annotation.
"""
if detail is None:
return None
lp: str = string.strchr(detail, '(')
if lp is None:
return None
n: sizet = string.strlen(detail)
start: sizet = sizet(uint64(lp) - uint64(detail)) + 1
if start >= n:
return None
buf: bytes = pool.alloc(n + 2)
if buf is None:
return None
cnt: int = 0
i: sizet = start
seg_len: int = 0
depth: int = 0
in_ann: int = 0
ann_pos: int = 0
seg_kind: int = 0
while i < n:
c: int = int(detail[i])
if depth == 0 and (c == ',' or c == ')'):
if seg_len > 0:
if in_ann == 0: buf[cnt] = char(51) # '3'
else: buf[cnt] = char(48 + seg_kind) # '0' / '1' / '2'
cnt += 1
if c == ')':
break
seg_len = 0
in_ann = 0
ann_pos = 0
seg_kind = 0
i += 1
continue
if c == '(' or c == '[' or c == '{':
depth += 1
elif c == ')' or c == ']' or c == '}':
if depth > 0: depth -= 1
elif c == ':' and depth == 0:
in_ann = 1
elif c != ' ' and c != '\t':
seg_len += 1
if in_ann != 0 and c != ' ' and c != '\t' and c != ':':
if ann_pos == 0 and c == '&':
seg_kind = 1
if i + 4 < n and detail[i + 1] == 'm' and detail[i + 2] == 'u' \
and detail[i + 3] == 't' and detail[i + 4] == ' ':
seg_kind = 2
ann_pos += 1
i += 1
buf[cnt] = 0
return str(buf)
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:
@@ -1721,6 +1780,7 @@ def _CheckCall(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*,
fname = "function"
pflags: str = _ParamNullFlags(pool, t.detail)
pown: str = _ParamOwnFlags(pool, t.detail)
pref: str = _ParamRefFlags(pool, t.detail)
if argc > param_n and sig.has_vararg == 0:
msg: bytes = pool.alloc(sizet(256))
if msg is not None:
@@ -1781,6 +1841,15 @@ def _CheckCall(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*,
stdio.snprintf(msg_o, sizet(320),
"cannot pass '%s' to an `own` parameter: it is not owned here; move an `own` binding or pass `new ...`", an_o.id)
_EmitTypeError(pool, doc, arg, str(msg_o), errors)
# A `&mut T` parameter only accepts `&mut value` (or a `&mut` binding).
if pref is not None and arg is not None and sizet(pi) < string.strlen(pref) \
and int(pref[sizet(pi)]) == 50:
sk_p: int = _RefExprKind(arg)
if sk_p == 0:
sk_p = _RefKindOfText(_SymDetailAt(pool, a, arg))
if sk_p == 1:
_EmitTypeError(pool, doc, arg,
"cannot pass a read-only borrow to a `&mut` parameter: create it with `&mut value`", errors)
ai += 1