fix(gen): 模块限定函数调用的 ret(borrows=x) 形参位映射错位(A2 跨模块静默失效)

现场:NewLifetimeRetXmodTest 漏检(应拒绝却通过)。诊断(printf 断点,已删)显示
_CallRetKind 完全正确:cls=(none) → 模块分支 → mod_p=retdep2 sha=1 → vpi lookup rk=8。
即契约取到了,错在**下一跳**:life_of_expr 的 borrows 分支用
_CallArgNodeForParam(cl, pi) 把 pi=0 当"接收者"——那是**方法**的语义(形参表含 self)。
`retdep2.IdentBorrow(l)` 是**模块限定函数**(callee 也是 Attribute,但不是方法),
形参 0 就是第一个实参,于是取到了 `retdep2` 这个模块名节点:
life_of_expr(Name retdep2) 查不到变量 → 返回 LIFE_STATIC(≠FRAME)→ 放行。

修法:
- _CallArgNodeForParam 增加 is_method 参数:为 1 时才把 pi=0 映射到接收者、其余偏移一格;
  为 0(模块限定/裸函数)时 pi 直接就是实参下标。
- 新增 _CallIsMethod(trans, cl):接收者是对象变量(_RecvClassName 命中)才算方法调用。
- life_of_expr 的 borrows 分支据此传参。

同模块用例(NewLifetimeRetEscapeTest)与正向用例(NewLifetimeRetOkTest)在此之前"碰巧"
通过/失效——前者走 Name callee 不涉偏移,后者的正例把结果用在本帧内、STATIC 也放行。
This commit is contained in:
TermiNexus committed 2026-10-10 13:00:51 +08:00
1 parent 1784eaf906
commit 5ec75b2d38
1 file changed
+18 -4
+18 -4
View File
@@ -614,12 +614,15 @@ novirtual class AssignGenMixin:
return sname
# _CallArgNodeForParam - `@life(ret(borrows=<形参 pi>))` 在调用点对应的实参节点。
# 方法调用的形参表含 self(pi=0 即接收者),故方法分支要偏移一格。
def _CallArgNodeForParam(self, cl: ast.Call*, pi: int) -> ast.AST*:
# **方法**调用的形参表含 self(pi=0 即接收者),故 is_method=1 时偏移一格;
# **模块限定函数**(`m.f(...)`:callee 也是 Attribute,但不是方法)形参 0 就是第一个实参,
# 不能偏移 —— 否则 pi=0 会被解析成"模块名"这个非实参节点(现场:本函数把
# `retdep2.IdentBorrow(l)` 的形参 0 取成了 `retdep2` → life=STATIC → 跨模块 ret 契约静默失效)。
def _CallArgNodeForParam(self, cl: ast.Call*, pi: int, is_method: int) -> ast.AST*:
"""第 pi 号形参对应的实参表达式;越界/判不定返回 None"""
if cl is None or pi < 0: return None
cargs: list[ast.AST*]* = cl.args
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
if is_method != 0:
if pi == 0:
at: ast.Attribute* = ast.Attribute*(cl.func)
return at.value
@@ -628,6 +631,15 @@ novirtual class AssignGenMixin:
if cargs is None or sizet(pi) >= len(cargs): return None
return cargs.get(sizet(pi))
# _CallIsMethod - 该调用是否"接收者是对象变量"的方法调用(决定形参表是否含 self)。
def _CallIsMethod(self, trans: HT.Translator*, cl: ast.Call*) -> int:
"""1 = 方法调用(形参表含 self);0 = 模块限定函数 / 裸函数"""
if cl is None or cl.func is None: return 0
if cl.func.kind() != ast.ASTKind.Attribute: return 0
at: ast.Attribute* = ast.Attribute*(cl.func)
if self._RecvClassName(trans, at.value) is None: return 0
return 1
# _CallRetKind - 解析被调者的 `@life(ret(...))` 契约(本文件表 → 跨模块 .vpi);-1 = 无声明/判不定。
def _CallRetKind(self, trans: HT.Translator*, cl: ast.Call*) -> int:
"""被调者 ret_kind 编码(GenApi.RET_KIND_*);-1 = 无声明/判不定"""
@@ -722,7 +734,9 @@ novirtual class AssignGenMixin:
if rk == GenApi.RET_KIND_NONFRAME: return GenTypes.LIFE_UNKNOWN
if rk >= GenApi.RET_KIND_BORROW_BASE:
pi: int = rk - GenApi.RET_KIND_BORROW_BASE
bn: ast.AST* = self._CallArgNodeForParam(cl, pi)
# 形参位 → 实参位:**方法**调用的形参表含 self(pi=0 = 接收者);
# 模块限定函数(`m.f(...)`)不含,pi 直接就是实参下标。
bn: ast.AST* = self._CallArgNodeForParam(cl, pi, self._CallIsMethod(trans, cl))
if bn is None: return GenTypes.LIFE_UNKNOWN
return self.life_of_expr(trans, bn, None)
# 分配类调用 → 堆;其余调用 → 未知(函数摘要落地前保守放行)