尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Roc 名义类型模块中关联嵌套类型的前向引用:从快照测试看 `ModType.InternalType` 的解析与校验

Roc 名义类型模块中关联嵌套类型的前向引用:从快照测试看 `ModType.InternalType` 的解析与校验 Roc 名义类型模块中关联嵌套类型的前向引用从快照测试看ModType.InternalType的解析与校验【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc本指南基于 Roc 编译器仓库test/snapshots/nominal/目录下的快照测试 type_module_nominal_field_depends_on_nested_type.md深入讲解在 Roc 的类型模块type module中一个名义nominal类型的字段如何依赖同模块关联块associated block中声明的嵌套类型。通过阅读本文你将掌握:名义类型声明的字段类型解析机制、NoSpaceDotUpperIdent词法标记到规范 IR 中ty-lookup的完整编译链路以及“暴露于公共表面”与“私有类型”之间的可见性边界——同时了解编译器如何通过快照测试为这一行为提供回归保障。快照测试Roc 编译器如何固化语言行为在 Roc 编译器仓库中test/snapshots/目录存放着一批特殊的“快照测试”snapshot tests。每个.md文件本质上是一份可执行的语言行为规格它把一段 Roc 源码喂给编译器前端然后把词法分析、语法分析、格式化、规范化canonicalization和类型推断五个阶段各自的输出结果快照固化下来。任何修改如果改变了这些阶段的输出CI 都会捕捉到差异从而防止语言语义被无意破坏。本次关联文档 type_module_nominal_field_depends_on_nested_type.md 正是其中一个极有价值的用例。它的 META 描述明确写道Nominal type mod whose field depends on a nested associated type (qualified ModType.InternalType). This compiles because the nested type is exposed as ModType.InternalType.也就是说该用例专门验证类型模块中一个字段引用“本类型模块内、用完整限定名书写的嵌套关联类型”时应当正常编译EXPECTED 为 NIL即无任何错误或警告。快照文件使用固定的五段结构组织以#开头的章节标记章节内容在本文用例中的作用# META用例描述与源文件类型说明这是typefile:ModType.roc的完整文件用例# SOURCE被测的 Roc 源码被测片段# EXPECTED/# PROBLEMS期望的诊断结果NIL表示编译干净通过# TOKENS词法分析输出标记序列token stream# PARSE语法分析输出的 S 表达式抽象语法树AST# FORMATTED格式化后的源码等价的规范排版# CANONICALIZE规范化后的 IR规范中间表示canonical IR# TYPES类型推断结果推断出的名义类型声明被测源码字段依赖关联嵌套类型的类型模块本文核心用例的 SOURCE 如下ModType : { field : ModType.InternalType, }.{ InternalType : [Some, Other] }逐层拆解这段代码ModType : { ... }—— 声明一个名为ModType的名义类型nominal type。在 Roc 中:创建的是具有独立身份、与结构字面量不互相混同的新类型见 docs/langref/types.md 附近关于 nominal type 的说明。field : ModType.InternalType—— 记录字段field的类型标注为ModType.InternalType这是一个带完整限定名的嵌套类型引用它指向ModType关联块.{ ... }内部声明的InternalType。.{ InternalType : [Some, Other] }—— 类型模块的关联块associated items block其中又用:声明了一个嵌套名义类型InternalType其定义为标签联合tag union[Some, Other]即两个无载荷标签Some与Other。这里的关键在于字段在记录类型中先被引用而InternalType的声明出现在紧随其后的关联块中——字段对其依赖类型的“前向引用”是完全合法的。Roc 的声明解析并不要求先声明后使用编译器会先收集整个类型模块内的所有声明再统一解析类型引用。为了对比同目录下的姊妹用例 type_module_nominal_field_depends_on_unqualified_nested_type.md 验证了不写限定名field : InternalType的写法同样可以编译并明确覆盖了 issue #9486 的关联定义排布场景。这证实了 Roc 在解析时会把非限定名解析为“当前模块内的局部定义”对应 CANONICALIZE 中(ty-lookup (name InternalType) (local))的local标记。词法分析ModType.InternalType如何被切分# TOKENS段落给出了词法分析lexing的结果UpperIdent,OpColonEqual,OpenCurly, LowerIdent,OpColon,UpperIdent,NoSpaceDotUpperIdent,Comma, CloseCurly,Dot,OpenCurly, UpperIdent,OpColonEqual,OpenSquare,UpperIdent,Comma,UpperIdent,CloseSquare, CloseCurly, EndOfFile,逐行对照源码解读标记序列对应源码说明UpperIdent, OpColonEqual, OpenCurlyModType : {类型名、:操作符、记录左花括号LowerIdent, OpColon, UpperIdent, NoSpaceDotUpperIdent, Commafield : ModType.InternalType,字段名field、类型标注冒号、类型名ModType、限定名点号 嵌套类型名InternalType、逗号CloseCurly, Dot, OpenCurly}.{记录闭合、类型模块点号、关联块左花括号UpperIdent, OpColonEqual, OpenSquare, UpperIdent, Comma, UpperIdent, CloseSquareInternalType : [Some, Other]嵌套类型声明标签联合两个标签CloseCurly, EndOfFile}关联块闭合、文件结束值得注意的词法细节是NoSpaceDotUpperIdent这是 Roc 词法分析器专门用于“限定名点号紧跟大写标识符”场景的复合标记。它把ModType、.、InternalType之间的紧密连接识别为一个整体——点号前后不允许出现空白且点号后必须是UpperIdent。这一标记的存在使得解析器可以在语法分析阶段直接区分“模块限定访问”与“记录字段访问点”等不同语义是保证ModType.InternalType能被正确识别为类型引用的第一道关卡。语法分析AST 中的类型模块、字段与关联块# PARSE段落给出了语法分析parse后的 S 表达式树(file (type-mod) (statements (s-type-decl (header (name ModType) (args)) (ty-record (anno-record-field (name field) (ty (name ModType.InternalType)))) (associated (s-type-decl (header (name InternalType) (args)) (ty-tag-union (tags (ty (name Some)) (ty (name Other)))))))))这个 AST 揭示了几个关键结构信息顶层(file (type-mod) ...)表示整个文件被识别为类型模块type module。类型模块是 Roc 模块系统中的特殊形态模块文件必须包含一个与文件名同名的顶层名义类型该类型及其全部关联项构成模块对外暴露的“公共表面”见 docs/langref/modules.md 对 type module 的说明。(s-type-decl ...)是类型声明节点header记录名称ModTypeargs为空表示无类型参数。(ty-record (anno-record-field (name field) (ty (name ModType.InternalType))))表明field是一个带类型标注的记录字段其类型引用节点ty直接保存了完整限定名ModType.InternalType。(associated (s-type-decl ... (ty-tag-union ...)))说明关联块内嵌套了一个类型声明InternalType其定义是包含Some与Other两个标签的标签联合。从语法树可以看出嵌套类型声明与字段引用是“平级”挂在同一个s-type-decl节点之下的——一个在ty-record里一个在associated里。这解释了为什么字段可以前向引用关联块中的类型解析器只需遍历同一个类型声明节点的所有子结构就能把字段类型名与嵌套声明名配对解析。作为对照type_module_associated_items_exposed.md 展示了关联块内同时包含嵌套类型Bar与普通值定义stuff {}、baz {}的形态其规范 IR 显示关联值会被提升为Foo.Bar.baz、Foo.stuff这样的限定名d-let声明——关联项通过点号限定名获得全局唯一的身份。规范化ty-lookup与前向引用如何被解析# CANONICALIZE段落是理解类型解析语义的核心(can-ir (s-nominal-decl (ty-header (name ModType)) (ty-record (field (field field) (ty-lookup (name ModType.InternalType) (local))))) (s-nominal-decl (ty-header (name ModType.InternalType)) (ty-tag-union (ty-tag-name (name Some)) (ty-tag-name (name Other)))))规范化canonicalization阶段完成了两件重要工作引用解析为查找节点字段类型由源码中的(ty (name ModType.InternalType))变为规范 IR 中的(ty-lookup (name ModType.InternalType) (local))。ty-lookup表示这是一个需要按名称在环境中查找的类型引用(local)标记则表明该名称在本模块/本作用域内即可解析——不需要跨模块导入。嵌套声明被展平为限定名声明InternalType不再作为ModType的子节点存在而是被提升为一条独立的顶层类型声明(s-nominal-decl (ty-header (name ModType.InternalType)))。这正是 META 描述中所说“the nested type is exposed as ModType.InternalType”的规范 IR 形态——关联块中的嵌套类型在规范化后以完整限定名ModType.InternalType作为其全局身份同时它仍然是ModType对外暴露的公共类型之一。(local)标记的存在至关重要它意味着解析器在构造规范 IR 时已经验证了ModType.InternalType就是本文件中声明的那个嵌套类型而不是某个外部模块的同名类型。这也是本用例与“引用未声明类型”的本质区别。# TYPES段落则从类型推断的角度给出最终结论(inferred-types (defs) (type_decls (nominal (type ModType) (ty-header (name ModType))) (nominal (type ModType.InternalType) (ty-header (name ModType.InternalType)))) (expressions))推断结果确认类型环境中注册了两个名义类型——ModType与ModType.InternalType后者即前者关联块中的嵌套类型。defs与expressions为空符合本用例“纯类型声明、无值定义”的形态。至此整条链路闭环词法 → 语法 → 规范化 → 类型推断全部通过无任何错误或警告EXPECTED: NIL。对比与边界什么情况下编译器会发出警告要真正理解本文用例“为什么能编译”把它与同目录下的“反面教材”对照会更有说服力。type_module_nominal_field_depends_on_later_private_toplevel_type.md 的源码与之高度相似但嵌套类型被移出了关联块变成文件顶层的“私有”名义类型ModType : { field : InternalType, } InternalType : [Some, Other]这一版本的 EXPECTED 不再为 NIL而是产生一条警告PRIVATE TYPE IN EXPOSED FIELD - type_mod_nominal_field_depends_on_later_private_toplevel_type.md:2:13:2:25PROBLEMS 段落给出了完整报告文本Thefieldfield ofModTyperefers toInternalType, butInternalTypeis private to this mod. Other mods can see this field becauseModTypeis exposed and not opaque, but they cannot name this private type.这条警告的实现位于编译器源码 src/canonicalize/ModuleEnv.zigprivate_type_in_exposed_field分支以Report.init(allocator, Private Type In Exposed Field, , .warning)构造警告报告并附带了与快照中完全一致的提示文本Hint: Expose the referenced type, makeModTypeopaque with::, or move the type intoModTypes associated block.也就是说将私有类型移入关联块正是编译器官方推荐的三种修复方式之一——本文主用例所做的正是这件事InternalType被放进ModType的关联块后它就自动成为ModType.InternalType这一可被外部命名的公开类型警告随之消失。顺带一提源码中还存在另一个同类警告 private_type_in_exposed_type“Private Type In Exposed Type”针对暴露类型本身而非字段引用私有类型的情况两者共同构成编译器对“公共表面泄漏私有类型”问题的完整诊断体系。而::关键字在 Roc 中用于声明不透明名义类型opaque nominal type——外部模块只能看到类型本身、看不到其内部表示见 docs/langref/types.md这正是提示中“make ModType opaque”的语义基础。完整链路一览与动手验证把上述各阶段串起来ModType.InternalType从源码到类型检查的完整旅程可以总结为词法ModType.InternalType被识别为UpperIdentNoSpaceDotUpperIdent复合标记序列语法解析器把InternalType记作字段标注中的类型名节点同时把其声明收入同一类型声明的associated子结构规范化类型名解析为(ty-lookup (name ModType.InternalType) (local))嵌套声明被展平为顶层s-nominal-decl身份为完整限定名类型推断ModType与ModType.InternalType两个名义类型成功注册诊断由于嵌套类型随关联块暴露不触发Private Type In Exposed Field警告结果为干净通过。如果你想在本地验证这一行为可以克隆本仓库后把上面任意一段 SOURCE 保存为ModType.roc并观察编译输出更直接的方式是阅读同目录下的其他快照用例例如 type_module_nominal_field_depends_on_private_toplevel_type.md私有顶层类型的同类场景与 type_module_opaque_field_depends_on_nested_type.md不透明类型字段依赖嵌套类型的场景体会“声明位置与可见性”如何共同决定 Roc 的诊断结果。小结通过剖析 type_module_nominal_field_depends_on_nested_type.md 这份快照测试我们完整还原了 Roc 编译器处理“名义类型字段依赖关联嵌套类型”的五个阶段词法层面由NoSpaceDotUpperIdent标记承载限定名语法层面字段与嵌套声明同属一个s-type-decl节点从而支持前向引用规范化层面引用被解析为带(local)标记的ty-lookup且嵌套类型以限定名ModType.InternalType身份暴露最终在类型推断中注册为两个独立名义类型并通过全部检查。与私有顶层类型场景的对照则揭示了 Roc 的可见性模型暴露的字段必须引用可被外部命名的类型而把类型移入关联块、公开类型或改用::不透明声明是编译器提示的三种标准解法。理解这条链路有助于你写出既符合模块封装边界、又不会被诊断工具打扰的 Roc 类型模块代码。【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表