Roc 编译器 match 表达式快照测试深度解析空列表模式与列表 rest 模式的 segfault 回归用例【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc本文以 test/snapshots/match_expr/empty_list_before_rest_pattern.md 这份快照测试文档为骨架逐段拆解 Roc 编译器Roc一门快速、友好、函数式的编程语言在match表达式中空列表模式[]后紧跟列表 rest 模式[.., e]这一组合的完整编译流水线从词法TOKENS、语法PARSE、格式化FORMATTED、规范化CANONICALIZE到类型推导TYPES并以此为主线讲清快照测试文件的格式约定、工具链用法与列表 rest 模式的语法演进。读者读完可以掌握如何阅读、运行与更新这类编译器快照测试并理解 Roc 列表模式匹配含 rest 模式在编译器内部各阶段的真实表示形式。一、快照文件定位一个 segfault 回归测试这份文档的 META 头用一段 description 直接点明了自己的身份descriptionMatch expression with empty list pattern followed by list rest pattern (segfault regression test) typeexpr也就是说它不是一个演示样例而是一个回归测试快照regression snapshot历史上空列表模式[]分支之后紧跟着带列表 rest 模式[.., e]的分支这种组合曾触发编译器段错误segfault本快照把这段源码固化为测试输入并把编译器各阶段的输出钉死为期望值防止段错误问题卷土重来。typeexpr表示这是普通快照ordinary snapshot与 test/snapshots/README.md 中定义的快照分类一致普通快照捕获诊断的语义PROBLEMS段是每个reporting.Report的规范 S 表达式序列化不含任何渲染器细节如框线字符、ANSI 转义、换行与标记回答的是编译器是否产生了正确的诊断/结果这一问题而typereporting的渲染快照位于reporting/目录才负责钉住 CLI、Markdown、HTML、LSP 等用户可见格式的排版输出。该快照文件由七个标准段组成SOURCE、EXPECTED、PROBLEMS、TOKENS、PARSE、FORMATTED、CANONICALIZE、TYPES。下文逐一展开。二、SOURCE被测源码与回归场景match l { [] Err(EmptyList) [.., e] Ok(e) }这是一个标准的 Rocmatch表达式对列表l做分支匹配第一个分支[] Err(EmptyList)当l为空列表时返回 tagErr包裹的EmptyList第二个分支[.., e] Ok(e)..是列表 rest 模式list rest patterne绑定列表最后一个元素即匹配至少含一个元素的列表并把尾元素绑定到e返回Ok(e)。这段代码本身在语义上没有诊断问题所以下文EXPECTED与PROBLEMS均为NIL。它的价值完全在于这种空列表分支在前、rest 模式分支在后的顺序组合曾经是编译器段错误的触发条件。把它固化成快照等于给编译器加了一道永久防线。顺带一提在这份快照中 rest 模式写作..匿名形式而同一目录下的其他快照如 list_rest_scoping.md 会看到.. as rest命名形式这两者的区别与演进见第六节。三、EXPECTED 与 PROBLEMS无诊断的干净快照# EXPECTED NIL# PROBLEMS NILNIL表示这次编译没有产生任何诊断报告。正如 test/snapshots/README.md 所说明的普通快照的PROBLEMS段保存的是reporting.Report的规范 S 表达式序列化实现位于 src/reporting/report_sexpr.zig包含 severity、title、source region 以及完整的文档结构文本、注解、源码摘录、下划线但不含任何渲染器专属信息NIL即本次编译无报告。EXPECTED段则是一份人类可读的诊断摘要清单title 加源码位置由PROBLEMS段派生而来。从 src/snapshot_tool/main.zig 的源码可以看到快照工具在生成时会把PROBLEMS里的报告渲染成EXPECTED的摘要格式EXPECTED的位置字段只取源区域在文件内的末段且EXPECTED段的 title 永远从渲染出的PROBLEMS提取二者不会漂移。两个NIL意味着该源码路径在所有编译阶段都无报错、无警告属于正向用例快照与那些故意带错误的负向快照例如 list_patterns.md 中EXPECTED列出OLD LIST REST PATTERN、NAME NOT IN SCOPE、UNUSED VARIABLE等一堆诊断摘要形成鲜明对比。四、TOKENS词法阶段的令牌流KwMatch,LowerIdent,OpenCurly, OpenSquare,CloseSquare,OpFatArrow,UpperIdent,NoSpaceOpenRound,UpperIdent,CloseRound, OpenSquare,DoubleDot,Comma,LowerIdent,CloseSquare,OpFatArrow,UpperIdent,NoSpaceOpenRound,LowerIdent,CloseRound, CloseCurly, EndOfFile,词法分析把源码切成如下令牌序列逐项对应源码片段令牌含义matchKwMatch关键字lLowerIdent小写标识符变量{OpenCurly左花括号[]OpenSquareCloseSquare空列表模式OpFatArrowfat arrow 操作符ErrUpperIdent大写标识符tag 构造器(NoSpaceOpenRound左圆括号标记无空格风格EmptyListUpperIdenttag)CloseRound右圆括号[OpenSquare左方括号..DoubleDot双点号rest 模式标记,Comma逗号eLowerIdent绑定变量]CloseSquare右方括号OpFatArrowfat arrowOk(UpperIdentNoSpaceOpenRoundtag 与左括号eLowerIdent变量)}CloseRoundCloseCurly收尾文件尾EndOfFileEOF 标记值得注意的细节是NoSpaceOpenRound这个词法令牌记录了调用左括号前没有空格这一排版事实它是格式化与 LSP 需要回传的源码布局信息之一说明 Roc 的词法器不只是丢出类型化令牌还保留了用于重建源代码风格的空白信息。五、PARSE 与 FORMATTED语法树与格式化输出5.1 语法树(e-match (e-ident (raw l)) (branches (branch (p-list) (e-apply (e-tag (raw Err)) (e-tag (raw EmptyList)))) (branch (p-list (p-list-rest) (p-ident (raw e))) (e-apply (e-tag (raw Ok)) (e-ident (raw e))))))解析阶段把源码变成一棵树此处以 Clojure 风格的 S 表达式打印p-前缀表示 patterne-前缀表示 expression整棵树是e-match被匹配的表达式是e-ident变量l第一个branch的模式是p-list参数为空 空列表模式[]动作是e-apply把 tagErr应用到 tagEmptyList上第二个branch的模式是p-list内含p-list-rest对应..与p-ident绑定e动作是把 tagOk应用到变量e上。p-list-rest是这份快照的核心语法构件它表示列表模式中的 rest 段允许出现在列表模式的首位[.., e]、末位[first, ..]乃至中间位置[a, .. as middle, x, y]见 middle_rest.md。把 rest 模式与空列表模式p-list零元素放在同一match里正是为了覆盖模式匹配 exhaustiveness 分析、分支去重、desugar 顺序这些历史上容易在边界条件上出错甚至段错误的路径。5.2 格式化输出match l { [] Err(EmptyList) [.., e] Ok(e) }FORMATTED段是编译器格式化器formatter对同一份源码的规范化重排输出。可以看到它保持了语义等价只统一了缩进tab与换行。快照把格式化结果也钉死意味着任何会改变输出排版即使语义不变的编译器改动都会在此快照上留下 diff从而在代码评审阶段被捕获。六、CANONICALIZE规范化后的核心语义表示(e-match (match (cond (e-runtime-error (tag ident_not_in_scope))) (branches (branch (patterns (pattern (degenerate false) (p-list (patterns)))) (value (e-tag (name Err) (args (e-tag (name EmptyList)))))) (branch (patterns (pattern (degenerate false) (p-list (patterns (p-assign (ident e))) (rest-at (index 0))))) (value (e-tag (name Ok) (args (e-lookup-local (p-assign (ident e))))))))))CANONICALIZE是解析树经过 desugar 与名字解析之后的规范化 AST与PARSE相比这里有三个关键差异desugar 为条件链e-match被规范化为matchcondbranches的结构cond里的(e-runtime-error (tag ident_not_in_scope))是 match 表达式在穷尽性分析未覆盖路径上生成的运行时错误节点——它不代表本用例会出错而是当所有分支都不匹配时落入的运行时不变量这是 Roc 编译器把模式匹配编译为条件分支的标准骨架。模式被显式标注每个分支的模式包在(pattern (degenerate false) ...)里degenerate false表示该模式不是必然退化为永远匹配/永远不匹配的退化模式变量绑定统一为p-assign如(p-assign (ident e))。rest 位置被显式记录第二个分支的列表模式写为(p-list (patterns (p-assign (ident e))) (rest-at (index 0)))。rest-at (index 0)表明 rest 段位于该列表模式的第 0 个最前位置——这正是[.., e]的规范表示除尾元素e外前面任意多个元素全部归入 rest。rest-at把rest 在哪儿、前面/后面还有多少固定元素这一信息从语法位置提升为语义索引后续的类型检查、穷尽性分析与代码生成都依赖这个索引做算术推导。对照 middle_rest.md 的 CANONICALIZE 可以进一步印证[first, .., last]规范化后是(patterns (p-assign first) (p-assign last)) (rest-at (index 1))[a, b, .. as middle, x, y]则是(rest-at (index 2) (p-assign (ident middle)))——rest 段带着名字且位于索引 2。可见rest-at的 index 语义贯穿所有 rest 位置首、尾、中间与匿名/命名两种形态。七、TYPES类型推导结果与开放 tag union(expr (type [Err([EmptyList, ..]), Ok(_a), ..]))这是类型检查阶段对整棵表达式推导出的类型以 S 表达式形式呈现。解读如下[Err(...), Ok(_a), ..]是 Roc 内部对tag union标签联合类型的表示..表示这是一个开放 union允许未来通过解构或函数参数补入更多 tagErr携带一个参数其类型是[EmptyList, ..]——即仅含EmptyListtag 的开放 union这正是第一个分支Err(EmptyList)传入的东西Ok携带_a一个尚未被约束的类型变量因为e来自列表l的尾元素而l在本快照中是未绑定的自由变量ident_not_in_scope的来源所以e的类型被保留为多态占位_a整个 match 的结果类型是两个分支结果类型的并Err分支的结果与Ok(_a)分支的结果合并成开放的 tag union任何外层调用者都可以继续向该 union 补充成员。这解释了为何本快照的PROBLEMS是NIL虽然l未定义会在运行时按ident_not_in_scope兜底见 CANONICALIZE 的cond但类型检查层面代码是完全合法且被成功推导的。从源码结构看这类快照的TYPES段由快照工具在类型检查阶段直接捕获并序列化是验证类型推导是否随代码变化漂移的重要依据。八、快照机制与工具链如何运行与更新8.1 快照目录约定Roc 的快照测试集中在 test/snapshots 目录按主题分子目录存放match_expr/下就沉淀了 39 份 match 表达式相关快照覆盖 tag unionbasic_tag_union.md、布尔/字面量/元组/记录/嵌套模式、guardguards_1.md、多 rest 错误list_patterns_err_multiple_rest.md、变量遮蔽variable_shadowing.md等场景共同构成模式匹配功能的回归矩阵。快照的元数据约定见 test/snapshots/README.md普通快照的PROBLEMS段保存诊断语义、reporting/子目录下的渲染快照才保存 CLI/Markdown/HTML/LSP 等渲染输出渲染层改动只应影响reporting/诊断语义改动则会影响普通快照。8.2 常用命令快照工具集成在 Zig 构建系统中build.zig 中定义了run-snapshot-tool、run-check-snapshots、build-snapshot-tool等步骤快照工具本体在 src/snapshot_tool/main.zig# 生成 / 更新全部快照 zig build run-snapshot-tool # 只更新指定快照文件 zig build run-snapshot-tool -- test/snapshots/match_expr/empty_list_before_rest_pattern.md # 根据 PROBLEMS 段重新生成 EXPECTED zig build run-snapshot-tool -- file_path --update-expected # REPL 快照调试typerepl 专用debug 构建默认开启追踪 zig build run-snapshot-tool -- src/snapshots/repl/repl_record_field_access.md --trace-eval需要说明的是由于快照的EXPECTED/PROBLEMS由PROBLEMS段派生并全局做了关键字改写如将已移除的 header 关键字统一改写为mod更新快照后务必人工 review diff。仓库还提供了 CI 级的守护run-check-snapshots步骤会重新生成全部快照并与已跟踪文件比对任何差异都会使构建失败build.zig 中check-snapshot-diff相关逻辑从而保证改了编译器必须同步更新快照且更新必须经过评审。修改编译器后跑一遍快照再审视 diff是本仓库贡献流程的强制一环。8.3 快照工具的实现要点从 src/snapshot_tool/main.zig 的源码结构可以确认几个机制EXPECTED内容由PROBLEMS报告生成Generate EXPECTED content from problems位置字段取源区域在文件内的位置EXPECTED只保留 basename 形式的文件名EXPECTED的 title 直接从渲染出的PROBLEMS提取两者不会漂移PROBLEMS段的生成/渲染同时支持 markdown 与 HTML 两种形态Render reports to PROBLEMS section format (markdown and HTML)HTML 形态服务于快照的可视化浏览配套 src/snapshot_tool/snapshot.css 与 src/snapshot_tool/snapshot.js快照工具会读取 META 中的type、canonicalize_diagnostics、source_escapes等标志来控制各段的生成行为例如source_escapestrue时源码中的回车需写作\r转义字节。九、延伸列表 rest 模式的语法演进与边界本快照使用匿名 rest..而 Roc 列表 rest 模式经历了语法演进这在同目录快照中有直接记录旧语法..rest已被废弃list_rest_scoping.md 与 list_patterns.md 的PROBLEMS段均包含Old List Rest Pattern诊断标题为I was parsing a list pattern, and this uses the old rest syntax.文档指出 rest 模式现应写作.. as name名字可选若存在必须跟在as之后例如[first, .. as rest]命名 rest 的三种位置[first, .. as rest]尾部、[.. as rest, last]首部、[x, .. as rest, y]中间三种写法在规范化后分别对应rest-at (index 1)、rest-at (index 0)、rest-at (index 1)与命名绑定见 list_rest_scoping.md多个 rest 非法list_patterns_err_multiple_rest.md 专门固化同一列表模式中出现多个 rest的报错行为中间 rest 的穷尽性middle_rest.md 展示了[first, .., last]、[a, b, .. as middle, x, y]、[single]、[]四个分支共存时 rest 索引index 1、index 2与固定元素的算术关系。回到本文主角把[]零固定元素与[.., e]rest 在 index 0、一个固定尾元素放在同一个 match 中等于把 rest 索引的极端值0与空模式放在同一份穷尽性分析里——这正是当年 segfault 的触发组合。如今它被 empty_list_before_rest_pattern.md 永久固化只要这段源码在词法、语法、格式化、规范化、类型五个阶段的输出不再与快照一致CI 就会立刻报警。对编译器贡献者而言它既是调试 segfault 类 bug 的稳定复现样例也是理解 Roc 模式匹配内部表示p-list-rest、rest-at、开放 tag union的最小教科书。【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考