Slang 诊断目录测试包:为每个诊断代码生成 DIAGNOSTIC_TEST 的系统化方案
【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang
导读
Slang 编译器将用户可见的诊断(错误、警告、提示)组织成一份集中式的诊断目录:source/slang/slang-diagnostics.lua与source/compiler-core/下的*-diagnostic-defs.h头文件共同定义了全部 695 个诊断代码。本篇文章围绕docs/generated/tests/design/cross-cutting/diagnostics-catalog/_prompt.md这份生成契约,完整讲解 Slang 测试框架中"每个诊断代码对应一个最小复现测试"的诊断目录测试包(diagnostics-catalog bundle):从测试的元数据(//META)契约、catalog-digest摘要校验、DIAGNOSTIC_TEST:SIMPLE指令、文件命名规范,到硬性规则、README.md结构、质量检查清单与禁止事项,并深入仓库源码揭示诊断目录的生成管线与运行时渲染机制。读完本文,你将掌握如何为任意一个 Slang 诊断代码编写最小复现测试、如何通过regenerate.py工具链校验测试与目录快照的一致性,以及如何理解诊断严重级别、警告分组与错误渲染格式的底层实现。
一、什么是诊断目录测试包
diagnostics-catalog是 Slang 测试框架中一个非常特殊的测试包(bundle)。普通测试包以叙事文档为"源真相",从文档中的技术声明(claim)推导测试;而本包是 agentic-tests 计划 §15.3 要求的系统性负测试扫描(systematic negative-test sweep):每个 Slang 编译器能够发射的诊断代码,对应一个DIAGNOSTIC_TEST文件,用最小复现输入触发该诊断,并断言诊断流中出现目标代码(如E30019、W41024)。
其特殊之处在于:本包的"源真相"不是叙事文档,而是编译器源码中的结构化 LUA / 宏式诊断目录。实际产物位于 docs/generated/tests/design/cross-cutting/diagnostics-catalog/,目录中包含数百个形如30019-type-mismatch.slang、15302-include-recursion.slang、38000-no-entry-point-found.slang的测试文件,以及记录整体覆盖情况的README.md与生成契约_prompt.md。
二、为什么这个测试包在 doc-anchoring 契约下成立
框架的通用规则(见 docs/generated/tests/_meta/prompts/_common.md)是"测试必须锚定到文档声明,而非源码"。诊断目录测试包被视为一个例外,原因在于编译器源码中的诊断目录本身是结构化规范来源(structured spec source):
- source/slang/slang-diagnostics.lua 与
source/compiler-core/下的*-diagnostic-defs.h(如 slang-lexer-diagnostic-defs.h、slang-core-diagnostics.h)中,每一条目都是(code, severity, name, message)的声明式元组; - 这组元组定义了编译器向用户提供的诊断契约——"这个编译器会发射哪些诊断"的权威枚举;
- 它们不是任意实现源码,因此目录文件本身即可充当规范。
于是,本包中每个测试的doc_ref锚定到目录条目本身(LUA 中的err()调用行或DIAGNOSTIC()宏行),而不是叙事文档中的某个段落;同时叙事文档 docs/generated/design/cross-cutting/diagnostics.md 仍被引用用于总体框架(框架、严重级别模型、共享 ID 命名空间)。
需要强调:目录条目是"编译器能够发射"的诊断,不是"某个输入必然触发"的承诺。条目的名称只能提示触发构造,无法保证能够从最小输入到达该代码——这正是本包要系统性验证并记录的事实(见下文"Codes dropped")。
三、输入资料:目录快照、分桶文件与叙事文档
生成测试时可供使用的输入包括四类:
| 输入 | 路径 | 作用 |
|---|---|---|
| 完整目录快照 | docs/generated/tests/_meta/diagnostics-catalog/catalog.txt | 695 个代码的权威快照,含 severity / name / source / message 列 |
| 分桶文件 | docs/generated/tests/_meta/diagnostics-catalog/uncovered-bucket-<N>.txt(N=0..4) | 分配给本生成桶的未覆盖代码子集 |
| 叙事文档 | docs/generated/design/cross-cutting/diagnostics.md | 框架与既有锚点参考 |
| 编译器源码 | source/slang/slang-diagnostics.lua等 | 仅用于验证代码能从最小复现输入到达,禁止挖掘目录未规定的行为 |
3.1 目录快照的结构
快照文件以注释头说明统计信息:总代码 695,测试代理已覆盖 82,未覆盖 613。其列格式为code<TAB>covered<TAB>severity<TAB>name<TAB>source<TAB>message,例如:
1 UNCOVERED err cannot-open-file slang-diagnostics.lua cannot open file '~path' 10000 UNCOVERED error illegalCharacterHex slang-lexer-diagnostic-defs.h illegal character (0x$0)观察可知:代码按来源分属两类目录——slang-diagnostics.lua中的 LUA 条目(代码区间如 1–105、15000–15616、20000+ 等)与*-diagnostic-defs.h中的 C 宏条目(如词法器诊断 10000–10012),且旧式宏使用$0占位符、LUA 条目使用~name占位符,渲染时统一为E<5位代码>前缀。
3.2 分桶机制
操作者会并行派发五个代理,每个代理负责一个uncovered-bucket-<N>.txt。例如uncovered-bucket-0.txt的头部说明其包含 123 个代码(范围 1..20102),列格式与快照一致。硬性要求是:只处理自己桶内的代码,不得染指其他桶。
四、每个测试的契约://META 元数据块
每个.slang测试文件必须以//META块开头,逐字段填写(契约在 docs/generated/tests/_meta/prompts/_common.md 通用规则基础上扩展):
//META: generated=true //META: model=<your model id> //META: generated_at=<ISO 8601 UTC> //META: source_commit=<HEAD> //META: doc_ref=source/slang/slang-diagnostics.lua ← 或相应的 -defs.h //META: doc_section_digest=<output of `_meta/regenerate.py catalog-digest <code>`> //META: purpose=Fires diagnostic E<code> (<name>) — <message> //META: intent=negative //META: pipeline_stage=<lex | parse | check | lower | ir-pass | emit | link> //META: catalog_code=<code> //META: catalog_name=<name from catalog> //META: warning=Auto-generated. May drift from source. Do not edit by hand.字段要点:
doc_ref:指向声明该代码的目录文件——source/slang/slang-diagnostics.lua或对应的*-diagnostic-defs.h。这是"目录条目即规范"契约的直接体现。doc_section_digest:必须运行_meta/regenerate.py catalog-digest <code>获取,不要自己哈希(见第五节)。pipeline_stage:声明触发该诊断的流水线阶段,可选值为lex、parse、check、lower、ir-pass、emit、link。词法器/预处理器诊断落在lex/parse,语义检查诊断落在check,发射期诊断落在emit等。catalog_code与catalog_name:catalog_code是lint重新计算摘要的键,必须与目录中的拼写完全一致;同时它使 grep 变得微不足道:
grep "catalog_code=30019" docs/generated/tests # 找到代码 30019 的测试4.1 实际测试文件示例
仓库中已落地的 30019-type-mismatch.slang 完整展示了上述契约:
//META: generated=true //META: model=claude-opus-5[1m] //META: generated_at=2026-08-04T00:00:00+00:00 //META: source_commit=7e725f15572c6589ee6d738a8856fb3348f11617 //META: doc_ref=source/slang/slang-diagnostics.lua //META: doc_section_digest=72ffe778cb00717e39fbbd393cb340c63dd3002cfddf50b2a64064e805d41409 //META: purpose=Fires diagnostic E30019 (type-mismatch) - type mismatch in expression //META: intent=negative //META: pipeline_stage=check //META: catalog_code=30019 //META: catalog_name=type-mismatch //META: warning=Auto-generated. May drift from source. Do not edit by hand. // 只有初始化器可转换到声明类型时初始化才成功。三分量向量不能转换为 // 标量 int,CHECK 钉住类型不匹配错误。 //DIAGNOSTIC_TEST:SIMPLE(diag=CHECK,non-exhaustive): void main() { int x = float3(1,2,3); } //CHECK: E30019注意该文件在//META块后附有 2–4 行说明注释,解释"验证什么声明、如何验证"(测试的推理,而非引用文档原文)——这是通用规则 docs/generated/tests/_meta/prompts/_common.md 的强制要求。
五、catalog-digest:摘要计算与漂移检测
doc_section_digest是目录条目按code<TAB>severity<TAB>name<TAB>message四列拼接后的sha256,数据来自_meta/diagnostics-catalog/catalog.txt。必须运行 docs/generated/tests/_meta/regenerate.py 的子命令catalog-digest生成,而不是手工哈希——因为lint以完全相同的方式重算摘要,任何列或分隔符的手工偏差都会被报告为漂移(drift)。
python3 docs/generated/tests/_meta/regenerate.py catalog-digest 30019配套机制:
regenerate.py lint会重算每一条目摘要,对已漂移的条目发出警告;- 这意味着目录条目被改名或消息被修改时,恰好只有该代码对应的测试失效,实现精准的失效传播;
- 测试包
README.md明确说明:注释标记必须写作//CHECK:(斜杠后无空格),因为 runner 只识别这种形式;此前曾因// CHECK:带空格被当作普通注释解析,导致测试"通过"却没有断言任何内容——这是本包修复过的一个真实历史问题(见 README.md)。
六、测试体:最小复现与 DIAGNOSTIC_TEST 指令
测试体是触发诊断的最小输入:
//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK,non-exhaustive): // <minimum reproduction here> // CHECK: E<code>关键规则:
non-exhaustive的取舍:当编译器对同一输入发射多于一个诊断时使用non-exhaustive(这很常见——语义检查常伴随候选 note 等次级诊断),只匹配目标代码即可;如果所有发射的诊断都已标注,则不要添加non-exhaustive——runner 会拒绝多余的non-exhaustive;反之若漏标,穷举模式会失败。- 精确代码:CHECK 必须写精确代码(
E30019、W41024等),slang-test 按代码匹配,不能只写error:。 - CHECK 变体:同一文件中可写多条
// CHECK:(或用CHECK-DAG处理乱序匹配);诊断消息通常同时发射短消息 + 长消息 + 次级 note,无法确定措辞时应断言稳定的错误代码。 - 指令变体:
DIAGNOSTIC_TEST也可携带目标与入口参数,如实际目录中的36111-unexpected-capability.slang使用//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK,non-exhaustive):-target hlsl -stage compute -entry main -profile sm_6_0;39022-vk-index-without-vk-location.slang使用-target spirv -stage fragment -entry main。
七、文件命名约定
每个测试文件按<code>-<kebab-case-name>.slang命名,使整个目录可按代码与名称双向浏览:
| 代码 | 文件名示例 |
|---|---|
| 30019 | 30019-argument-type-mismatch.slang |
| 15302 | 15302-include-recursion.slang |
| 41024 | 41024-name-shadow-warning.slang |
| 38000 | 38000-no-entry-point-found.slang |
仓库中实际落地的文件严格遵循此约定,例如10000-illegal-character-hex.slang、15000-end-of-file-in-preprocessor-conditional.slang、41016-using-uninitialized-variable.slang、36111-unexpected-capability.slang等。
八、硬性规则
生成测试时必须遵守以下硬性规则:
- 限定桶内:不写桶文件之外的代码测试,其他代理并行处理其他桶。
- 不合成无法触发诊断的代码:尝试 2 次后仍找不到能触发某代码的最小输入,放弃该代码;在
README.md的## Codes dropped (could not reach from minimum input)中列出,附一行原因,例如:- "internal diagnostic with no user trigger"(内部诊断,无用户触发途径)
- "requires multi-file test"(需要多文件测试)
- "requires API surface not available to slangc CLI"(需要 slangc CLI 不可用的 API 表面)
- 不锁定 bug 行为:如果编译器实际发射的内容与目录消息不符,放弃该代码,不要编写断言错误输出的测试。
- 一码一测:一个测试文件只对应一个代码;多代码挤在一个文件会破坏目录的 grep 能力。
- 全部严重级别均在范围内:error、warning、note、info 都要覆盖;对它们统一使用
intent=negative(我们测试的是"编译器发射了正确的诊断"这一行为,而非功能)。
九、README.md 结构
第一个进入目录的代理创建README.md,同波次后续代理追加其桶的行。规范结构如下(实际仓库的 README.md 可作范例):
- Front-matter(YAML,遵循通用规则):
--- generated: true model: <your model identifier> generated_at: <ISO 8601 timestamp, UTC> source_commit: <git HEAD when you ran> watched_paths_digest: <sha256 from regenerate.py digest> source_doc: docs/generated/design/cross-cutting/diagnostics.md source_doc_digest: <sha256 of source_doc file at source_commit> warning: "Auto-generated. May drift from source. Do not edit by hand." ---其中watched_paths_digest与source_doc_digest用python3 docs/generated/tests/_meta/regenerate.py digest <bundle>计算。 2.## Intent:一段话说明这是目录扫描包、每个测试是什么、以及这是 agentic-tests 计划 §15.3 的交付物。 3.## Catalog coverage:覆盖统计表:
| Bucket | Codes in bucket | Tests added | Codes dropped |## Functional coverage:每行一个测试(每个诊断代码即一个 claim),按代码排序。列遵循通用 Coverage 规则:Claim | Intent | Anchor | Tests。本包中Claim单元格即测试的//META: purpose行(通常为Fires diagnostic E<code> (<name>) — <message>);Intent为negative;Anchor为source/slang/slang-diagnostics.lua或相应的*-diagnostic-defs.h;Tests为单个<code>-<name>.slang链接。## Codes dropped (could not reach from minimum input):每个被放弃代码一行,附放弃原因。
实际仓库README.md还额外维护了一套声明簿记(framing claims F1–F7 与逐条目 claim 家族 E1),并给出关键统计:快照共 695 个条目、331 个已有测试、67 个有 dropped 行、297 个既无测试也无 dropped 行(即覆盖缺口)、8 个测试引用了快照不含的代码——这展示了该枚举机制如何让"无复现的条目"从不可见变为可见。
十、质量检查清单
每个测试提交前必须逐项确认:
- 在
build/Release/bin/slang-test下报告100% of tests passed (1/1); - CHECK 模式钉住精确诊断代码(
E30019,而不是仅仅error:); - 仅当编译器发射目标诊断之外的诊断时使用
non-exhaustive; - 文件名为
<code>-<kebab-name>.slang; catalog_code与文件中的代码一致;- 测试体是最小的——没有多余函数、没有无关构造;
- 无法从单个
.slang文件触发的代码被放弃(不用 hack 合成)。
十一、禁止事项
- 不要
mark-fresh(由操作者处理); - 不要 commit;
- 不要修改其他代理的测试(各桶独立);
- 不要写桶外代码的测试;
- 不要包含编译器实现中的源码行内容(只允许目录条目本身)。
十二、仓库源码纵深:诊断目录从 LUA 到 C++ 的生成管线
理解测试包锚定的"目录条目"之前,有必要看清目录本身在仓库中如何产生。docs/generated/design/cross-cutting/diagnostics.md 给出了完整管线,这正是本测试包doc_ref所指对象的来源:
- source/slang/slang-diagnostics.lua(约 6400 行)通过调用
err、warning、standalone_note、internal、fatal等辅助函数声明编译器的全部诊断; - source/slang/slang-diagnostics-helpers.lua 收集这些调用、校验它们,并把每条消息解析为带类型的参数与 span;文件末尾调用
process_diagnostics返回处理后的列表,校验失败则抛出 Lua 错误; slang-rich-diagnostics.h.lua加载处理后的列表,提供模板所需的映射辅助函数(toPascalCase、getCppType、getSeverityEnum、getWarningLevelEnum);- 内嵌在
slang-rich-diagnostics.h/slang-rich-diagnostics.cpp中的 FIDDLE 模板为每条目生成 C++:namespace Slang::Diagnostics中的结构体(每个参数与位置一个成员)、插值消息并构建 span 的toGenericDiagnostic()方法,以及携带代码/严重级别/名称/警告组的DiagnosticInfo常量。
声明辅助函数的签名(以err(name, code, message, primary_span, ...)为例)固定了参数顺序。消息中的~name:Type是带类型插值参数,validate_diagnostic会拒绝非 kebab-case 名称及error/warning/note/internal/fatal之外的严重级别。运行时侧,source/compiler-core/slang-diagnostic-sink.cpp 中的DiagnosticSink负责渲染:getEffectiveMessageSeverity依次应用源级 warning 状态跟踪、按 ID 的严重级别覆盖、警告组门控与TreatWarningsAsErrors提升;渲染头格式为<severity>[E<5-digit id>]: <message>——因此warning 也以E前缀渲染(如warning[E41016]),测试断言时统一使用E<code>。
12.1 严重级别与警告组
source/compiler-core/slang-diagnostic-sink.h 声明了严重级别枚举Severity { Disable, Note, Warning, Error, Fatal, Internal }(与公共头 include/slang.h 的SLANG_SEVERITY_*通过static_assert对齐),以及警告组枚举WarningLevel { Default, All, Extra, Pedantic }。两组是独立而非嵌套的:sink 的启用位掩码默认只打开Extra位,因此Extra级警告开箱即发,All与Pedantic级警告需显式启用;对应 CLI 开关是-Wall/-Wextra/-Wpedantic。此外存在 9 个fatal级条目(如 E39901cannot-process-include、E40002cyclic-reference、E55206generic-specialization-recursion-cycle、E56003use-of-uninitialized-opaque-handle),fatal error与internal error渲染后会通过SLANG_ABORT_COMPILATION中止编译;而internal条目(internal-compiler-error、unimplemented、unexpected,均为代码 99999)报告的是编译器自身缺陷,输入到达它们属于编译器 bug,不应作为受支持的复现——这正是本测试包不为 99999 写测试的原因。
12.2 目录消息中的占位符与别名
LUA 目录条目支持~name:Type类型化插值参数与~decl.name成员访问(生成器据此生成decl->getName()等 C++ 代码),还支持variadic_span/variadic_note(每个元素渲染为独立记录)。另外,目录中存在一个诊断别名:overlappingBindings→parameterBindingsOverlap;别名仅在用户通过名称引用诊断时可见(-warnings-disable、-warnings-as-errors、-W<id>、-Wno-<id>经由findDiagnosticByName解析操作数),因此旧的-warnings-disable overlappingBindings用法继续有效。
十三、总结:这份契约解决了什么问题
诊断目录测试包把"诊断契约"从编译器内部的结构化数据变成了可执行、可 grep、可漂移检测的回归测试资产:
- 系统性:以目录快照(695 个代码)为真值表,逐条目枚举,未覆盖代码在
## Untested claims/## Codes dropped中显式记账,覆盖缺口不再隐形; - 可维护:
catalog-digest+lint把目录条目改名/改消息精确映射到对应测试失效,漂移立即可见; - 可检索:
<code>-<name>.slang命名 +catalog_code元数据让grep "catalog_code=30019"一步定位测试; - 诚实性:只写"最小输入确实触发的诊断",无法到达的代码记录原因而非强行合成,同时拒绝锁定编译器 bug 行为。
对于需要深入 Slang 编译器诊断系统的读者,建议按以下顺序阅读仓库:先看本包 README.md 的声明簿记与统计,再对照 catalog.txt 快照与任意一个 30019-type-mismatch.slang 测试,最后回到 source/slang/slang-diagnostics.lua 与 docs/generated/design/cross-cutting/diagnostics.md 理解条目声明与运行时渲染的完整闭环。
【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考