- 编程语言
- AI Agent
- 编译器
- CLI
- 人工智能
【免费下载链接】baml
The programming language for agents
BAML 的类型系统规范文档 TYPE_SYSTEM.md 是一份**规定性(prescriptive)**文档——它描述类型系统"应该"如何工作,而非"当前"如何实现。baml_language/tools/type_quiz/COMPILER_DIVERGENCE.md正是为弥合这两者而存在的:它逐条登记编译器当前与规范相矛盾的每一个具体行为,用 canary commit 锁定观察基线,并通过 type-quiz 的一致性套件把"分歧仍然存在"变成一条可自动断言的测试。读完本文,你将掌握这份登记册的条目格式、三条已登记分歧(CD-001/002/003)的完整细节与最小复现,以及它在type_quiz工具链中如何作为"分歧守门员"工作——编译器一旦"追上"规范,套件就会失败,促使条目被关闭。
这份文档在项目中的定位
COMPILER_DIVERGENCE.md是type_quiz(BAML 类型系统测验工具)的三份"档案"之一:
| 档案 | 作用 | 引用者 |
|---|---|---|
| TYPE_SYSTEM.md | 类型系统的规定性规范 | 所有规则的事实来源 |
| SPEC_GAPS.md | 规范遗漏或规定不足的原则 | 无章节可引用的规则 |
| COMPILER_DIVERGENCE.md | 编译器与规范矛盾的行为 | 状态为CompilerDiverges的 item |
文档开篇即点明其性质:
The doc is prescriptive, so each entry is a compiler defect until a human rules otherwise.
也就是说,规范是权威,任何与规范相悖的编译器行为默认都是编译器缺陷,除非有真人裁决"这是有意为之"并修改规范。这种"以规范为准"的立场,与 README.md 中"一项被标记为分歧的 item 必须引用此处的条目,且套件在编译器停止分歧的那一刻失败,促使条目被关闭"的机制完全一致。
条目格式:一个分歧如何被精确描述
每条记录采用固定结构,确保分歧可以被自动化断言而不依赖人工判断:
- id:
CD-001起顺序编号,被代码注释与 conformance 套件引用,一旦分配便不再复用; - 规范原文:逐字引用 TYPE_SYSTEM.md 中相关章节的表述;
- 编译器实际行为:附上观察所用的 canary commit(如
9184ee38e、0488221d0、71dfab507)与日期,保证结论可复现; - 矛盾类型(contradiction kind),共三档:
Accepts:编译器接受了规范拒绝的程序;Rejects:编译器拒绝了规范接受的程序;Misreports:编译器像规范一样拒绝了,但报错代码不同或位置不同;
- Blocks:该分歧阻塞了
ns_bank中哪些 item 不能参与出题(见下文)。
CD-001:无守卫的别名循环被编译器接受
- 规范依据:规范 "Productivity" 一节规定:"a fully unguarded cycle is uninhabited:
type B = Bdenotesnever(and is a compile error, E0068)"。原因是 BAML 的递归子类型判定是余归纳(coinductive)的,任何推导中的循环都必须经过一个类型构造子(如数组/映射),否则循环不产生任何信息,该类型无人居住。 - 编译器行为(canary
9184ee38e,2026-09-08):以下两种写法在baml check与reflect.Package.compile下均干净通过,不发出任何 E0068:type B = B; type A = B; type B = A;无论该别名是否被用作参数、绑定或字段类型,结果都一样。
- 矛盾类型:
Accepts(编译器接受了规范要求拒绝的程序)。 - 阻塞 item:
aliases/unguarded-cycle。
代码侧的对应实现
在 ns_bank/items.baml 中,UnguardedCycleItem完整地实现了这个分歧 item:
function id(self) -> string throws never { "aliases/unguarded-cycle" } function status(self) -> root.engine.Status throws never { root.engine.CompilerDiverges { divergence: "CD-001", contradiction: root.engine.Contradiction.Accepts, } }它的generate会构造type B = B与其两别名形式,并给出规范期望的 key:E0068(两别名形式期待两条 E0068,因为规范中 E0068 按 SCC 中的每个循环成员计)。注释明确写道:"The compiler accepts both today: see COMPILER_DIVERGENCE.md, CD-001." 由于该 item 没有"能编译的邻居程序",其foil返回null。
CD-002:函数类型被拒绝作为接口实现目标
- 规范依据:规范 "Concrete Types" 明确把函数类型列入具体类型("include all primitives, all class types, all enum types, all function types...");而 "Interfaces" 规定"Only concrete types may implement interfaces"。两者合起来意味着函数类型应当是合法的
implement目标。 - 编译器行为(canary
0488221d0,2026-09-10):下面的程序被拒绝,报E0138: cannot implement an interface for (int) -> int throws never — the target must be a single concrete type:implement Marker for (int) -> int throws never {}同一轮测试中,其余具体目标均被接受:
int、string、int[]、map<string, int>、一个枚举、以及int的别名;非具体目标(存在类型、联合、int?、unknown)被正确地以同一错误码拒绝。 - 矛盾类型:
Rejects(编译器拒绝了规范接受的目标)。 - 阻塞 item:实现目标池(implementation-target pool)的"具体一半"——在问题解决前,函数类型不会作为实现目标参与出题。
代码侧佐证见 ns_algebra/ty.baml 的注释:"a function type is concrete but rejected (CD-002)"。这显示Ty模型中函数类型被归为具体类型,但编译器行为与之背离——正是文档要登记的分歧。
CD-003:注解中嵌套联合的let绑定被"反向检查"
这是三条中最微妙、也是 README 中着墨最多的一条。
- 规范依据:规范 "BAML Subtyping Cases" 规定
T <: (T | ...)对所有T成立,且联合是可结合的——A | (B | C)就是A | B | C,不包含任何D;而 "Implementation & Design Guidelines" 的金科玉律是"runtime values may never violate their compile-time type contracts"。 - 编译器行为(canary
71dfab507,2026-09-18):以下程序编译通过:function through(right: bool | string | bigint) -> bool | string | int { let left: bool | (string | int) = right; left }调用
through(5n)返回一个在bool | string | int槽位中反射为bigint的值;随后对bool、string、int的穷尽match会走int分支——一个不该出现的bigint穿透了类型契约。而把注解展开写成let left: bool | string | int = right则如规范所愿被 E0001 拒绝。 - 触发条件极其精确:
let的注解是顶层联合,且其成员中有带括号的联合——bool | (string | int)、(bool | string) | int、(bool | string | int) | null都触发。以下情况不触发:整个联合被括号包住((string | int))、括号成员不是联合(bool | (string))、嵌套出现在数组元素或泛型实参内部;同一类型写作参数、字段、返回类型或数组元素类型时也检查正确。 - 反向检查的表现:当初始化器与注解没有共享成员时,绑定确实被拒绝,但检查明显反向——报
expectedbigint, foundint | string | bool``,且 span 落在注解而非初始化器上——这正是"把注解当作可反驳模式去测初始化器"才会给出的报告;当两者共享成员时(如上面的例子),则什么都不报。 - 矛盾类型:
Accepts。 - 阻塞 item:
unions/nested-in-let-annotation;同时它把任何会拼写出此类注解的类型对从let位点排除出去(root.algebra.sites_for不再允许它们落到绑定上)。
这个分歧是怎么被发现的
文档指出它由 near miss 事实union_regrouped_differs发现:此前银行一直在把A | (B | C)放到绑定位点,但永远只放在等价的A | B | C旁边(union_associates),而等价关系正反两个方向都能编译,分歧被掩盖了。直到第一个非等价的嵌套联合出现,问题才暴露——这正是 README 中"near miss 是形状的平衡者"设计思想的实战成果。对应的实现见 ns_bank/facts.baml 的union_regrouped_differs:A | (B | C)对A | B | D,结论为Unrelated。
在 ns_bank/items.baml 中,NestedUnionLetItem固定流方向为reversed: true(使嵌套联合恰好成为注解),位点为SiteKind.Let,状态为CompilerDiverges { divergence: "CD-003", contradiction: Accepts }。README 的"Compiler issues surfaced by this tool"第 15 条也独立记录了同一问题,并交叉引用 CD-003。
分歧如何被"钉死":conformance 套件的断言机制
COMPILER_DIVERGENCE.md不是一份被动记录——它被测试代码直接读取并断言。三层机制保证了登记册与真实编译器状态永不脱节:
- 条目存在性校验:ns_conformance/bank.baml 直接读取
tools/type_quiz/COMPILER_DIVERGENCE.md文本;任何声明CompilerDiverges的 item 若在文档中找不到对应条目,会报no entry <id> in COMPILER_DIVERGENCE.md。 - 分歧方向校验:同一文件第 186 行起,把 item 声明的
divergenceid 与文档条目逐一对照。 - 行为校验:ns_engine/verify.baml 对
CompilerDiverges状态的要求是:该 item 产生的每一个案例都必须按声明的矛盾方式与编译器实际结果不符;一旦编译器被修复、开始与规范一致,verify就会判定"不再分歧",conformance 套件随即失败。
这正应了文档第一段的机制闭环:"a compiler that catches up fails the suite until the item is promoted and the entry is closed."而"提升(promote)"意味着把 item 状态从CompilerDiverges改为Verified、从登记册中关闭条目——这是一个需要真人裁决的动作,裁决的输入正是这份文档中逐字引用的规范文本与 canary 观察记录。
与 SPEC_GAPS.md 的分工
两份档案容易混淆,但它们互补而不重叠:
- SPEC_GAPS.md(G-001 至 G-005):规范没说或说得不够——如"子类型检查发生在哪些位点"(G-001)、
void与null在函数类型中的关系(G-002)。这类条目为"无章节可引用的规则"提供引用锚点。 - COMPILER_DIVERGENCE.md:规范说了但编译器不听——即本文的三条 CD 记录。这类条目为"状态为
CompilerDiverges的 item"提供事实锚点。
两者共同构成 type-quiz 的"双重护栏":一条规则要么有规范章节可引(否则必须挂靠 SPEC_GAPS),要么被编译器违背(则必须挂靠 COMPILER_DIVERGENCE),不存在第三种"无主"状态。
从文档到工具链:如何查看与运行
这份登记册属于type_quiz包,运行入口与整个套件绑定:
# 从 baml_language/ 下(mise 环境) mise run type-quiz-test # 整个套件:含 conformance 对分歧的断言 mise run type-quiz-lint # 分层、禁用 API、通配符 arm 检查 mise run fmt-type-quiz # 该包使用的格式化器ns_conformance/bank.baml 对COMPILER_DIVERGENCE.md的读取,正是type-quiz-test在 CI(crates/baml_tests/tests/type_quiz.rs 下运行)中断言分歧仍然成立的路径。如果你只想检查某一条分歧是否仍可复现,也可以直接对 CD-001 或 CD-003 的 item 运行baml check观察其行为——文档中已给出最小复现程序与当时的 canary commit。
小结:一份"会失败的规范对照表"
COMPILER_DIVERGENCE.md的独特之处在于它把"规范与实现的偏差"从口头共识变成了可执行断言的数据源:
- 每条记录锁定三个事实:规范怎么说、编译器怎么做(含 canary 基线)、矛盾属于哪一档;
- 三项已登记分歧(CD-001 接受无守卫循环、CD-002 拒绝函数类型实现目标、CD-003 反向检查嵌套联合的
let注解)都有最小复现与代码侧的 item 实现可对照; - 只要编译器尚未修复,conformance 套件就持续断言分歧成立;一旦修复,套件失败倒逼条目被关闭、item 被提升为
Verified——这份文档因而永远忠实于"编译器与规范的真实距离"。
- 编程语言
- AI Agent
- 编译器
- CLI
- 人工智能
【免费下载链接】baml
The programming language for agents
相关推荐
Linux 内核 CXL 平台约定(CXL Linux Conventions)指南:规范偏差记录与 PRM 地址翻译实现解析
Linux 内核 CXL 平台约定(CXL Linux Conventions)指南:规范偏差记录与 PRM 地址翻译实现解析 导读 CXL(Compute E
操作系统内核驱动驱动开发虚拟化嵌入式网络存储PRQL 模块(Modules)机制详解:从规范设计到 prqlc 编译器实现
PRQL 模块(Modules)机制详解:从规范设计到 prqlc 编译器实现 导读 PRQL 的模块(module)是用于组织声明(declaration)的
后端Angular ngtsc 编译器性能追踪机制解析:`perf` 包与 `tracePerformance` 使用指南
Angular ngtsc 编译器性能追踪机制解析: perf 包与 tracePerformance 使用指南 Angular 的 Ivy 编译器(ngtsc
前端Web框架
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考