资讯动态

Roc 包文档投影机制解析:嵌套类型如何通过公开别名生成独立文档模块

发布时间:2026/9/18 21:49:28 来源:尧图企业网站定制
Roc 包文档投影机制解析嵌套类型如何通过公开别名生成独立文档模块【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/rocRoc 编译器的文档提取系统docs extraction在处理package声明时会把导出的嵌套类型nested source types通过其公开别名public aliases投影project为独立的文档模块并同步重写类型签名中的交叉引用。本文以仓库中的快照测试 docs_package_exposed_nested_type_aliases.md 为骨架结合 src/docs/extract.zig、src/docs/DocModel.zig 与 src/snapshot_tool/main.zig 的源码实现完整还原这一机制的工作原理、S-expression 输出格式与测试验证方式帮助读者理解公开别名投影在 Roc 文档管线中的具体语义。一、关联文档定位一份 docs 类型的快照测试test/snapshots/docs_package_exposed_nested_type_aliases.md是 Roc 编译器仓库中的一份docs 类型快照typedocs。根据 test/snapshots/README.md 的说明快照测试通过捕获编译各阶段tokenization、parsing、canonicalization、type checking 等的输出来验证编译器行为并防止回归。docs 快照使用三段式结构META以~~~ini包裹的元信息其中descriptionPackage docs project nested source types through their public aliases一句话点明本测试的意图——包的文档系统把嵌套的源类型通过其公开别名投影出来typedocs表明这是一份文档提取快照。SOURCE以## 文件名.roc分节的多文件 Roc 源码由 src/snapshot_tool/main.zig 中的parseMultiFileSource解析。DOCS以~~~clojure包裹的、对提取结果进行 S-expression 序列化后的期望输出。也就是说这份文件既是测试用例SOURCE 是输入又是回归基准DOCS 是期望输出同时还是一份文档系统如何呈现嵌套类型别名的权威说明。二、测试场景还原用as把嵌套类型导出为公开别名SOURCE 部分包含两个文件。包入口main.rocpackage [Container.Blub as Foo, Container.Other as Bar] {}这是一个典型的包package入口声明。package [...]中的每一项都是对源模块内某个顶层类型type module的投影声明Container.Blub as Foo把源类型Container的嵌套类型Blub导出到公共命名空间并重命名为FooContainer.Other as Bar同理把Other导出并重命名为Bar。注意as之后的Foo/Bar与源名称完全不同——这正是本测试要验证的核心场景文档页面必须使用公开别名Foo/Bar来命名模块而不是源类型名Blub/Other。类型定义文件Container.roc## Private parent documentation. Container :: [].{ ## Public Blub documentation. Blub :: [].{ to_other : Blub - Other to_other |_| crash not implemented } ## Public Other documentation. Other :: [].{ to_blub : Other - Blub to_blub |_| crash not implemented } Private :: [].{} }这里Container是一个不透明opaque::的 tag-union 类型内部嵌套了三个成员嵌套类型声明方式文档注释是否导出Blub::opaque## Public Blub documentation.是as FooOther::opaque## Public Other documentation.是as BarPrivate::opaque无否未在 package 中导出Blub上定义了方法to_other : Blub - OtherOther上定义了方法to_blub : Other - Blub两个方法在类型签名中互为交叉引用用crash not implemented占位实现仅用于类型检查。而Private虽然嵌套在同一个Container中但没有出现在package导出列表中因此必须被文档系统过滤掉。三、DOCS 期望输出逐段解读投影后的文档 S-expressionpackage-docs提取结果以确定性的 S-expression 序列化(package-docs ...)由 src/docs/DocModel.zig 中的PackageDocs.writeToSExpr生成。本测试的 DOCS 部分完整如下(package-docs (name test-app) (mod (name Bar) (package mod) (kind type_mod) (doc Public Other documentation.) (entry (name Bar) (kind opaque) (type Bar :: (tag-union)) (doc Public Other documentation.) (entry (name to_blub) (kind value) (type (fn (type-ref (mod mod.Bar) (name Bar)) (type-ref (mod mod.Foo) (name Foo)))) ) ) ) (mod (name Foo) (package mod) (kind type_mod) (doc Public Blub documentation.) (entry (name Foo) (kind opaque) (type Foo :: (tag-union)) (doc Public Blub documentation.) (entry (name to_other) (kind value) (type (fn (type-ref (mod mod.Foo) (name Foo)) (type-ref (mod mod.Bar) (name Bar)))) ) ) ) )这份输出蕴含了投影机制的五个关键语义别名即模块名(mod (name Bar) ...)与(mod (name Foo) ...)两个顶层模块模块名直接取公开别名而非源名称Other/Blub。模块类型为type_mod(kind type_mod)表明这是类型模块——由某个类型投影而来。ModuleKind.type_module枚举定义于 src/docs/DocModel.zig序列化字符串为type_module的toStr输出type_module快照后处理会把type_module重写为type_mod见 test/snapshots/README.md。文档注释上浮为模块文档源类型上的## Public Other documentation.被提升为Bar模块的(doc ...)同时保留在类型条目自身的(doc ...)中。这是因为投影后类型页面即模块页面源类型注释成为页面注释对应 src/docs/extract.zig 中module_doc_extract的处理逻辑。类型以 opaque 呈现(type Bar :: (tag-union))中::表示不透明类型(tag-union)表示底层是 tag-union但具体标签并未展开——即文档只暴露类型身份不暴露内部构造器。类型引用按别名重写to_blub的类型被序列化为(fn (type-ref (mod mod.Bar) (name Bar)) (type-ref (mod mod.Foo) (name Foo)))即方法参数是当前模块 Bar 中的 Bar 类型返回值是模块 Foo 中的 Foo 类型。源文件里写的Other - Blub在文档模型中被重写为公开别名路径mod.Bar/mod.Foo保证了所有交叉引用都指向投影后的公开页面。四、源码实现extract.zig 中的投影管线投影行为的核心实现在 src/docs/extract.zig。它围绕PublicTypeProjection结构组织pub const PublicTypeProjection struct { public_name: []const u8, // 公开别名如 Foo package_name: []const u8, // 所属包显示名如 mod source_env: *const ModuleEnv, // 源模块环境 source_identity: *const [32]u8, // 源模块身份哈希 source_decl: CIR.Statement.Idx, // 源类型声明 public_order: u32, // 在包公开表面的声明顺序 };定义于 src/docs/extract.zig并用sortPublicTypeProjectionsL248-L250按源身份哈希 公开顺序 公开名排序以便后续二分查找路由。4.1 名称重写projectedEntryName与rebaseEntryForProjection投影之后条目名必须从源名换成公开名。projectedEntryName 完成这一映射若条目名就是投影根则直接替换为selected.public_name若是投影根的子成员则保留后缀例如Blub.to_other会被重写为Foo.to_other。而 rebaseEntryForProjection 会就地更新每个条目的名字。4.2 归属判定nameIsAtOrUnder与nameBelongsToProjection投影必须精确划走属于该类型的所有定义。nameIsAtOrUnder 判断一个名称是否位于某根之下根自身或根.子路径nameBelongsToProjection 与 statementBelongsToProjection 据此过滤定义集。在本测试中Private不属于任何投影根因此不会进入任何文档模块——这正好印证 DOCS 输出里没有任何Private条目。4.3 引用路由projectedTypeReference与selectPublicProjection方法签名里的类型引用需要解析到应该指向哪个公开模块。projectedTypeReferenceL715-L741先根据引用是本地local还是外部external取出(identity, target_statement)对再交给selectPublicProjectionL743-L777若当前正处于某个投影中且该投影包含此 identity 与声明则直接命中当前投影否则在排好序的public_types上按source_identity二分查找再在所有候选投影中挑选最近的公开根root_len最大者作为命名空间所有者。最终annotatedTypeReferenceDisplayL787-L818把命中投影的类型引用渲染成mod.别名路径 别名类型名。这就是 DOCS 中出现(type-ref (mod mod.Foo) (name Foo))的来源——源文件里的Blub/Other在此被替换成了公开别名。4.4 提取入口与过滤extractModuleDocsWithOptionsL266 起接收ExtractOptions含exposed_names、public_type、public_types等见 L217-L234逐条过滤定义并递归提取子条目。模块名L307-L310与本地模块路径L319-L321都优先取public_name保证文档页面永远以公开别名命名。五、数据模型与确定性序列化DocModel.zig提取结果统一装入 src/docs/DocModel.zig 的数据模型PackageDocsL10持有包名与模块列表writeToSExpr/writeToSExprIndentedL22-L38输出(package-docs (name ...) (mod ...)*)ModuleKindL734-L750区分app / module / package / platform / type_module快照中看到的type_mod即由此枚举序列化而来DocEntry支持递归的children使Foo - to_other这样的嵌套条目结构得以表达本测试中方法作为类型条目的子条目出现。此外还有两个重要的后处理步骤resolveDocRefsL42-L52解析文档注释中的简写引用如[Str]、[Utf8.default]通过PackageDocRefResolver把它们路由到最终文档布局中的具体页面/锚点reshapeBuiltinL67-L130把编译器内部的巨型Builtin类型展开为每个内建类型Str、List、Num等独立成模块并重写所有相对引用——与别名投影一样属于把内部结构重塑为面向用户的文档页面的机制。对快照而言S-expression 序列化的确定性至关重要模块按moduleDocsLessThan排序快照工具中 main.zig#L3645确保同一输入永远产生相同输出快照比对才可靠。六、快照如何运行与验证processDocsSnapshot 全流程docs 快照由 src/snapshot_tool/main.zig 中的processDocsSnapshot处理流程分六步多文件解析parseMultiFileSourceL3758 起把 SOURCE 段按## xxx.roc子标题拆成若干SourceFile临时目录构建把源码写入临时目录用BuildEnv.build执行真实的包构建L3555-L3574收集公开类型投影遍历build_env.getCompiledPublicModules()为每个有public_type_decl的模块构造PublicTypeProjectionL3595-L3612并排序逐模块提取对每个 documentable module 调用extractModuleDocsWithOptions传入exposed_names、public_type、public_types等选项L3615-L3641组装并序列化把所有ModuleDocs装入PackageDocs名为test-app调用writeToSExpr输出 S-expressionL3647-L3667比对与写入把新生成的 DOCS 与文件中的既有 DOCS 段比对——update模式直接覆盖check模式不一致即失败并给出 diffnone模式仅告警L3670-L3700。日常用法见 test/snapshots/README.md#L36-L42# 生成所有快照 zig build run-snapshot-tool # 只更新指定快照 zig build run-snapshot-tool -- file_path # 从 problems 更新期望输出 zig build run-snapshot-tool -- file_path --update-expected七、与兄弟快照的横向对照同一目录下还有几个与之互补的 docs 快照可以交叉理解嵌套类型投影的边界docs_package_exposed_nested_type_cross_module.md嵌套公开类型位于不同源文件First.Foo与Second.Bar其中Bar是:声明的 nominal 记录类型验证跨模块引用Foo.to_bar : Foo - Second.Bar也能被投影路由到正确的模块页面docs_type_module.mdapp场景下带文档注释的 type moduleColor展示(doc ...)从类型注释上浮为模块文档的通用行为docs_package_hides_private_modules.md验证未导出的私有模块不会出现在 package docs 中——与本测试中Private类型被过滤属于同一设计原则。这些测试共同构成了公开表面public surface决定文档可见性的完整保障只有被包导出或 app/platform 提供的类型才会被投影为文档模块源文件内部的私有细节一律不泄漏。八、总结与实战要点通过这份快照及其源码实现可以得到关于 Roc 包文档提取系统的几个可复用结论package [A.B as C]不只是编译期导出声明也是文档命名空间声明嵌套类型被投影后其公开别名成为文档模块名源类型名不再出现在文档页面上。类型签名中的交叉引用会按投影路由重写Other - Blub会变成指向mod.Bar/mod.Foo的type-ref保证文档内链接始终有效源码依据projectedTypeReference、selectPublicProjection。文档注释随投影上浮被投影类型的##注释同时成为模块文档与类型条目文档私有父类型Container的注释Private parent documentation.则不会出现在任何投影模块中。opaque 类型的构造器细节不进入文档(type Bar :: (tag-union))只保留不透明 tag-union这一事实避免暴露内部表示。快照测试以真实构建 确定性序列化保证回归安全任何改变投影行为、命名规则或 S-expression 格式的改动都会在zig build run-snapshot-tool中被捕获。【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价