ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Roc 快照测试解析:数字字面量上的 Bang 运算符(`!3`)为何必然产生类型错误

Roc 快照测试解析:数字字面量上的 Bang 运算符(`!3`)为何必然产生类型错误 【免费下载链接】rocA fast, friendly, functional language.项目地址https://gitcode.com/GitHub_Trending/ro/roc点击查看免费下载导读本篇以 Roc 编译器仓库中的快照测试文件 bang_on_numeric_literal.md 为核心剖析一行极简源码!3对数字字面量 3 应用逻辑取反运算符 Bang在编译器完整流水线——词法分析、语法解析、格式化、规范化canonicalize与类型检查——中的每一站表现并最终推导出它必然触发 Type Mismatch 类型错误的底层原因。读完本文你将掌握 Roc 快照测试文件test/snapshots/*.md各章节META、SOURCE、EXPECTED、PROBLEMS、TOKENS、PARSE、FORMATTED、CANONICALIZE、TYPES的完整含义理解编译器如何把!脱糖为对Bool.not的调用以及类型系统如何借助静态分发static dispatch约束拒绝数字操作数。快照文件总览一次编译的全部证据留痕本仓库的 test/snapshots/README.md 对快照测试的定位做了清晰说明快照测试通过捕获每个编译阶段对特定 Roc 代码示例的输出来验证编译器行为。它覆盖了源码从 tokenization词法分析、parsing语法解析、canonicalization规范化到 type checking类型检查的完整变换过程每个快照文件包含各阶段应有的输出用于在编译器行为发生意外变化时检测回归。bang_on_numeric_literal.md正是其中一个普通快照typeexpr它属于 README 中所述的Ordinary snapshots类别PROBLEMS章节保存的是每个reporting.Report的规范化 S-expression 序列化对应 report_sexpr.zig不含渲染器细节无方框字符、ANSI 转义、换行或标记。这类快照回答的问题是编译器是否生成了正确的诊断文件顶部依次是META、SOURCE、EXPECTED、PROBLEMS、TOKENS、PARSE、FORMATTED、CANONICALIZE、TYPES九个段落我们逐段拆解。META声明快照的类型与测试意图descriptionBang operator on numeric literal should produce type error typeexprdescription一句话描述该快照的语义预期——对数字字面量使用 Bang 运算符应当产生类型错误这正是整篇快照验证的核心断言。typeexpr声明这是一条表达式级expression-level快照即SOURCE中的内容以表达式而非完整程序文件的形式参与编译。这是最紧凑的测试形态适合验证单个语法构造成分的诊断行为。SOURCE 与 EXPECTED最小复现与预期结论!3TYPE MISMATCH - bang_on_numeric_literal.md:1:2:1:3SOURCE只有两个字符加一个数字一元 Bang 运算符!作用于整数字面量3。EXPECTED用最简洁的形式声明预期结果出现TYPE MISMATCH错误位置为文件第 1 行第 2 列到第 1 行第 3 列即3这个数字字面量本身所占的区域因为运算符!占据第 1 列3占据第 2–3 列。这里可以立即点出第一个设计意图Roc 中的!是逻辑非运算符对应Bool.not只能作用于Bool值。把!用在数字3上属于类型层面的误用因此编译器在类型检查阶段拒绝而不是在语法解析阶段拒绝——!3在语法上是完全合法的表达式问题出在类型上。PROBLEMS诊断报告的规范 S-expression(reports (report (severity runtime_error) (title Type Mismatch) (region (start 1 2) (end 1 3)) (headline (reflow This number is being used where a non-number type is needed.)) (document (source-region (file bang_on_numeric_literal.md) (start 1 2) (end 1 3) (annotation error) (line-text !3)) (line-break) (reflow Other code expects this to have the type:) (line-break) (line-break) (annotation-start code-block) (indent 1) (text Bool) (annotation-end))))这是整个快照的核心证据逐字段解读(reports ...)报告列表的顶层容器此处只有一个report。(severity runtime_error)严重级别为运行时错误编译期即报出。(title Type Mismatch)报告标题即用户看到的错误类别。(region (start 1 2) (end 1 3))错误定位区域与EXPECTED中的位置一致精确指向数字3。(headline ...)标题行文本This number is being used where a non-number type is needed.这个数字被用在了需要非数字类型的位置。(document ...)完整文档结构(source-region ...)在源码片段中标注出错区域annotation error表示以错误样式高亮并带line-text !3原行文本(reflow Other code expects this to have the type:)引导说明——其他代码期望该处具有以下类型(annotation-start code-block) (indent 1) (text Bool)以代码块形式给出期望的类型是Bool。诊断消息的源码出处这条 headline 文本并非手写进快照而是由类型检查错误报告模块生成。在 src/check/report.zig 的buildStaticDispatchDispatcherDoesNotImplMethod中编译器遇到某个类型不实现所期望的静态分发方法时会先检查出错来源是否是一个字面量// Special case: a literal used where a type lacking its from_* // conversion method is expected. if (data.origin.literalKind()) |kind| { return switch (kind) { // number literal used where a non-number type is expected .numeral if (data.num_literal ! null and data.num_literal.?.explicit_suffix) self.buildStaticDispatchMissingMethod(data) else self.buildNumberUsedAsNonNumber(data), // string/interpolation literal used where a non-string type is expected .quote, .interpolation self.buildStringUsedAsNonString(data), }; }数字字面量.numeral且未带显式类型后缀时走buildNumberUsedAsNonNumber分支正是它构造了 This number is being used where a non-number type is needed. 这一报告report.zig 附近并把期望类型渲染为Bool。由此可以推断!3的错误本质是一次静态分发static dispatch失败——!期望操作数具备逻辑非的能力即类型为Bool而数字类型没有实现该约束。TOKENS 与 PARSE词法与语法层面的合法通行证OpBang,Int, EndOfFile,(unary ! (e-int (raw 3)))TOKENS表明词法分析器将!3切分为三个 token一元运算符OpBang、整数 tokenInt、文件结束符EndOfFile。这里没有任何词法错误。PARSE显示语法树根节点是一个一元表达式(unary !)其操作数是整数e-int (raw 3)。也就是说!3在语法上是完全合法的构造——问题绝不在语法层而在类型层这与前文结论互相印证。词法层面的实现在 src/parse/tokenize.zigOpBang是词法器维护的运算符 token 之一与OpUnaryMinus、OpNotEquals等并列扫描到单字符!时压入OpBangtoken同时!会被识别为独立的OpNotEqualstoken见 tokenize.zig 中的 tokenizer 不变式校验checkTokenizerInvariants(gpa, !, false)与!。这解释了为什么!与!是互不混淆的两个运算符。FORMATTED格式化器不介入类型错误NO CHANGEFORMATTED段落说明对!3运行 Roc 格式化器后源码无需任何改动NO CHANGE。这验证了一个重要事实格式化器只负责排版层面的规范化与类型诊断完全无关——即使类型检查失败格式化阶段依然按语法结构正常输出且!3的写法本身已符合格式规范。CANONICALIZE把!脱糖为Bool.not调用(e-call (constraint-fn-var 215) (e-lookup-associated-resolved (source Bool.not) (builtin) (target-node 17337) (target-def 17337)) (e-runtime-error (tag erroneous_value_expr)))这是最能说明底层原理的一段规范化canonicalize阶段把一元!表达式改写为一次函数调用被调用的目标是(e-lookup-associated-resolved (source Bool.not) (builtin) ...)即编译器内置的、与Bool类型关联的方法Bool.not调用类型是(constraint-fn-var 215)一个约束函数类型变量constraint function variable等待类型检查阶段通过静态分发来解析具体的实现实参是(e-runtime-error (tag erroneous_value_expr))一个标记为错误值表达式的运行时错误节点说明该参数在类型检查阶段已被判定为非法值。源码实现addBoolNotCall这一脱糖逻辑在 src/canonicalize/Can.zig 的addBoolNotCall函数中实现其注释明确指出设计意图/// Logical negation always calls the compiler-owned Bool.not, independent of /// the operands type and any source declarations shadowing Bool.即逻辑非永远调用编译器自有的Bool.not不受操作数类型影响也不受源码中任何遮蔽shadowingBool的声明影响。这正是!3能一路通过解析和规范化、最终却在类型检查阶段失败的根本原因——!的语义被固定为对 Bool 取反数字3在此处无法满足调用约束于是操作数被标记为erroneous_value_expr。在 Can.zig 的 unary 处理逻辑中只有当运算符 token 是OpUnaryMinus或OpBang时才走一元完成路径随后 Can.zig 对OpBang分支直接调用self.addBoolNotCall(...)而OpUnaryMinus则构造e_unary_minus表达式——两者在规范化的起点就已分道扬镳!从一开始就被绑定到Bool.not上。TYPES类型检查的终局判决(expr (type Bool))TYPES段落在类型检查结束后给出整个表达式的类型快照表达式!3的最终类型被解析为Bool。这与PROBLEMS中期望类型是Bool完全对应——调用Bool.not的结果类型天然是Bool但操作数3的类型是数字静态分发约束要求操作数类型必须实现Bool.not所要求的约束即操作数自身是Bool数字类型不满足于是诊断触发操作数被替换为erroneous_value_expr表达式整体仍以Bool收尾以便类型检查继续向下进行。类型快照机制本身实现在 src/check/snapshot.zigSnapshotContent联合类型覆盖flex未绑定类型变量、rigid已绑定类型变量、alias、structure记录/函数/元组等平坦类型结构、recursive、err等情形其中structure又细分为 box、tuple、nominal、fn、record、tag_union 等。TYPES章节正是借助这套快照结构把检查时刻的类型状态定格成 S-expression供回归比对。从快照到实战如何运行与更新此类测试对于test/snapshots/目录下的快照文件仓库提供了标准操作方式见 test/snapshots/README.md# 生成/更新全部快照 zig build run-snapshot-tool # 仅更新指定快照文件 zig build run-snapshot-tool -- test/snapshots/bang_on_numeric_literal.md # 根据当前 PROBLEMS 输出更新期望值 zig build run-snapshot-tool -- test/snapshots/bang_on_numeric_literal.md --update-expected相关注意点快照按语义诊断与渲染输出解耦bang_on_numeric_literal.md这类普通快照只锁定诊断语义PROBLEMS中的规范 S-expression渲染布局则由reporting/目录下的 reporting 快照单独锁定两者互不干扰快照后处理会全局性地把移除的头部关键字改写为mod该改写同样作用于 S-expression 输出内部若SOURCE需要包含回车符字节可在META中加入source_escapestrue并把每个回车写成\rREPL 快照typerepl还可配合--trace-eval开启解释器追踪调试release 构建需加-Dtrace-evaltrue。小结!3的完整旅程把九个段落串起来!3在 Roc 编译器中的完整旅程是词法TOKENS!被识别为OpBang3被识别为Int——合法语法PARSE构造一元表达式(unary ! (e-int 3))——合法格式化FORMATTED排版无需改动——合法规范化CANONICALIZE!被脱糖为对编译器内置Bool.not的调用见 Can.zig 的addBoolNotCall操作数类型约束被固定为Bool类型检查PROBLEMSTYPES数字3无法满足Bool.not的静态分发约束触发runtime_error级别的Type Mismatch诊断精确高亮数字字面量区域并明确告知期望类型是Bool表达式最终类型仍收敛为Bool。由此可以得出一个可复用的判断方法在 Roc 中凡是把!应用于非Bool表达式数字、字符串、列表、记录等的代码都会在编译期得到与此快照同构的 Type Mismatch 诊断——这不是语法错误而是静态分发约束在类型层的强制保证。开发者若想进一步验证同族行为可在test/snapshots/下浏览bool_equality.md、bool_closure_type_check.md等同主题快照并结合 src/check/report.zig 中buildStaticDispatchDispatcherDoesNotImplMethod的字面量特判逻辑数字/字符串字面量分别走buildNumberUsedAsNonNumber与buildStringUsedAsNonString理解诊断的完整分支。赞分享【免费下载链接】rocA fast, friendly, functional language.项目地址https://gitcode.com/GitHub_Trending/ro/roc点击查看免费下载相关推荐Roc 字符串插值进阶记录字面量作为函数参数——基于 roc 编译器快照测试的深度解析Roc 字符串插值进阶记录字面量作为函数参数——基于 roc 编译器快照测试的深度解析 字符串插值String Interpolation是 Roc 语言Roc 编译器浮点字面量快照测试解析从 12.34 到 Dec 类型Roc 编译器浮点字面量快照测试解析从 12.34 到 Dec 类型 test/snapshots/primitive/expr_float.md 是 RocRoc 编译器错误报告解析整数字面量上的方法调用Missing Method 快照测试深度解读Roc 编译器错误报告解析整数字面量上的方法调用Missing Method 快照测试深度解读 导读 本文以 Roc 编译器仓库中的 REPL 快照测试创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表