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:
1 parent
38a8d338c1
commit
a2a04f567e
2 files changed
+90
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user