Unison 名称解析深度指南:命名空间与文件声明的冲突消歧、歧义错误与 TDNR 类型定向解析机制

发布时间:2026/10/10 2:43:27
Unison 名称解析深度指南:命名空间与文件声明的冲突消歧、歧义错误与 TDNR 类型定向解析机制
编程语言编译器语言运行时开发工具【免费下载链接】unisonA friendly programming language from the future项目地址https://gitcode.com/gh_mirrors/un/unison点击查看免费下载本篇技术指南以 Unison 仓库中的幂等转录测试 unison-src/transcripts/idempotent/name-resolution.md 为骨架系统讲解 Unison 编译器中“命名空间namespace中的声明”与“当前文件scratch.u中的声明”同时出现在作用域时同名类型/术语如何被解析、何时报歧义错误、以及 Type-Directed Name ResolutionTDNR类型定向名称解析如何依据期望类型自动消歧。读完本文你将掌握 Unison 名称解析的优先级规则、歧义错误的完整报错形态与修复手法并能从源码层面理解synthesizeFile中 TDNR 决策的底层调用链。背景Unison 中两套名称来源为何会产生“同名冲突”Unison 是一种基于内容寻址的编程语言代码库codebase中的每个定义都以其内容哈希唯一标识名称只是指向这些定义的“指针”。当一个 UCM 会话中同时存在两种名称来源时就会发生名称冲突命名空间namespace中的声明已经通过add提交到代码库分支中的定义例如Namespace.Foo、ns.foo当前文件scratch file中的声明正在编辑但尚未提交的定义例如File.Foo、file.foo。在 unison-src/transcripts/idempotent/name-resolution.md 中全部示例都使用scratch/main项目上下文先builtins.mergeio lib.builtins合并内置库再定义并add命名空间声明随后在 scratch 文件中编写文件声明最后用project.delete scratch清理。这正是转录测试transcript的典型工作流它以 UCM 命令与文件编辑交替出现的方式回归验证名称解析行为。需要说明的是该文档所在目录unison-src/transcripts/idempotent/下的测试都是“幂等”的它们被设计为可重复执行且结果稳定任何一次运行都必须复现文档中:added-by-ucm标记的输出否则测试失败。因此本文中出现的所有 UCM 输出含错误信息都是可验证的回归基准。类型名冲突的三种典型场景场景一两个限定名都没有“精确匹配”Foo报歧义错误转录文档的第一个示例展示了最典型的歧义情形We have a namespace type namedNamespace.Fooand a file type namedFile.Foo. A reference to the typeFoois ambiguous.先在命名空间中建立Namespace.Footype Namespace.Foo Bar随后通过 UCM 提交scratch/main builtins.mergeio lib.builtins Done. scratch/main add Okay, Im searching the branch for code that needs to be updated... Done.接着在 scratch 文件中定义File.Foo并试图用裸名Foo引用它type File.Foo Baz type UsesFoo UsesFoo Foo此时 UCM 拒绝加载并给出完整的歧义诊断Loading changes detected in scratch.u. ❓ I couldnt resolve any of these names: 2 | type UsesFoo UsesFoo Foo Name Type Suggestions Foo type File.Foo Namespace.Foo关键信息有三层错误定位明确指出第 2 行type UsesFoo UsesFoo Foo中的Foo无法解析候选列表现状Foo有两个候选File.Foo与Namespace.Foo两者都是两段式限定名与裸名Foo均非精确匹配故无法唯一确定修复指引Suggestions列给出的两个全限定名正是消除歧义的标准手段。将引用改为全限定名后文件即可正常加载type File.Foo Baz type UsesFoo UsesFoo Namespace.Foo File.FooLoading changes detected in scratch.u. type File.Foo type UsesFoo Run update to apply these changes to your codebase.这张Name / Type / Suggestions三列表格不是转录脚本的临时输出而是编译器错误渲染模块的固定产物——在 parser-typechecker/src/Unison/PrintError.hs 中它由Pr.column3Header Name Type Suggestions生成用于统一呈现“无法解析的名字及其候选建议”。而错误正文I couldnt figure out what ... refers to here则来自同一文件 PrintError.hs它先渲染错误点源码再调用handleSuggestions按候选的“名称相似度/类型匹配度”分级给出建议。场景二命名空间类型是精确匹配时裸名指向它第二个示例把局面反过来命名空间中存在一个恰好叫Foo的类型而文件里只有File.FooWe have a namespace type namedFooand a file type namedFile.Foo. A reference to the typeFoois not ambiguous: it refers to the namespace type (because it is an exact match).type Foo Bar提交后在文件中定义File.Foo并写type UsesFoo UsesFoo Footype File.Foo Baz type UsesFoo UsesFoo Foo这次没有报错文件被正常接受Loading changes detected in scratch.u. type File.Foo type UsesFoo Run update to apply these changes to your codebase.用view确认解析结果UsesFoo引用的是裸名Fooscratch/main add Okay, Im searching the branch for code that needs to be updated... Done. scratch/main view UsesFoo type UsesFoo UsesFoo Foo结论当某个候选与裸名逐段完全一致精确匹配时它直接胜出不再产生歧义——命名空间中的Foo与裸名Foo完全同名因此优先被选中。场景三文件类型是精确匹配时裸名指向它第三个示例是对称情形命名空间里是Namespace.Foo文件中恰好定义了裸名FooWe have a namespace type namedNamespace.Fooand a file type namedFoo. A reference to the typeFoois not ambiguous: it refers to the file type (because it is an exact match).type Namespace.Foo Bar提交后在文件中定义type Foo Baz与type UsesFoo UsesFoo Footype Foo Baz type UsesFoo UsesFoo Foo同样成功加载并可通过view UsesFoo看到type UsesFoo UsesFoo Foo。把场景二、三合起来看类型名消歧的规则可以归纳为命名空间中的声明文件中的声明裸名Foo的解析结果Namespace.FooFile.Foo歧义报错并给出建议表Foo精确匹配File.Foo解析到命名空间类型FooNamespace.FooFoo精确匹配解析到文件类型Foo即裸名精确匹配优先于段数更多的限定名候选若多个候选都非精确匹配则歧义报错。术语的 TDNR 消歧类型不同时按期望类型自动选择类型名遵循“精确匹配优先”的静态规则而术语term的消歧还多了一层机制TDNRType-Directed Name Resolution类型定向名称解析。当多个同名候选的类型不同、且使用处存在明确期望类型时编译器会选择“能够通过类型检查”的那一个。转录文档中标题重复为# Example 4的段落连续演示了三个术语消歧变体下面逐一拆解。变体 A同名术语类型不同foo被解析为file.foo场景描述转录原文We have a namespace termns.foo : Natand a file termfile.foo : Text. A reference to the termfoois ambiguous, but resolves tons.foovia TDNR. … but resolves tofile.foovia TDNR.先在命名空间加入ns.foo : Natns.foo : Nat ns.foo 42scratch/main add Okay, Im searching the branch for code that needs to be updated... Done.然后在文件中定义file.foo : Text并在bar : Text中引用裸名foofile.foo : Text file.foo foo bar : Text bar foo bar虽然foo在“名称上”同样歧义ns.foo与file.foo都包含名为foo的段但由于bar的期望类型是Text需要Text参数只有file.foo : Text满足因此 TDNR 自动选中它Loading changes detected in scratch.u. bar : Text file.foo : Text Run update to apply these changes to your codebase.变体 B期望类型为Nat时解析到ns.foo紧接着的第二个变体把使用处改为bar : Natfile.foo : Text file.foo foo bar : Nat bar foo 42其输出同样显示bar : Nat与file.foo : Text被成功加载。需要提醒读者的是这段转录文本的开头描述声称“resolves tofile.foovia TDNR”但从同一段落的代码与输出bar : Nat foo 42被接受可以推断实际生效的解析必然落向能通过类型检查的ns.foo : Nat——此处转录的叙述文字与其代码输出存在出入阅读该文件时不宜照搬这段描述应以“TDNR 选择能通过类型检查的候选”这一机制为准。变体 C两个候选类型相同TDNR 无法救援报歧义错误TDNR 的边界条件很清晰当多个同名候选的类型也相同时类型定向无法区分彼此歧义错误必然发生。第三个变体正是如此We have a namespace termns.foo : Natand a file termfile.foo : Nat. A reference to the termfoois ambiguous. A reference tons.fooorfile.foowork fine.file.foo : Nat file.foo 43 bar : Nat bar foo 10UCM 给出详细诊断Loading changes detected in scratch.u. I couldnt figure out what foo refers to here: 5 | bar foo 10 The name foo is ambiguous. Its type should be: Nat I found some terms in scope that have matching names and types. Was any of these what you wanted? file.foo : Nat ns.foo : Nat这段报错比“无法解析任何名字”更具针对性它明确告诉用户foo的类型应为Nat并在作用域内找到两个名称与类型都匹配的候选file.foo : Nat与ns.foo : Nat请求用户指明意图。唯一出路是使用全限定名file.foo : Nat file.foo 43 bar : Nat bar file.foo ns.foo提交后view bar可以看到全限定引用被保留scratch/main add Okay, Im searching the branch for code that needs to be updated... Done. scratch/main view bar bar : Nat bar use Nat file.foo ns.foo源码视角TDNR 在synthesizeFile中的完整链路转录测试验证的是编译器对外行为其底层实现在 parser-typechecker/src/Unison/FileParsers.hs 的synthesizeFile函数中链路清晰可循预处理Term.prepareTDNR term将用户文件中所有尚未解析的裸引用替换为“空白/候选集合”形式为类型检查期间的消歧留出决策空间类型检查并解析Typechecker.synthesizeAndResolve unisonFilePPE env0在类型检查的同时执行消歧——每遇到一个同名候选集合就利用当前推断出的期望类型逐一尝试能通过类型检查的候选胜出回填决策类型检查成功后applyTdnrDecisions infos把记录在infosContext.TopLevelComponent等类型检查信息中的 TDNR 决策重新应用到用户原始 term 上得到最终解析完毕的表达式错误转换任何失败都会经convertNotes转换为用户可见的 Note最终由 PrintError.hs 渲染为前文展示的“无法解析/歧义”诊断文案。类型检查器一侧的支撑逻辑分布在 parser-typechecker/src/Unison/Typechecker/Context.hs源码注释明确说明求解 TDNR 时会把上下文变量区分为“参与 TDNR 解的部分”与“不参与的部分”只对前者重新推回以避免泛化干扰消歧而 parser-typechecker/src/Unison/Typechecker.hs 的注释则从另一侧说明若某个空白在类型检查后仍有多个候选只要其中唯一一个能通过类型检查就采用它——这正是“同名不同类型 → TDNR 自动选择”的底层依据。此外与 unison-src/transcripts/idempotent/name-resolution.md 同目录的姊妹转录 unison-src/transcripts/idempotent/tdnr.md 专门验证了 TDNR 的优先级次序文件内能通过类型检查的局部项优先于文件内不能通过类型检查的局部项、也优先于命名空间中不能通过类型检查的项。模糊匹配fuzzy类行为则在 unison-src/transcripts/idempotent/name-resolution-fuzzy.md 与 unison-src/transcripts/idempotent/resolution-failures.md 中有更完整的用例覆盖。名称解析规则总结与工程实践综合上述五个示例类型三例 术语三变体Unison 的名称解析规则可总结为精确匹配优先裸名若与某个候选的完整名称逐段一致该候选直接胜出无论它来自命名空间还是当前文件类型定向消歧TDNR无精确匹配、但候选类型不同时依据使用处的期望类型选出唯一能通过类型检查的候选歧义报错候选都非精确匹配类型名场景或候选类型相同导致 TDNR 无法区分术语场景时编译报错全限定名兜底任何歧义都可以通过书写Namespace.Foo、File.Foo、ns.foo、file.foo这类完整路径立即消除。对 UCM 使用者的实战建议当错误信息出现I couldnt resolve any of these names或The name foo is ambiguous时优先按Suggestions列表或“found some terms in scope”清单改写为全限定引用对同一个裸名有多个类型相同的候选不要依赖 TDNR它无能为力直接全限定或使用use指令缩小作用域若期望通过 TDNR 自动选择请确保候选类型之间存在差异且使用处的类型约束足够明确否则无法触发类型定向机制。如需在本地复现上述全部行为可进入unison-src/transcripts/idempotent/目录查看 name-resolution.md 的完整转录含全部 UCM 输出并对照 tdnr.md 验证 TDNR 的优先级次序编译器的实现与报错渲染则分别位于 FileParsers.hs、Typechecker/Context.hs 与 PrintError.hs。赞分享编程语言编译器语言运行时开发工具【免费下载链接】unisonA friendly programming language from the future项目地址https://gitcode.com/gh_mirrors/un/unison点击查看免费下载相关推荐TypeSpec 命名空间Namespace完全指南声明、嵌套、文件级命名空间与 using 名称解析TypeSpec 命名空间Namespace完全指南声明、嵌套、文件级命名空间与 using 名称解析 导读 命名空间Namespace是 TypeS编程语言编译器后端深入 Unison 类型导向名称解析TDNR从 fix845 转录测试看短名解析、报错建议与源码实现深入 Unison 类型导向名称解析TDNR从 fix845 转录测试看短名解析、报错建议与源码实现 导读 本篇文章围绕 Unison 语言编译器仓库中的编程语言编译器语言运行时开发工具Unison 语言 namespace 指令全解析文件级命名空间前缀与名称重写机制Unison 语言 namespace 指令全解析文件级命名空间前缀与名称重写机制 namespace 指令是 Unison 语言GitHub 加速计划 /编程语言编译器语言运行时开发工具上一篇Claude Code Haha v0.5.5 版本技术解读生成文件原生打开、图片编辑信任边界与流式可靠性加固下一篇深入 MCP 开源社区从贡献协议核心到发布自研 MCP 服务器mcp-for-beginners 社区参与完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考