尧图精选

Roc 编译器快照测试解析:类型标注下结构化记录如何与名义记录(Nominal Record)合一

🕒 发布时间:2026/9/18 23:18:17 📁 来源:尧图网络
Roc 编译器快照测试解析类型标注下结构化记录如何与名义记录Nominal Record合一【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc本文以 Roc 编译器仓库中的快照测试 test/snapshots/issue/nominal_record_construction.md 为骨架逐段拆解一条类型标注后结构化记录匿名记录字面量与名义记录Nominal Record类型自动合一的编译链路并结合src/check/unify.zig中unifyRecordWithNominal的实现与zig build run-snapshot-tool测试命令帮助读者掌握快照测试文件的完整结构、Roc 类型系统中名义类型与结构化类型的统一规则以及如何用快照测试验证编译器各阶段行为。快照测试是什么编译器行为的全流水线留影Roc 仓库把用一段源码驱动整个编译流水线的验证方式固化为快照测试snapshot test其组织方式与说明见 test/snapshots/README.md。每个快照文件都会对一段 Roc 源码依次跑完**分词tokenization、解析parsing、格式化formatting、规范化canonicalization、类型检查type checking**等阶段并把每个阶段的输出原样固化进文件。当编译器行为发生意外变化时这些固化输出就会与最新结果产生 diff从而精确暴露回归点。快照文件还做了语义与呈现的解耦普通快照typefile、snippet、expr等只固化诊断的语义其PROBLEMS段保存的是每个诊断的规范 S-表达式序列化结果见 src/reporting/report_sexpr.zig不含方框字符、ANSI 转义、换行等渲染层细节NIL表示编译未产生任何报告Reporting 快照typereporting位于reporting/目录才固化每个用户可见渲染器CLI / MARKDOWN / HTML / LSP的输出。本文所分析的nominal_record_construction.md即属于前者——它的EXPECTED与PROBLEMS均为NIL说明这段源码在整条流水线上是合法、零诊断的。快照文件结构总览从 META 到 TYPES 的九个分节本快照共包含九个分节每一节对应编译流水线上的一个具体产物分节内容对应阶段META快照元信息description、typesnippet测试描述SOURCE被测的 Roc 源码输入EXPECTED预期诊断摘要NIL表示无错误汇总PROBLEMS诊断的规范 S-表达式NIL表示无报告诊断TOKENS分词器输出的 token 流分词PARSE解析器产出的语法树解析FORMATTED格式化器对源码的输出NO CHANGE表示格式已规范格式化CANONICALIZE规范化后的 IRcan-ir规范化TYPES类型检查后推断出的类型inferred-types类型检查这条源码 → token → AST → 规范 IR → 类型的链路正是 Roc 编译器对任何.roc文件都要执行的公共路径因此一个快照文件同时是文档、测试与回归哨兵。SOURCE 段核心测试用例逐行解读被测源码只有三行Person : { name : Str, age : U64 } alice : Person alice { name: Alice, age: 30 }要点拆解Person : { ... }是一条名义类型声明nominal type declaration。:定义一个全新的、与底层结构不同的名义类型Person其底层结构是包含name : Str与age : U64两个字段的记录alice : Person是类型标注type annotation要求alice具有名义类型Personalice { name: Alice, age: 30 }用**结构化记录字面量structural/anonymous record literal**给alice赋值——这里没有出现任何Person构造器调用也没有Person之类的包装语法。这条用例验证的核心语义在 META 的description中写得非常明确Structural records should unify with nominal records when type-annotated——当值被标注为名义记录类型时结构化记录字面量应当与名义记录合一。换句话说Roc 允许你在期望类型已知的上下文中直接用匿名记录字面量构造名义记录而无需显式包装。TOKENS 与 PARSE 段词法与语法层如何表示名义记录TOKENS段给出了分词结果token 流UpperIdent,OpColonEqual,OpenCurly,LowerIdent,OpColon,UpperIdent,Comma,LowerIdent,OpColon,UpperIdent,CloseCurly, LowerIdent,OpColon,UpperIdent, LowerIdent,OpAssign,OpenCurly,LowerIdent,OpColon,StringStart,StringPart,StringEnd,Comma,LowerIdent,OpColon,Int,CloseCurly, EndOfFile,可以对照看到Person :被拆成UpperIdentPerson与OpColonEqual:记录类型{ name : Str, age : U64 }被拆成OpenCurly、字段三元组LowerIdentOpColonUpperIdent、Comma、CloseCurly赋值语句alice { ... }中字符串Alice走StringStart,StringPart,StringEnd整数30走Int赋值号是OpAssign。PARSE段则给出了解析树。注意在语法层记录字面量{ name: Alice, age: 30 }被解析为e-record记录类型被解析为ty-record二者都没有任何名义标记——名义性的引入完全发生在后续的类型检查阶段(file (type-mod) (statements (s-type-decl (header (name Person) (args)) (ty-record (anno-record-field (name name) (ty (name Str))) (anno-record-field (name age) (ty (name U64))))) (s-type-anno (name alice) (ty (name Person))) (s-decl (p-ident (raw alice)) (e-record (field (field name) (e-string (e-string-part (raw Alice)))) (field (field age) (e-int (raw 30)))))))FORMATTED段为NO CHANGE说明这段源码已符合 Roc 格式化规范无需重排——这同时也保证了快照的稳定性源码自身的排版不会引起无谓的 diff。CANONICALIZE 段规范化 IR 中的注释与名义声明规范化canonicalization是 Roc 把语法树降为类型检查可直接消费的 IR 的阶段产物为can-ir。本用例的规范化结果包含两条顶层语句(can-ir (d-let (p-assign (ident alice)) (e-record (fields (field (name name) (e-string (e-literal (string Alice)))) (field (name age) (e-num (value 30))))) (annotation (ty-lookup (name Person) (local)))) (s-nominal-decl (ty-header (name Person)) (ty-record (field (field name) (ty-lookup (name Str) (builtin))) (field (field age) (ty-lookup (name U64) (builtin))))))关键信息有三点d-let的(annotation (ty-lookup (name Person) (local)))表明alice携带了指向本地名义类型Person的类型注释记录字面量在规范化后仍是e-recorde-string/e-num并没有被提升或包装成某个名义构造表达式——名义合一发生在类型层面而非表达式层面s-nominal-decl给出了Person的名义声明本体ty-record的两个字段分别解析为内建类型Str与U64ty-lookup ... (builtin)。这意味着结构化记录 vs 名义记录的抉择完全交给了后续的类型检查器表达式依然以结构化形态存在只有类型层面被统一到Person。TYPES 段类型检查器给出的最终裁决(inferred-types (defs (patt (type Person))) (type_decls (nominal (type Person) (ty-header (name Person)))) (expressions (expr (type Person))))TYPES段是整条链路的判决书defs中alice这个 pattern 的推断类型是Persontype_decls将Person登记为nominal类型声明expressions中记录字面量表达式的推断类型同样是Person。配合EXPECTED/PROBLEMS均为NIL可以得出精确结论在类型标注存在的前提下Roc 的类型检查器把结构化记录字面量与名义记录类型Person成功合一未产生任何诊断。源码佐证unify.zig 中的名义记录合一实现结构化记录允许与名义记录合一并非快照自说自话在类型检查核心 src/check/unify.zig 中有对应的实现路径。从源码结构可以看到unify合一逻辑对记录类型与名义类型的相遇做了专门分支处理例如当一侧是名义记录、另一侧是匿名记录时调用unifyRecordWithNominal尝试按字段逐一合一见 src/check/unify.zig 附近 Try to unify nominal record (a) with anonymous record (b) 的分支反向情形匿名记录在前、名义记录在后同样走unifyRecordWithNominal见 Try to unify anonymous record (a) with nominal record (b) 分支对于空记录与名义记录的相遇则走unifyEmptyWithNominal等相关分支。从这些分支的命名与组织方式可以推断Roc 的类型合一器把名义记录 ↔ 结构化记录视为一类需要显式处理的交互而不是简单拒绝或无条件接受。本文快照正是这套逻辑的正向测试字段集合匹配、类型匹配、类型标注存在 → 合一成功、零诊断。对照实验原始类型背书的名义类型不接受字面量为了理解名义记录合一的边界可以对照同一目录下的姊妹快照 test/snapshots/issue/nominal_primitive_literal_construction.md。它测试的是原始类型背书的名义类型UserId : U64 uid : UserId uid 0 Token : Str token : Token token abc该快照的EXPECTED段明确列出了三条TYPE MISMATCH诊断CANONICALIZE段中字面量表达式被替换为e-runtime-errorerroneous_value_expr。也就是说UserId : U64之后不能直接把数字字面量0赋给uid : UserId——数字/字符串字面量不会与原始类型背书的 nominal 类型自动合一这与本文讨论的记录字面量可与名义记录合一形成了鲜明对照。两者对比清晰地勾勒出 Roc 名义类型的规则轮廓名义记录类型X : { ... }在类型标注上下文中接受对应的结构化记录字面量而原始类型背书的 nominalX : U64、X : Str则对字面量保持封闭需要显式构造。这也是nominal_record_construction.md之所以被归入issue/目录回归/行为固定用例的原因——它锁定的正是这一容易出错的边界行为。如何运行与维护这条快照测试快照测试的构建与运行入口在 build.zig其中定义了build-snapshot-tool与run-snapshot-tool两个 step快照工具的源码位于 src/snapshot_tool/main.zig。常用命令如下# 生成/刷新所有快照全量 zig build run-snapshot-tool # 只更新指定的单个快照文件 zig build run-snapshot-tool -- test/snapshots/issue/nominal_record_construction.md # 将 PROBLEMS 段更新为当前实际诊断-update-expected zig build run-snapshot-tool -- test/snapshots/issue/nominal_record_construction.md --update-expected维护要点详见 test/snapshots/README.md快照后处理会全局把旧的header关键字重写为mod该规则同样作用于 S-表达式输出内部若快照内容与编译器当前行为不一致CI 会报Tracked snapshots changed after regeneration提示运行zig build run-snapshot-tool后提交结果build.zig 中的check_snapshot_diff步骤负责该校验语义型改动会反映在普通快照的PROBLEMS中而渲染层改动只应出现在reporting/目录的 reporting 快照中。因此本文这条快照不仅是一段可以运行的代码示例更是一份可复现的编译行为契约任何人改动了分词、解析、规范化或类型合一逻辑只要改变了nominal_record_construction.md中任意一节的内容run-snapshot-tool与 CI 就会立刻指出行为漂移。小结通过逐段阅读 test/snapshots/issue/nominal_record_construction.md可以完整地看到一条 Roc 源码如何被分词、解析、格式化、规范化并最终通过类型检查语法层Person : { ... }只是普通的名义类型声明记录字面量只是e-record没有任何名义标记规范化层记录字面量保持结构化形态Person被登记为s-nominal-decl类型注释指向(ty-lookup (name Person) (local))类型检查层unifyRecordWithNominal等合一逻辑让结构化记录与名义记录类型成功统一最终alice与字面量表达式的推断类型都是Person全程零诊断边界对照原始类型背书的 nominal如UserId : U64不接受字面量直接赋值体现了名义类型规则的封闭性。对编译器开发者而言这份快照是理解 Roc 名义类型系统与快照测试基建的绝佳入口对普通 Roc 使用者而言它则明确了类型标注下用记录字面量构造名义记录这一日常写法的合法性边界。【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →