From 42ca901b2864fd31490e07c0642ad744a84aede0 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Thu, 8 Oct 2026 17:47:24 +0800 Subject: [PATCH] =?UTF-8?q?test(new):=20=E6=96=B0=E5=A2=9E=20NewTest=20?= =?UTF-8?q?=E5=B7=A5=E7=A8=8B=EF=BC=9B=E4=B8=A4=E4=B8=AA=E5=9B=9E=E5=BD=92?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E6=94=AF=E6=8C=81=20--escape/--only=20?= =?UTF-8?q?=E5=8D=95=E7=8B=AC=E8=B7=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NewTest:M2.4 new 描述符冒烟(stack→alloca、heap→malloc;memgr 段待确认管理器 API,非法组合以注释列出)。 regression_test.py / SequentialRegressionTest.py 新增 --escape(只跑生命期/逃逸 + new 相关项目)与 --only(逗号分隔指定项目);两者都会在 Test/<名> 与仓库根 <名> 查找 project.json / project.vpj,故根级 Escape* 项目也能直接跑;--escape 忽略 README 难度顺序。 README.md:新增「一·附 生命期/逃逸相关测试」一节,列出 5 个项目的期望结果与单独运行命令。 --- NewTest/App/main.vp | 46 +++++++++++++++++++++++++++++++++ NewTest/project.json | 29 +++++++++++++++++++++ README.md | 25 ++++++++++++++++++ SequentialRegressionTest.py | 51 +++++++++++++++++++++++++++++++++++-- regression_test.py | 47 +++++++++++++++++++++++++++++++++- 5 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 NewTest/App/main.vp create mode 100644 NewTest/project.json diff --git a/NewTest/App/main.vp b/NewTest/App/main.vp new file mode 100644 index 0000000..5a20168 --- /dev/null +++ b/NewTest/App/main.vp @@ -0,0 +1,46 @@ +from stdint import * +import w32.win32console +import t, c +from t import CInt, CExport +import stdio + + +# ============================================================================= +# M2.4 `new` 描述符分配:三来源各自落地不同(IR 差异见下方注释) +# stack → 帧内 alloca(无 malloc 调用,随帧失效) +# heap → call i8* @malloc(i64 sizeof) + bitcast 到 T* +# memgr → call i8* @.alloc(i64 sizeof) + bitcast 到 T* +# 本工程做冒烟验证:能编译、能跑出正确字段值。 +# 描述符顺序无关;省略来源 = heap(D3),省略生命期由来源决定。 +# ============================================================================= + + +novirtual class Point: + x: int + y: int + + +def main() -> CInt | CExport: + w32.win32console.SetConsoleCP(65001) + w32.win32console.SetConsoleOutputCP(65001) + + # (1) stack:等价既有写法 &Point(1, 2),生命期随帧 + p1: Point* = new stack slife Point(1, 2) + stdio.printf("stack: %d %d\n", p1.x, p1.y) + + # (2) heap:malloc(sizeof(Point)) + 构造(生命期默认 hlife) + p2: Point* = new heap Point(3, 4) + stdio.printf("heap : %d %d\n", p2.x, p2.y) + + # (3) memgr(x):从 memhub 管理器取存储 + # 待确认 memhub 管理器的构造 API(MemBuddy 建立方式)后打开本段: + # m: memhub.MemBuddy* = <构造管理器> + # p3: Point* = new memgr(m) hlife Point(5, 6) + # stdio.printf("memgr: %d %d\n", p3.x, p3.y) + + # 非法组合(应报 LifetimeError)——要验证时打开其中一条: + # 1) 语义矛盾:bad: Point* = new stack hlife Point(7, 8) + # 2) 多来源 :new stack heap Point(7, 8) → 解析期报错 + # 3) 多生命期:new heap slife hlife Point(7, 8) → 解析期报错 + + return 0 diff --git a/NewTest/project.json b/NewTest/project.json new file mode 100644 index 0000000..e2f21cb --- /dev/null +++ b/NewTest/project.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json", + "name": "NewTest", + "version": "1.0.0", + "source_dir": "./App", + "build_dir": "./.tpv_build", + "compiler": { + "cmd": "llc", + "flags": ["-filetype=obj", "-relocation-model=pic"] + }, + "linker": { + "cmd": "clang++", + "flags": ["-lmsvcrt", "-lucrt", "-lpthread", "-lmingwex", "-lkernel32"], + "output": "NewTest.exe" + }, + "includes": [ + "../../includes" + ], + "target": { + "triple": "x86_64-pc-windows-gnu", + "datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + }, + "options": { + "slice_level": 3, + "sha1_slice_level": 1, + "target": "llvm", + "strict_mode": true + } +} diff --git a/README.md b/README.md index 635b2b0..146badd 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,31 @@ --- +## 一·附、生命期 / 逃逸(escape)相关测试 + +验证 M1–M2.4 的生命期检查(栈地址逃逸、借用传播、函数逃逸摘要)与 `new` 描述符分配。 +因历史原因前 4 个位于**仓库根**(不在 `Test/` 下),`NewTest` 位于 `Test/`: + +| 项目 | 位置 | 期望结果 | +|---|---|---| +| EscapeTest | 仓库根 `EscapeTest/` | **编译失败**:`return Mid(&l)` → `LifetimeError`(v2 传递闭包) | +| EscapeOkTest | 仓库根 `EscapeOkTest/` | 编译通过并输出 `Hi/hello/AB/x/aa/loop`(值拷贝/静态/帧内使用等豁免不得误报) | +| EscapeMethodTest | 仓库根 `EscapeMethodTest/` | **编译失败**:方法 / `__init__` / 嵌套函数交出栈地址 → `LifetimeError`(三用例互斥,一次开一条) | +| EscapeXmodTest | 仓库根 `EscapeXmodTest/` | **编译失败**:跨模块摘要(`.vpi` 落盘 `# esc=`)路径 → `LifetimeError` | +| NewTest | [Test/NewTest/](NewTest/App/main.vp) | 编译通过并输出 `stack` / `heap` 的字段值(`memgr(x)` 段待确认管理器构造 API) | + +单独运行(`regression_test.py` 与 `SequentialRegressionTest.py` 都支持): + +```bash +python Test/regression_test.py --escape # 只跑上表全部项目 +python Test/regression_test.py --only NewTest # 只跑指定项目(逗号分隔可多个) +python Test/SequentialRegressionTest.py --escape --exe --rebuild-includes +``` + +> `--escape` / `--only` 会在 `Test/<名>` 与**仓库根** `<名>` 两处查找 `project.json` / `project.vpj`,所以根级 escape 项目也能直接跑;`--escape` 会忽略 README 的难度顺序。 + +--- + ## 二、建议实现顺序(难度升序,共 68 步) 按"先基础语法 → 再类型系统 → 再容器/OOP → 再泛型/闭包难点 → 最后编译器自身库与外部集成"推进,每步按依赖关系排序。 diff --git a/SequentialRegressionTest.py b/SequentialRegressionTest.py index c0fe7f8..066c6f6 100644 --- a/SequentialRegressionTest.py +++ b/SequentialRegressionTest.py @@ -33,6 +33,37 @@ PROJECTRANS = os.path.join(PROJECT_ROOT, "TransPyC", "Projectrans.py") # README.md 路径(顺序来源,原 TestsOverview.md) OVERVIEW = os.path.join(TEST_DIR, "README.md") +# ============================================================ +# 单独运行支持:--escape / --only +# ============================================================ +# escape 相关项目(生命期/逃逸检查 + M2.4 new 描述符): +# Test/ 下:NewTest;仓库根下:EscapeTest/EscapeOkTest/EscapeMethodTest/EscapeXmodTest +ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest"] + + +def find_project_file(proj_dir): + """项目目录下找 project.json(Test/ 约定)或 project.vpj(仓库根约定)""" + for fn in ("project.json", "project.vpj"): + p = os.path.join(proj_dir, fn) + if os.path.isfile(p): + return p + return None + + +def select_named_projects(names): + """按名字解析项目:优先 Test/,其次仓库根 (根级 escape 测试)""" + sel = [] + for n in names: + d = os.path.join(TEST_DIR, n) + if not os.path.isdir(d): + d = os.path.join(PROJECT_ROOT, n) + pj = find_project_file(d) + if pj is None: + print(f" 跳过 {n}: 找不到 project.json / project.vpj") + continue + sel.append((n, d, pj)) + return sel + # 编译超时(秒) COMPILE_TIMEOUT = 300 # 运行超时(秒) @@ -178,6 +209,10 @@ def main(): help="每次编译追加 --rebuild-includes:清空 includes.binary 全量重编(修复缓存不一致)") parser.add_argument("--number", type=int, default=1, help="从第几个案例开始(1 开始编号),编译该编号及之后的项目;默认 1") + parser.add_argument("--escape", action="store_true", + help="只跑 escape 相关项目(生命期/逃逸检查 + new 描述符),忽略 README 顺序") + parser.add_argument("--only", default=None, + help="只跑指定项目(逗号分隔,名字即目录名),忽略 README 顺序") args = parser.parse_args() compile_cmd = build_compile_cmd(args) @@ -186,7 +221,14 @@ def main(): print("=" * 70) print() - order = read_test_order() + if args.escape: + order = [n for (n, d, pj) in select_named_projects(ESCAPE_PROJECTS)] + args.number = 1 + elif args.only: + order = [n for (n, d, pj) in select_named_projects([x.strip() for x in args.only.split(",") if x.strip()])] + args.number = 1 + else: + order = read_test_order() total = len(order) start = max(1, args.number) @@ -200,8 +242,13 @@ def main(): for idx, name in enumerate(order, 1): if idx < start: continue + # 项目目录:优先 Test/,其次仓库根 (根级 escape 项目) proj_dir = os.path.join(TEST_DIR, name) - proj_json = os.path.join(proj_dir, "project.json") + if not os.path.isdir(proj_dir): proj_dir = os.path.join(PROJECT_ROOT, name) + proj_json = find_project_file(proj_dir) + if proj_json is None: + print(f" [SKIP] {name}: 找不到 project.json / project.vpj") + continue if not os.path.isfile(proj_json): print(f"[{idx}/{total}] {name}: 缺少 project.json,跳过") diff --git a/regression_test.py b/regression_test.py index e7e27dc..c6dad4d 100644 --- a/regression_test.py +++ b/regression_test.py @@ -82,6 +82,38 @@ def find_test_projects(): return projects +# ============================================================ +# 单独运行支持:--escape / --only +# ============================================================ +# escape 相关项目(生命期/逃逸检查 + M2.4 new 描述符): +# Test/ 下:NewTest;仓库根下:EscapeTest/EscapeOkTest/EscapeMethodTest/EscapeXmodTest +ESCAPE_PROJECTS = ["EscapeTest", "EscapeOkTest", "EscapeMethodTest", "EscapeXmodTest", "NewTest"] + + +def find_project_file(proj_dir): + """项目目录下找 project.json(Test/ 约定)或 project.vpj(仓库根约定)""" + for fn in ("project.json", "project.vpj"): + p = os.path.join(proj_dir, fn) + if os.path.isfile(p): + return p + return None + + +def select_named_projects(names): + """按名字解析项目:优先 Test/,其次仓库根 (根级 escape 测试)""" + sel = [] + for n in names: + d = os.path.join(TEST_DIR, n) + if not os.path.isdir(d): + d = os.path.join(PROJECT_ROOT, n) + pj = find_project_file(d) + if pj is None: + print(f" 跳过 {n}: 找不到 project.json / project.vpj") + continue + sel.append((n, d, pj)) + return sel + + def compile_project(name, proj_dir, proj_json_path): """编译单个测试项目,返回 (success, elapsed, output)""" cmd = f'D:\\Python312\\python.exe -u "{PROJECTRANS}" --project "{proj_json_path}" --clean' @@ -172,12 +204,25 @@ def extract_error_context(stdout, stderr, context_lines=5): def main(): + import argparse + ap = argparse.ArgumentParser(description="TransPyC 回归测试(可用 --escape/--only 单独跑子集)") + ap.add_argument("--escape", action="store_true", + help="只跑 escape 相关项目(生命期/逃逸检查 + new 描述符)") + ap.add_argument("--only", default=None, + help="只跑指定项目(逗号分隔,名字即目录名)") + args = ap.parse_args() + print("=" * 70) print("TransPyC 回归测试") print("=" * 70) print() - projects = find_test_projects() + if args.escape: + projects = select_named_projects(ESCAPE_PROJECTS) + elif args.only: + projects = select_named_projects([n.strip() for n in args.only.split(",") if n.strip()]) + else: + projects = find_test_projects() total = len(projects) print(f"发现 {total} 个测试项目\n")