
Roc 编译器的工程铁律从 AGENTS.md 到源码中 RedirectRule 的强制机制【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/rocRoc 是一个快速、友好且面向函数式的语言其编译器对纪律性的要求近乎苛刻根目录下的 AGENTS.md 用 10 条硬性规则定义了编译器的工程不变量——禁止 workaround、禁止 fallback 与启发式、各阶段必须消费显式数据、后端不得自行思考引用计数并规定了解题器checker中探测-改写操作的声明先行流程。读完本文你将理解这些规则各自约束的是什么行为、为什么这样设计以及它们如何被 design.md 的Solver-Mutating Rewrites章节和 src/types/store.zig 中的类型签名机制落到实处。规则总览10 条不变量AGENTS.md 的全部规则可归纳为四层层规则设计先行修改任何代码前必须先阅读 design.md它是已检查模块checked modules、post-check IR 管线、LIR、ARC、后端、LirImage 与编译器不变量的前瞻性设计参考数据流纪律禁止 workaround绝对禁止除解析与错误报告外禁止一切 fallback 与启发式除解析与错误报告外每个编译阶段必须消费前一阶段产生的显式数据而不是试图恢复、猜测、重建、近似或尽力而为地弥补缺失信息后端纪律后端不得以任何方式思考引用计数只能笨拙地dumbly执行前序编译步骤显式发出的 LIRincref/decref语句流程纪律Fuzzer 必须生成小型带类型语言结构并随机组合禁止手工场景发射器的目录式fuzzerzig build minici失败时先定向修复失败的 sectionchecker 中新增探测-改写重写前必须先声明规则这些规则的边界措辞值得注意几乎每条禁令都保留了同一个例外——解析parsing与错误报告error reporting。这意味着 Roc 把语言核心判断与用户交互体验明确分开类型求解、IR 生成、优化、代码发射各阶段必须是确定性、无猜测的数据变换唯有解析和报错这两类天然需要猜用户意图的环节被允许使用容错手段。阶段间显式数据契约每个阶段消费前一阶段的显式数据这一条是对编译器工程中最常见坏味道——隐式恢复implicit recovery——的系统性封堵。从 design.md 的结构看该不变量贯穿 checked modules、post-check IR、LIR 与后端上游阶段若未产生某类信息下游阶段不允许自己去推断只能报错或走设计文档中声明的显式路径。这一约束直接服务于可审查性——如果某个阶段可以best effort地重建信息那么代码评审时无法区分正常路径与兜底路径编译器行为也就无法被静态推理。后端与 ARC引用计数的显式化AGENTS.md 中针对后端的规定是后端以任何方式思考引用计数都是绝对禁止的唯一允许的行为是笨拙地执行前序编译步骤显式发出的 LIRincref和decref语句。从 design.md 的相关章节可以印证这一设计的意图后端只接收普通 LIR 与显式的 ARC 语句不得知道一个值究竟来自 public iterator、minted iterator、forced-dynamic callable state 还是被标量化的循环。把引用计数决策完全上移到 IR 层让后端退化为纯执行者是保证多后端当前与未来目标行为一致的关键隔离手段。Fuzzer 与 minici 的流程纪律关于 fuzzerAGENTS.md 规定其必须生成小型带类型语言结构并随机组合明确否决了由手工编写场景发射器拼成的目录式fuzzer。这保证了测试语料分布来自语言文法的随机组合而非开发者手挑的用例避免覆盖偏向。关于 build.zig 中的minicistep其定义为Run a subset of CI build and test steps见 build.zigAGENTS.md 给出了一套明确的调试协议当zig build minici在某个 section 失败时先修复该 section 并反复重跑这个特定 section直到通过再回到完整的zig build minici。完整运行只用于找到下一个失败的 section而不是充当内层重试循环——这是对每次改动都全量重跑这种低效调试习惯的显式纠正。核心深潜探测-改写重写与 RedirectRule 签名强制AGENTS.md 的最后一条规则信息密度最高也是最能体现设计文档即 API理念的条款。它针对的是 checker 中的一类危险操作——probe-then-mutate rewrite对已求解类型做一次结构化探测再依据探测结果去改写已求解的图或重盖restamp已盖章的 dispatch-plan 元数据。为什么这类操作需要先立法规design.md 的 Solver-Mutating Rewrites 章节解释了动机纯粹的 unification统一是什么程序能通过类型检查的最终权威。任何在普通 unification 之外改写已求解类型图、或重盖 dispatch-plan 元数据的代码要么是机制Mechanism改写不能改变哪些程序通过类型检查也不能改变无错程序的输出计划。例如对已报告错误的诊断恢复、写出与 unify 完全相同结果的描述符快速路径、孤儿变量回收策略Policy改写让原本会被纯 unification 拒绝的程序通过类型检查或改变无错程序的 checked-module 输出。每一条 policy 改写必须实现本文档design.md中已声明的一条规则、以该规则命名并有测试同时钉住其接受侧与拒绝侧。该章节点破了这类代码的根本危险一个 probe-then-mutate rewrite 在评审时与语言类型规则本身的一次修改无法区分——它能通过自己的复现测试而类型系统中没有任何东西会标记子型关系或分派策略变了。因此规则要求新重写必须先声明规则、以规则命名重写、测试钉住接受与拒绝两侧它让一个测试通过了不构成一条规则。源码层面的签名强制dangerousSetVarRedirect这套流程不是靠自觉而是靠编译期签名强制执行。src/types/store.zig 中定义了RedirectRule枚举目前恰好两个成员pub const RedirectRule enum { /// (i) Diagnostic recovery: the target var belongs to an expression /// whose error has already been reported, and the redirect only lets /// checking continue past it. diagnostic_recovery_reported_error, /// (ii) design.md Hosted Try Question Widening: ... hosted_try_question_widening, }; pub fn dangerousSetVarRedirect(self: *Self, comptime rule: RedirectRule, target_var: Var, redirect_to: Var) Allocator.Error!void { ... }关键设计有两点。其一rule是comptime 参数且必须位于两个变量之前——没有规则引用的调用无法通过编译新增调用点意味着必须引用一个现有成员或新增成员并同步在 design.md 中声明其引用的规则这让每一次图改写都是可 grep、可评审的。store.zig 中的测试 甚至用反射断言锁死了这个签名顺序第一个参数必须是Store.RedirectRule类型以及枚举的穷举性。其二实现本身带防御性断言把同一个求解类等价类重定向到自身会直接 panicself-redirect of equivalent vars重定向到指向同一根的透明 alias 也会被断言拦截避免静默产生自引用无限alias。从源码结构看src/check/Check.zig 中目前只有两个调用点与枚举成员一一对应Check.zig 第 22076 行.hosted_try_question_widening用于把?条件的根重定向到期望错误行的新Try变量Check.zig 第 39788 行.diagnostic_recovery_reported_error对已报告错误的表达式做恢复性重定向让检查可以越过它继续。已声明规则实例Hosted Try Question Wideningdesign.md 声明的 policy 规则之一是 Hosted Try Question Widening其完整语义是?解包Try条件并把错误行重抛到外层函数的返回行。当被调方错误行已闭合、而外层注解返回行是开放的rigid extension时普通 unification 会拒绝这对——这正是设计意图。唯一的声明例外是对被托管hosted函数的直接调用托管函数的边界类型是键控在其声明闭合行上的 ABI 契约托管被调方无法采纳调用方更宽的行若要求调用者手工重标托管错误会使托管函数在?下不可用。当?条件是托管函数的直接调用函数表达式静态解析到e_hosted_lambdadef分派调用与值携带函数均不合格且被调方行中每个可见错误都包含在期望行中时checker 在使用点把条件加宽condition 的根重定向到期望行的新Try即上文 Check.zig 的调用点托管被调方自身的声明类型保持不变。单态化降低会为加宽后的特化请求生成一个 Roc adapter在请求类型处调用声明类型边界并把错误重标进更宽的行——extern 边界本身永远按声明行发射。该规则的两侧都被测试钉住这正是 AGENTS.md 所要求的接受侧与拒绝侧接受侧test/fx-open/issue_9963_hosted_try_question_mark.roc——在开放行平台函数中直接对托管函数用?编译通过且 host 的Ok被观察为Ok对应 issue #9963 的回归场景加宽请求必须由 adapter 桥接而不是把 host ABI 特化到加宽布局拒绝侧test/fx-open/hosted_try_question_not_included.roc——外层注解省略了托管错误时类型检查必须失败反例回归非托管?进入开放注解行仍是类型错误回归测试在 src/check/test/type_checking_integration.zig对应 issue #9798。design.md 特别强调这条规则只决定哪些程序通过类型检查仅此而已保持 host ABI 完整的是 Monotype 降低阶段的 producer-side 检查与这条类型规则正交。机制类改写的完整清单Rewrite Inventorydesign.md 第 7040 行起 的 Rewrite Inventory 把 checking 阶段所有求解器改写逐一点名并分类。dangerousSetVarRedirect的调用点之外还包括markErroneousBranchWithExpected机制诊断恢复——表达式已有已报告错误其变量被重定向到与期望返回统一的新变量unifyWithFreshdangerousSetVarDesc机制写出 unify 根 flex 占位符与全新内容时恰好会产生的描述符的快速路径markErroneous机制已报告错误后的诊断恢复直接标记 checker 节点求解类保留类级联抑制retireCallLikeExprWithErroneousOperands/ 语句拥有的 iterator plan 恢复机制Erroneous Call Operand Retirement——操作数已拥有已报告错误时其 call-like 父节点在任何 dispatch 约束引入前进入两个表达式集合checkMatchExpr的分支模式目标机制错误 scrutinee 无法关联各分支模式改为与共享新变量统一无错程序永远不会到达该探测resetAnnotationNodesresetVarToUnbound机制scheme 已作为独立孤儿复制后回收注解节点变量finalizeTypeDeclarationValidity、occurs-check 污染、validateNominalDeclArgumentGrowth、finalizeFunctionEffectsAtBoundary策略类分别对应类型声明模板有效性、参数增长验证、泛化边界处的定向 effect 物化等已声明规则。评审清单把规则变成可执行的 diff 检查AGENTS.md 最后给出的评审清单把以上机制翻译成同一次变更中的检查项一个 diff 如果新增了RedirectRule成员、在清单外的位置调用setVarContent/dangerousSetVarDesc、或重盖 CIR plan 元数据则必须在同一变更中更新 design.md 的 Rewrite Inventory。换言之规则声明design.md、签名引用store.zig/Check.zig、清单登记Rewrite Inventory与双侧测试四个工件缺一不可——这正是设计先行 编译期强制 测试钉住三层防线在 Roc 编译器中的具体形态。对于任何想在类似强不变量代码库中工作的人人或 AIAGENTS.md 示范了一条可复制的路径把不可协商的工程原则写成无歧义的短句为每条原则标注唯一允许的例外再把最危险的那类操作提升到 API 签名层面强制执行最后用清单inventory 双侧测试让每次改动都可审查。【免费下载链接】rocA fast, friendly, functional language.项目地址: https://gitcode.com/GitHub_Trending/ro/roc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考