Skip to content

添加可选链操作符支持 (?. ?: ?[ ?():支持多返回值,新增 OP_SETTOP 指令,由 BEE_OPTCHAIN 宏启用 - #67

Open
sumneko wants to merge 8 commits into
masterfrom
feature/optional-chaining
Open

添加可选链操作符支持 (?. ?: ?[ ?():支持多返回值,新增 OP_SETTOP 指令,由 BEE_OPTCHAIN 宏启用#67
sumneko wants to merge 8 commits into
masterfrom
feature/optional-chaining

Conversation

@sumneko

@sumneko sumneko commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

概述

为 Lua 添加可选链操作符(optional chaining)支持,语法与语义对齐 ES2020 的 ?.,包含四种形式:

  • x?.y — 字段访问
  • x?:f() — 方法调用
  • f?() — 函数调用
  • t?[1] — 索引访问

支持任意链式组合,如 a?.b.ca?.b?.cobj?: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),可精确覆盖所有短路场景,语义上可行。但该方案:

  • 依赖运行时辅助函数:需要一个全局/内部注册的"nil 函数",既污染命名空间,又可能被用户重定义而破坏短路语义;
  • 真实调用开销:每次短路都走一次完整函数调用(栈帧/参数调整),远慢于一条简单指令,短路路径也需"加载函数 + 调用"两条指令;
  • 语义隐晦:依赖"辅助函数返回 1 个 nil"与"CALL 自动补 nil"的间接行为。

因此最终新增 OP_SETTOP:一条指令同时完成"寄存器填 nil"与"精确设置栈顶",语义直接、零运行时依赖与调用开销,且位于 OP_EXTRAARG 之后不影响普通代码的指令编号。

语义(对齐 ES2020)

  • nil 短路false?.a 仍会报错(与 JS 的 undefined / null 语义对齐);
  • 短路即整链:一旦中间某节为 nil,整条链立即得到 nil,后续 key / 实参不再求值(无副作用);
  • receiver 只求值一次recv()?.a?.brecv() 只调用一次;
  • 结果不可赋值obj?.a = 1 为语法错误;
  • 支持多返回值:链末调用保留 open call,可产生多个值;短路路径精确产生对应数量的 nil:
    • return f?() 短路 → 恰好 1 个 nil
    • g(f?()) 短路 → 恰好 1 个参数
    • {f?()} 短路 → 恰好 1 个 nil 元素
    • a, b, c = f?() 短路 → 3 个 nil
    • 非短路时正常返回被调用函数的全部值

构建与测试

  • 通过 luamake -optchain 启用;
  • 新增 test/test_optional_chain.lua,覆盖字段 / 索引 / 方法 / 调用(含带参)/ 链式组合 / 短路副作用 / 单次求值 / 不可赋值 / 非法语法 / 多返回值(含短路精确性)等用例;
  • 测试文件在默认构建下自动跳过(编译期探测),不影响现有测试。

改动范围

文件 说明
3rd/lua{54,55}/lparser.c 可选链解析与代码生成(BEE_OPTCHAIN 门控)
3rd/lua{54,55}/lopcodes.{h,c} 新增 OP_SETTOP 指令(枚举 / 模式表,门控)
3rd/lua{54,55}/lopnames.h 指令反汇编名称(门控)
3rd/lua{54,55}/lvm.c vmcase(OP_SETTOP)(填 nil + 设置栈顶,门控)
compile/common.luacompile/lua.lua -optchain 时定义 BEE_OPTCHAIN
test/test_optional_chain.luatest/test.lua 测试用例与自动跳过逻辑
.luarc.json 诊断配置忽略测试文件中的自定义语法

- 补丁位于 3rd/lua-patch/optchain/{lua54,lua55}/,不改动 vendored 源码
- 构建时 luamake -optchain 开启并定义 BEE_OPTCHAIN,默认关闭行为不变
- 新增 test/test_optional_chain.lua(test.lua 探测到可用时才加载)
- 语义对齐 ES2020:整链短路、false 不短路、不可作赋值目标
Copilot AI lite review requested due to automatic review settings August 13, 2026 22:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 patched lparser.c copies for Lua 5.4 and 5.5 (macro-gated).
  • Update build scripts to switch to the patched onelua.c and define BEE_OPTCHAIN when -optchain is 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.

Comment thread 3rd/lua-patch/optchain/lua55/lparser.c Outdated
Comment on lines +1229 to +1242
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 '.' ':' '[' '(' */
}
Comment thread 3rd/lua-patch/optchain/lua54/lparser.c Outdated
Comment on lines +1116 to +1129
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 '.' ':' '[' '(' */
}
Comment thread .luarc.json
@sumneko
sumneko marked this pull request as draft August 13, 2026 22:32
可选链调用(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,返回错误值)。
@sumneko sumneko changed the title 添加可选链操作符支持 (?. ?: ?[ ?(),由 BEE_OPTCHAIN 宏启用 添加可选链操作符支持 (?. ?: ?[ ?():支持多返回值,新增 OP_SETTOP 指令,由 BEE_OPTCHAIN 宏启用 Aug 14, 2026
验证 f?(1,2,3) 等带参数场景:非短路正常传参调用、短路时参数不求值
(无副作用)、多值短路精确、表构造/参数位置正确。
@sumneko
sumneko marked this pull request as ready for review August 14, 2026 00:20
@actboy168

Copy link
Copy Markdown
Owner

这种补丁方式在版本升级时非常难维护

将可选链补丁从 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)全部测试通过。
@sumneko

sumneko commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

改成在源码里插桩了,但感觉改的有点多。要不这个功能就留在分支里吧,之后master更新后让AI合到分支里

@actboy168

Copy link
Copy Markdown
Owner

把它变成git diff提交到仓库,构建的时候apply?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants