添加可选链操作符支持 (?. ?: ?[ ?():支持多返回值,新增 OP_SETTOP 指令,由 BEE_OPTCHAIN 宏启用 - #67
添加可选链操作符支持 (?. ?: ?[ ?():支持多返回值,新增 OP_SETTOP 指令,由 BEE_OPTCHAIN 宏启用#67sumneko wants to merge 30 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds an optional-chaining syntax extension to the vendored Lua 5.4/5.5 parser copies, gated behind a BEE_OPTCHAIN build macro and wired into the build/test flow so it only runs when the feature is enabled.
Changes:
- Add
?./?:/?[/?()optional-chaining support in patchedlparser.ccopies for Lua 5.4 and 5.5 (macro-gated). - Update build scripts to switch to the patched
onelua.cand defineBEE_OPTCHAINwhen-optchainis enabled. - Add optional-chaining test coverage and a loader that auto-skips these tests on builds without the syntax.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
test/test.lua |
Conditionally loads the optional-chaining test module only when the syntax is accepted by the interpreter. |
test/test_optional_chain.lua |
New test suite covering field/index/method/call chaining semantics and short-circuit behavior. |
compile/lua.lua |
Enables selecting patched onelua.c and defining BEE_OPTCHAIN for relevant targets when lm.optchain is on. |
compile/common.lua |
Switches the Lua source set to the patched onelua.c and defines BEE_OPTCHAIN when enabled. |
3rd/lua-patch/optchain/lua55/onelua.c |
Patched Lua 5.5 single-file build entry to include patched parser sources. |
3rd/lua-patch/optchain/lua55/lparser.c |
Patched Lua 5.5 parser copy with optional-chaining parsing/codegen (macro-gated). |
3rd/lua-patch/optchain/lua54/onelua.c |
Patched Lua 5.4 single-file build entry to include patched parser sources. |
3rd/lua-patch/optchain/lua54/lparser.c |
Patched Lua 5.4 parser copy with optional-chaining parsing/codegen (macro-gated). |
.luarc.json |
Adjusts LuaLS workspace settings to reduce diagnostics noise from the custom syntax in tests. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| case '?': { /* optional chain: '?.' '?:' '?[' '?(' */ | ||
| int reg, nilreg; | ||
| luaX_next(ls); /* consume '?' */ | ||
| if (ls->t.token != '.' && ls->t.token != ':' && | ||
| ls->t.token != '[' && ls->t.token != '(') | ||
| luaX_syntaxerror(ls, "unexpected symbol near '?'"); | ||
| reg = luaK_exp2anyreg(fs, v); /* evaluate receiver only once */ | ||
| nilreg = fs->freereg; | ||
| luaK_nil(fs, nilreg, 1); /* ensure it is nil at runtime */ | ||
| fs->freereg++; | ||
| luaK_codeABCk(fs, OP_EQ, reg, nilreg, 0, 1); /* jump when nil */ | ||
| luaK_concat(fs, &niljumps, luaK_jump(fs)); | ||
| break; /* next iteration handles '.' ':' '[' '(' */ | ||
| } |
| case '?': { /* optional chain: '?.' '?:' '?[' '?(' */ | ||
| int reg, nilreg; | ||
| luaX_next(ls); /* consume '?' */ | ||
| if (ls->t.token != '.' && ls->t.token != ':' && | ||
| ls->t.token != '[' && ls->t.token != '(') | ||
| luaX_syntaxerror(ls, "unexpected symbol near '?'"); | ||
| reg = luaK_exp2anyreg(fs, v); /* evaluate receiver only once */ | ||
| nilreg = fs->freereg; | ||
| luaK_nil(fs, nilreg, 1); /* ensure it is nil at runtime */ | ||
| fs->freereg++; | ||
| luaK_codeABCk(fs, OP_EQ, reg, nilreg, 0, 1); /* jump when nil */ | ||
| luaK_concat(fs, &niljumps, luaK_jump(fs)); | ||
| break; /* next iteration handles '.' ':' '[' '(' */ | ||
| } |
|
这种补丁方式在版本升级时非常难维护 |
|
改成在源码里插桩了,但感觉改的有点多。要不这个功能就留在分支里吧,之后master更新后让AI合到分支里 |
|
把它变成git diff提交到仓库,构建的时候apply? |
|
看看? |
|
这个patch的构建脚本不太好,应该用luamake的依赖规则来实现,否则无法支持增量编译。另外patch构建脚本可以让它更通用一点吗,目前硬编码太多 |
| -- 避免下游以为产物变化而重新编译。 | ||
| local src, dst, patch = ... | ||
|
|
||
| local fs_ok, fs = pcall(require, "bee.filesystem") |
| else | ||
| os.execute(('rm -rf "%s"'):format(dst)) | ||
| os.execute(('mkdir -p "%s"'):format(dst)) | ||
| local function ensure_dir(dir) |
| local ok = os.execute(('git apply --directory="%s" "%s"'):format(dst, patch)) | ||
| assert(ok, "git apply failed: " .. patch) | ||
|
|
||
| -- 补丁后内容没变的文件恢复修改时间 |
| lm.luadir / "linit.c", | ||
| }, | ||
| defines = { | ||
| defines = lm.optchain and { |
There was a problem hiding this comment.
| defines = lm.optchain and { | |
| defines = { | |
| "MAKE_LIB", | |
| "LUA_BUILD_AS_DLL", | |
| lm.optchain and "BEE_OPTCHAIN", | |
| }, |
其他类似的代码,不再赘诉
- 补丁位于 3rd/lua-patch/optchain/{lua54,lua55}/,不改动 vendored 源码
- 构建时 luamake -optchain 开启并定义 BEE_OPTCHAIN,默认关闭行为不变
- 新增 test/test_optional_chain.lua(test.lua 探测到可用时才加载)
- 语义对齐 ES2020:整链短路、false 不短路、不可作赋值目标
可选链调用(f?())短路路径改为固定布局:CALL(k 标志标记可选链) / JMP / OP_SETTOP。
OP_SETTOP 填 R[A..A+B] 为 nil 并精确设置 L->top,使开放指令
(OP_RETURN/OP_CALL/OP_SETLIST)读到恰好数量的 nil:
- return f?() 短路 -> 恰好 1 个 nil
- g(f?()) 短路 -> 恰好 1 个参数
- {f?()} 短路 -> 恰好 1 个 nil 元素
- a,b,c = f?() 短路 -> 3 个 nil
不再把短路指令位置 hack 进 expdesc->t(修复了 dischargevars/goiftrue
将 e->t 当作跳转列表 patch 导致的编译期崩溃);e->t/f 保持官方语义,
单值消费(print(f?())、if f?() then、f?()+1)天然安全。
OP_SETTOP 追加在 OP_EXTRAARG 之后,普通代码指令编号不变;
默认构建(无 BEE_OPTCHAIN)零影响。
将 case '?' 中裸的 fs->freereg++ 改为 luaK_reserveregs(fs, 1)(同时正确 更新 maxstacksize,避免绕过 luaK_checkstack),使用后立即释放。 曾尝试按 review 建议将 nil 寄存器一次性 reserve 到链末复用,但这会破坏 Lua 编译器的线性 freereg 不变量:链中间的 freeexp/exp2nextreg 假设待释放 表达式是最后分配的寄存器,reserve 保持会使链末 CALL 基址偏移、结果寄存器 与局部变量错位(CALL 0 变 CALL 1 + 多余 MOVE,返回错误值)。
验证 f?(1,2,3) 等带参数场景:非短路正常传参调用、短路时参数不求值 (无副作用)、多值短路精确、表构造/参数位置正确。
将可选链补丁从 3rd/lua-patch/optchain/ 的整文件复制改为直接写入
3rd/lua{54,55} 官方源码,全部由 #if defined(BEE_OPTCHAIN) 门控:
- lparser.c / lvm.c / lopcodes.{h,c} / lopnames.h:每文件仅增几行门控
补丁(lparser.c ~110 行、lvm.c 15 行、指令表各 3-7 行)
- OP_SETTOP 枚举/模式表/反汇编名均以 BEE_OPTCHAIN 门控,默认构建
预处理后与官方源码字节级一致,零影响
- compile 不再切换 onelua.c 来源,仅保留 -optchain 时定义宏
分支相对 master 的 diff 从 ~10100 行降至 ~500 行。
默认 + optchain(lua54/lua55)全部测试通过。
按 actboy168 建议,将可选链补丁从" 官方源码插桩\改为\git diff 补丁 +
source_set 的 deps 只作用于 target 组合,不保证 obj 编译顺序; 改用 objdeps(ninja implicit inputs / order-only),让所有依赖 patched 源码的 obj 在编译前先运行 apply_optchain_patch,消除 并行构建时 lua.hpp 缺失的竞态。 runlua 的 args/inputs/outputs 改用 lm:path 对象,自动加当前 项目前缀,保证 bee.lua 作为 submodule 被引用时路径正确。
4969fcb to
61c5007
Compare
|
在已经使用git patch的情况下 |
|
@sumneko 看下这个 test |
| static void lastlistfield (FuncState *fs, ConsControl *cc) { | ||
| if (cc->tostore == 0) return; | ||
| if (hasmultret(cc->v.k)) { | ||
| + luaK_setreturns_optchain(fs, &cc->v, LUA_MULTRET); |
There was a problem hiding this comment.
如果nresults等于LUA_MULTRET,这个函数就变成空函数,可以直接删了。
|
findsetreg需要支持新的字节码 |
|
ljumptab.h也需要patch。另外看看CI的错误 |
|
只改了lua55,没改lua54? |
5.4 的 |
|
这不是吗 Line 1852 in 676d850 |
|
@sumneko 看下CI的错误 |
概述
为 Lua 添加可选链操作符(optional chaining)支持,语法与语义对齐 ES2020 的
?.,包含四种形式:x?.y— 字段访问x?:f()— 方法调用f?()— 函数调用t?[1]— 索引访问支持任意链式组合,如
a?.b.c、a?.b?.c、obj?:get()?.x。设计思路
解析器补丁 + 一个新增指令,默认构建零影响
实现直接修改
3rd/lua{54,55}的官方源码,所有改动都由#if defined(BEE_OPTCHAIN)宏门控:lparser.c):?.在编译期展开为标准的LOADNIL/EQ/JMP+ 字段访问/调用指令组合;OP_SETTOP:链末调用(f?()、obj?:m())的短路路径使用CALL(k) / JMP / OP_SETTOP固定布局,OP_SETTOP将结果寄存器填为 nil 并精确设置栈顶(L->top),使开放指令(OP_RETURN/OP_CALL/OP_SETLIST)读到恰好数量的 nil;OP_SETTOP追加在OP_EXTRAARG之后(同样由BEE_OPTCHAIN门控),普通代码的指令编号完全不变,补丁版与标准版对普通代码的字节码双向兼容;BEE_OPTCHAIN:所有补丁块被预处理剔除,与官方源码字节级一致、行为零影响;启用时仅需luamake -optchain。为什么需要新增指令(及备选方案)
Lua 的
LOADNIL只把寄存器填为 nil,不会调整栈顶L->top,因此链末调用在多值上下文(return f?()、g(f?())、{f?()})短路时,开放指令会读到栈上残留的旧值,产生"nil 数量偏多"。此前考虑过不加指令的绕过方案:短路路径改为调用一个预先准备的、返回 1 个 nil 的函数——OP_CALL会按实际结果数设置L->top,且在固定结果数时自动补足 nil(如a, b, c = nilfn()得 3 个 nil),可精确覆盖所有短路场景,语义上可行。但该方案:因此最终新增
OP_SETTOP:一条指令同时完成"寄存器填 nil"与"精确设置栈顶",语义直接、零运行时依赖与调用开销,且位于OP_EXTRAARG之后不影响普通代码的指令编号。语义(对齐 ES2020)
nil短路:false?.a仍会报错(与 JS 的undefined/null语义对齐);nil,整条链立即得到nil,后续 key / 实参不再求值(无副作用);recv()?.a?.b中recv()只调用一次;obj?.a = 1为语法错误;return f?()短路 → 恰好 1 个 nilg(f?())短路 → 恰好 1 个参数{f?()}短路 → 恰好 1 个 nil 元素a, b, c = f?()短路 → 3 个 nil构建与测试
luamake -optchain启用;test/test_optional_chain.lua,覆盖字段 / 索引 / 方法 / 调用(含带参)/ 链式组合 / 短路副作用 / 单次求值 / 不可赋值 / 非法语法 / 多返回值(含短路精确性)等用例;改动范围
3rd/lua{54,55}/lparser.cBEE_OPTCHAIN门控)3rd/lua{54,55}/lopcodes.{h,c}OP_SETTOP指令(枚举 / 模式表,门控)3rd/lua{54,55}/lopnames.h3rd/lua{54,55}/lvm.cvmcase(OP_SETTOP)(填 nil + 设置栈顶,门控)compile/common.lua、compile/lua.lua-optchain时定义BEE_OPTCHAIN宏test/test_optional_chain.lua、test/test.lua.luarc.json