test(lifetime): A2 补“形参位↔实参位映射”双向钉桩(受控用例)

背景:NewLifetimeRetXmodTest 曾漏检 —— ret(borrows=x) 的 pi→实参映射把模块限定函数
`m.f(...)` 误当方法(给 pi=0 加 self 偏移),取到模块名而非实参。
为防同类回归,在正向工程 NewLifetimeRetOkTest 里加两个“错就编译失败”的钉桩:

- (4a) 模块限定函数:retdep.Identity2(fr, hp)(borrows=1)。
  实参 0 故意是**本帧**指针、实参 1 是**堆**指针 ⇒ 正确映射取实参 1=堆(可存进堆字段);
  若误加 self 偏移 → 取成实参 0(本帧)→ 结果 FRAME → 该行报错。
- (4b) 方法:box.SelfRef(fr)(borrows=self)。
  接收者是**堆**对象、用户实参是**本帧**指针 ⇒ 正确映射取接收者=堆(可存进堆字段);
  若忘了 self 偏移 → 取成实参 0(本帧)→ 结果 FRAME → 该行报错。

即两个方向任一偏移错都会让本正向工程编译失败(正向工程的“错就报错”比再堆一个负向
工程更能锁死偏移量)。README 的 NewLifetimeRetOkTest 行同步说明。
This commit is contained in:
TermiNexus committed 2026-10-10 13:24:27 +08:00
1 parent e8a61473b2
commit b737bedceb
3 files changed
+40 -1

No files matched your search

+30
View File
@@ -26,6 +26,18 @@ novirtual class Holder:
c: char*
# 方法版 borrows 的钉桩:`ret(borrows=self)` ⇒ 结果的生命期 = **接收者**(不是实参)。
# 调用点故意让接收者是**堆**对象、用户实参是**本帧**指针:
# 映射正确 → 结果=堆(可存进长命存储);
# 若把方法误当"无 self"而不偏移 → 取成实参 0(本帧)→ 结果 FRAME → 报错 ⇒ 用例失败。
novirtual class BorrowBox:
v: int*
@life(noescape(self, x), ret(borrows=self))
def SelfRef(self, x: int*) -> int*:
return self.v
# ---- 同模块契约(走本文件 EscDirTable,不经 .vpi)----
@life(ret(heap))
@@ -56,9 +68,27 @@ export def main() -> int:
base: int* = new stack int
pb: int* = retdep.IdentBorrow(base)
# (4) 形参位↔实参位映射的**两个方向**都用"错就报错"的方式钉住(对照 §14.3.1):
# (4a) 模块限定函数 `retdep.Identity2(fr, hp)`:**不**含 self ⇒ 形参位 = 实参位。
# 实参 0 故意用**本帧**指针、实参 1 用**堆**指针;borrows=1 → 结果应为堆 → 可存进 h.p。
# 若误按方法偏移一格 → 取成实参 0(本帧)→ 结果 FRAME → 本行报错。
# (4b) 方法 `box.SelfRef(fr)`:**含** self ⇒ 形参位要偏移。接收者用**堆**对象、实参用**本帧**指针;
# borrows=self → 结果应为堆(接收者)→ 可存进 out.v。
# 若忘了偏移 → 取成实参 0(本帧)→ 结果 FRAME → 本行报错。
fr: int* = new stack int
hp: int* = new heap hlife int
h.p = retdep.Identity2(fr, hp)
box: BorrowBox* = new heap hlife BorrowBox()
box.v = new heap hlife int
out: BorrowBox* = new heap hlife BorrowBox()
out.v = box.SelfRef(fr)
if pf is None: return 1
if pl is None: return 2
if pb is None: return 3
if h.p is None: return 4
if out.v is None: return 5
stdio.printf("RetOkTest OK: @life(ret(...)) contracts accepted\n")
return 0
+9
View File
@@ -44,3 +44,12 @@ def FrameRet() -> int*:
@life(noescape(x), ret(borrows=x))
def IdentBorrow(x: int*) -> int*:
return x
# ret(borrows=<形参 1>):借用**第 2 个**形参(不是第 1 个)。
# 用途:钉住"模块限定函数调用 `retdep.Identity2(...)` **不**含 self ⇒ 形参位=实参位"这一映射。
# 调用点故意让实参 0 是**本帧**指针、实参 1 是**堆**指针:映射正确 → 结果=堆(可存长命存储);
# 若把模块限定函数误当方法而偏移一格 → 取成实参 0(本帧)→ 结果 FRAME → 调用点报错 ⇒ 用例失败。
@life(noescape(a, b), ret(borrows=1))
def Identity2(a: int*, b: int*) -> int*:
return b
+1 -1
View File
@@ -144,7 +144,7 @@
| NewLifetimeWillFreeAddrTest | [Test/NewLifetimeWillFreeAddrTest/](NewLifetimeWillFreeAddrTest/App/main.vp) | **编译失败**:P2 **取地址**路径 —— `stdlib.free(p)` 之后取 `&p` → `LifetimeError`(与读取同一口径) |
| NewLifetimeFieldEscapeTest | [Test/NewLifetimeFieldEscapeTest/](NewLifetimeFieldEscapeTest/App/main.vp) | **编译失败**:P3 —— `h.p = &local`(堆对象字段收下本帧栈地址)→ `LifetimeError` |
| NewLifetimeAnnFieldEscapeTest | [Test/NewLifetimeAnnFieldEscapeTest/](NewLifetimeAnnFieldEscapeTest/App/main.vp) | **编译失败**:P3 **注解形态** —— `h.p: int* = &local`(AnnAssign 属性目标,同一条规则;也守住"非 Name 目标不得被静默丢弃")→ `LifetimeError` |
| NewLifetimeRetOkTest | [Test/NewLifetimeRetOkTest/](NewLifetimeRetOkTest/App/main.vp) | **编译成功 + 退出码 0**:A2(§14)`@life(ret(...))` 契约**不得误报** —— `ret(heap)`/`ret(static)`/`ret(borrows=none)` 的结果存进堆对象字段;`ret(frame)`/`ret(borrows=x)` 的结果只在本帧内使用;同模块(契约表)与跨模块(`retdep.vp`,经 `.vpi` 的 `escr=`)两条取用路径都走一遍 |
| NewLifetimeRetOkTest | [Test/NewLifetimeRetOkTest/](NewLifetimeRetOkTest/App/main.vp) | **编译成功 + 退出码 0**:A2(§14)`@life(ret(...))` 契约**不得误报** —— `ret(heap)`/`ret(static)`/`ret(borrows=none)` 的结果存进堆对象字段;`ret(frame)`/`ret(borrows=x)` 的结果只在本帧内使用;同模块(契约表)与跨模块(`retdep.vp`,经 `.vpi` 的 `escr=`)两条取用路径都走一遍。**另含两个"形参位↔实参位映射"的钉桩**(映射偏移错任一方向都会让它编译失败):`retdep.Identity2(fr, hp)`(模块限定函数**不**含 self ⇒ 形参位=实参位)与 `box.SelfRef(fr)`(方法**含** self ⇒ 需偏移) |
| NewLifetimeRetEscapeTest | [Test/NewLifetimeRetEscapeTest/](NewLifetimeRetEscapeTest/App/main.vp) | **编译失败**:A2 —— 同模块 `@life(ret(frame))` 的返回值被写进**堆对象字段**(长命左值)→ `LifetimeError`(定义端按声明放行 `ret(frame)`,去向由**调用点**判定) |
| NewLifetimeRetXmodTest | [Test/NewLifetimeRetXmodTest/](NewLifetimeRetXmodTest/App/main.vp) | **编译失败**:A2 **跨模块** —— 被调者(`retdep2.vp`)声明 `@life(noescape(x), ret(borrows=x))`,结果(生命期 = 实参 = 本帧)写进堆对象字段 → `LifetimeError`(契约经对方 `.vpi` 的 `escr=` 传来) |