Roc 语言 Match 表达式列表模式:multiple rest pattern 编译错误的原理、排查与正确用法
发布时间:2026/9/18 6:56:11 作者:尧图编辑部 阅读量:1,286

Roc 语言 Match 表达式列表模式multiple rest pattern 编译错误的原理、排查与正确用法【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc本文以 Roc 编译器快照测试 list_patterns_err_multiple_rest.md 为核心素材深入解析 match 表达式中列表模式list pattern关于 rest 模式..全列表至多一个的硬性约束为什么[.., middle, ..]会被编译期拒绝、编译器内部如何检测与处理、以及单个 rest 模式的合法写法含中间位置与命名形式。读完本文你将能准确理解这类报错的成因、在 Can.zig 中的实现路径并掌握符合 Roc 语法规范的列表解构写法。快照测试文件一份可执行的语法契约在 Roc 仓库中test/snapshots/match_expr/目录下存放着一批以 Markdown 格式书写的快照测试每个文件描述一个 match 表达式的编译场景。list_patterns_err_multiple_rest.md即其中专门验证多个 rest 模式这一错误场景的用例其固定结构如下META测试元信息description用一句话说明场景typeexpr表示被测对象是表达式SOURCE被测的 Roc 源码片段EXPECTED/PROBLEMS预期的诊断输出NIL表示无TOKENS词法分析产生的 Token 序列PARSE语法分析parse得到的语法树S-表达式形式FORMATTED格式化器formatter处理后的输出CANONICALIZE规范化canonicalization后的中间表示TYPES类型推导结果。快照测试由编译器测试框架驱动相关实现位于 src/snapshot_tool 与 src/parse/AST.zig后者负责将 AST 序列化为 S-表达式。这类测试既是回归测试也是语言语法规则最精确的可读文档。违规示例逐层拆解为什么[.., middle, ..]必须报错SOURCE被测试的源码match numbers { [.., middle, ..] ... # error, multiple rest patterns not allowed }该分支对列表numbers进行模式匹配列表模式中包含两个 rest 模式..。在 Roc 的列表语义中rest 模式表示匹配任意数量的剩余元素若允许出现多个元素的切分位置将产生歧义因此编译器将其判定为非法。TOKENS词法层面的印证KwMatch,LowerIdent,OpenCurly, OpenSquare,DoubleDot,Comma,LowerIdent,Comma,DoubleDot,CloseSquare,OpFatArrow,TripleDot, CloseCurly, EndOfFile,DoubleDot即..的 Token 类型。可以看到列表模式内部出现了两个DoubleDot词法阶段能够完整识别它们说明该错误并非语法识别失败而是语义规范化阶段主动拦截的结果。PARSE语法树中的两个p-list-rest(e-match (e-ident (raw numbers)) (branches (branch (p-list (p-list-rest) (p-ident (raw middle)) (p-list-rest)) (e-ellipsis))))p-list-rest表示列表 rest 模式节点AST.zig 中对应p-list-rest的序列化输出。注意此处两个p-list-rest都没有名称——..不带as name时为匿名 rest。分支值e-ellipsis...表示该分支未实现。CANONICALIZE规范化后只剩一个 rest(e-match (match (cond (e-runtime-error (tag ident_not_in_scope))) (branches (branch (patterns (pattern (degenerate false) (p-list (patterns (p-assign (ident middle))) (rest-at (index 0))))) (value (e-not-implemented))))))规范化结果清晰地展现了编译器的处理策略rest-at (index 0)表示最终只保留一个rest 位置索引 0即第一个..第二个 rest 模式被丢弃仅保留普通元素p-assign (ident middle)同时(cond (e-runtime-error (tag ident_not_in_scope)))与TYPES中的(expr (type _a))是因为测试中numbers未定义产生名称不在作用域错误类型退化为未约束的类型变量_a——这是快照测试故意简化上下文所致与本例核心错误无关。EXPECTED / PROBLEMS本用例的诊断定位该快照的EXPECTED与PROBLEMS均为NIL意味着它不参与 canonicalize 阶段的诊断快照对比仅作为应产生错误的编译场景被记录与回归。真正的拦截逻辑在编译器内部完成见下文这种错误场景快照的作用是把非法输入的语法树形态固定下来防止解析或规范化行为发生无意的漂移。源码级原理Can.zig 如何拦截 multiple rest编译器对 rest 模式的检查位于规范化阶段 Can.zig。当遍历列表模式中的各元素、遇到list_rest类型的 AST 节点时if (ast_pattern .list_rest) { // Check for multiple rest patterns (not allowed) if (state.rest_index ! null) { const list_rest_region self.parse_ir.tokenizedRegionToRegion(ast_pattern.list_rest.region); try self.env.pushDiagnostic(Diagnostic{ .pattern_not_canonicalized .{ .region list_rest_region, } }); try stacks.pushListNext(frame_allocator, .{ .patterns state.patterns, .region state.region, .scratch_top state.scratch_top, .next state.next 1, .rest_index state.rest_index, .rest_pattern state.rest_pattern, }); continue :patternkernel_loop .dispatch; } // ... 处理命名/匿名 rest 模式 }核心逻辑可概括为state.rest_index记录已出现的 rest 模式位置遍历中一旦再次遇到list_rest即rest_index已非空立即判定为 multiple rest对违规的 rest 区域list_rest_region发出pattern_not_canonicalized诊断——这正是not allowed报错的来源在诊断的同时跳过该 restnext 1并保持已有的rest_index与rest_pattern继续处理剩余元素因此规范化输出中只保留第一个 rest对应上面 CANONICALIZE 中的rest-at (index 0)。从源码结构可以推断这种报错但不崩溃、继续规范化的设计使得编译器在报告该错误后仍能继续处理后续元素从而在一次编译中尽可能多地收集其他问题如名称未定义、未使用变量等。规范化后列表模式的统一表示在 Pattern.zig 中序列化为rest-at节点。合法用法单个 rest 模式的三种位置同目录下的其他快照测试给出了 rest 模式被允许的完整形态1. Rest 在尾部最常见match numbers { [] acc [first, .. as rest] 0 }参见 list_patterns.md其规范化结果为rest-at (index 1)并携带p-assign (ident rest)。2. Rest 在中间位置合法且实用match items { [first, .., last] first last [a, b, .. as middle, x, y] a b x y [single] single [] 0 }参见 middle_rest.md其规范化结果分别得到rest-at (index 1)匿名与rest-at (index 2)并绑定middle。这说明rest 模式出现在列表模式内部而非仅限尾部是完全合法的唯一限制是全列表至多一个——这与本文主题[.., middle, ..]的非法性形成鲜明对比那个用例的两个 rest 分别位于首尾虽位置合法数量却超限。3. Rest 在头部match items { [.. as rest, last] 1 }参见 list_rest_invalid.md其规范化结果为rest-at (index 0)。注意该文件的重点是旧式语法报错但恰好展示了 rest 位于首位的合法形态。新旧语法差异..rest已废弃请使用.. as rest由 list_rest_invalid.md 可见Roc 曾允许[first, ..rest]这类双点后直接跟变量名的旧式写法而当前编译器会针对它抛出Old List Rest Pattern运行时错误诊断提示List rest patterns now use.. as name. The name is optional, but if it is present it must come afteras.正确写法是.. as rest名称可选若存在必须位于as之后。例如[first, ..]匿名与[first, .. as rest]命名都是合法的而[..rest, last]、[x, ..rest, y]均需改写为[.. as rest, last]、[x, .. as rest, y]。格式化器FORMATTED字段会自动把..rest规范为.. as rest但诊断依旧保留提醒开发者迁移。小结排查 multiple rest 错误的速查要点一个列表模式中..无论是否带as name最多只能出现一次即全列表至多一个 rest 点该约束在规范化阶段由 Can.zig 强制实施触发pattern_not_canonicalized诊断规范化输出只保留第一个 restrest-at (index N)若要同时匹配首尾元素并捕获中间部分合法的做法是rest 放在头部或尾部如[first, .. as rest]、[.. as rest, last]或用单个中间 rest 配合固定数量的首尾元素如[first, .., last]若看到Old List Rest Pattern报错请将..rest改写为.. as rest相关行为均可通过仓库中的快照测试 match_expr 目录下的用例list_patterns.md、middle_rest.md、list_rest_invalid.md 等验证与回归。【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考