From 5ec75b2d3889b8a468fc4da641286c9fa9ca94b8 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 10 Oct 2026 13:00:51 +0800 Subject: [PATCH] =?UTF-8?q?fix(gen):=20=E6=A8=A1=E5=9D=97=E9=99=90?= =?UTF-8?q?=E5=AE=9A=E5=87=BD=E6=95=B0=E8=B0=83=E7=94=A8=E7=9A=84=20ret(bo?= =?UTF-8?q?rrows=3Dx)=20=E5=BD=A2=E5=8F=82=E4=BD=8D=E6=98=A0=E5=B0=84?= =?UTF-8?q?=E9=94=99=E4=BD=8D=EF=BC=88A2=20=E8=B7=A8=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E9=9D=99=E9=BB=98=E5=A4=B1=E6=95=88=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现场: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 也放行。 --- App/lib/core/Gen/AssignGenMixin.vp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index 7591632..3ec3f1b 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -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) # 分配类调用 → 堆;其余调用 → 未知(函数摘要落地前保守放行)