
Slang 解析器与 AST 构建文档的缺口治理实录12 个文档缺口的源码级修复【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang本文以 Slang 开源仓库中 docs/generated/design/_meta/gap-intake/pipeline/02-parse-ast.md.gap-intake.md 这份缺口收编gap-intake报告为主体完整还原 Slang 的生成式设计文档如何通过文档缺口治理工作流被逐条修补。读者既能学到 Slang 解析器与 AST 构建阶段两阶段解析、错误恢复、泛型歧义消解、修饰符解析、字面量诊断的真实行为细节也能掌握一套以测试为证据反向校订文档的可复用方法。一、背景生成式设计文档为何需要缺口治理Slang 仓库维护着一批由 LLM 生成、从编译器源码反向工程而来的设计文档位于docs/generated/design/例如本报告治理的对象 docs/generated/design/pipeline/02-parse-ast.md标题为 Parse and AST Construction完整讲解 token 流如何变成强类型 AST。这些文档由regenerate.py驱动、由 agent 依据watched_paths受监视的源码文件与 prompt 模板生成并在文件头标注Auto-generated. May drift from source. Do not edit by hand.自动生成可能与源码漂移请勿手改。由于文档是读源码写出来的它可能漏写、写窄、甚至写错。仓库为此专门设计了两条质量工作队列评审review队列由与生成方不同模型家族的 agent 逐篇审读文档产出结构化评审报告见 docs/generated/design/_meta/reviews/pipeline/02-parse-ast.md.review.md。文档缺口doc gap队列由位于 docs/generated/tests/ 的代理测试套件锚定到每篇文档通过真实运行编译器来检验文档。测试发现文档错误、不完整或含糊且不是编译器 bug 时就在对应 bundle 的 README## Doc gaps observed表中记录一行缺口。这类证据是评审无法产生的——评审者重读生成者读过的同一份源码两者可能达成一致却都搞错了编译器实际行为而测试是真正跑出来的。缺口按gap_id12 位十六进制由锚定文档、标题、缺口类型与描述文字派生登记在 doc-gap-state.json 账本中共五种状态fixed已修复、rejected-bogus误报、rejected-out-of-scope归属其他文档、deferred暂缓、escalated-to-finding实为编译器缺陷转交缺陷通道。收编intake阶段的工作流细节记录在 docs/generated/design/_meta/regenerate.md 的 The gap-intake stage 一节。二、本次治理总览12 个缺口全部修复本次报告针对pipeline/02-parse-ast.md一篇文档共收编12 个缺口全部fixed无一被驳回、延后或升级为编译器缺陷8 个来自design/pipeline/02-parse-astbundle产出了 6 个小节的修改### Two-stage parsing、### Error recovery、### The major node families、### ASTBuilder、## Generics ambiguity、## Modifier parsing、## Failure modes4 个来自coverage/parserbundle新增了一个### Angle-bracket annotations小节并对## Modifier parsing与## Failure modes做了补充页面体积从 21,328 字节增长到 28,630 字节上限 32,768 字节。值得注意的性质12 个缺口全部是缺失或写窄类投诉没有一个是漂移drift类且Suggested addition列中每条假设都能在source/slang/slang-parser.cpp或 AST 头文件中得到证实——观察与源码始终一致。修复后bundle README 的缺口表已清空no gaps remain open见 docs/generated/tests/design/pipeline/02-parse-ast/README.md 的## Doc gaps observed一节。下面按主题深入 12 个缺口的技术内容gap_id取自报告源码行号以报告记录的提交为准。三、AST 数据模型类型同一性与Val系列3.1 Hash-consing 带来两个拼写是一个签名7042738f5836ASTBuilder对类型做hash-consing结构相同的两个类型共享同一个Type*指针查重表为m_cachedNodes声明于 source/slang/slang-ast-builder.h 第 223 行。其用户可见后果非常反直觉核心模块在 source/slang/core.meta.slang 第 2594 行声明typedef vectorfloat,3 float3;由于两种拼写 intern 到同一个Type*以下两个函数是同一条签名int probe(float3 v); int probe(vectorfloat, 3 v); // 这是重声明redeclaration不是重载overload第二个probe触发重声明诊断bundle 测试 astbuilder-hash-consed-type-identity.slang 的 CHECK 行验证了 E30201 结果。理解这一点对调试为什么我的重载不生效至关重要。3.2Val系列的用户可见表面泛型值参数bb1cf0c2a51cVal基类位于 source/slang/slang-ast-base.h 第 380 行是编译期值家族。报告把这一抽象家族落地到两个具体用户语法上vectorfloat, 3中的3——VectorExpressionType::getElementCount()返回的IntVal*source/slang/slang-ast-type.h 第 751 行int a[4]中的4——ArrayExpressionType::getElementCount()返回的IntVal*同文件第 583 行。这些 intern 常量通过getOrCreateConstantIntVal创建source/slang/slang-ast-builder.h 第 438 行。配套测试见 val-generic-value-argument-element-count.slang。四、错误恢复唯一的双集合策略与保释集合缺口84e410a04bb8要求说明块block之外其他位置的恢复前/恢复后集合。源码核查结论是块策略是唯一同时使用 recover-before 与 recover-after 的地方。source/slang/slang-parser.cpp 第 628-633 行是唯一的TryRecover双集合调用其余所有恢复点走TryRecoverBefore第 621 行只传单个recover-before token、recover-after 集合为nullptr, 0。两个调用点分别是Parser::readTokenImpl第 635、659 行与AdvanceIfMatch第 805、829 行终止已匹配区域的保释集合是kMatchedTokenInfos第 783 行中的kMatchedToken_BailAtCurlyBraceOrEOF/kMatchedToken_BailAtEOF第 779-782 行在第 851-870 行消费。结论参数列表、初始化列表、声明位置没有自己的恢复集合它们继承所在匹配区域的闭 token。对应测试见 error-recovery-skips-balanced-group.slang 等 error-recovery 系列。五、泛型歧义消解FOLLOW 集合与基类判定5.1 泛型应用的 FOLLOW 集合0188169a6cb8tryParseGenericApp先在一份Parser副本上投机解析真实 token 读取器不移动只有投机解析无错且紧跟之后的 token 属于 FOLLOW 集时才回放。slang-parser.cpp第 3030-3053 行的 post-speculationswitch接受集合恰好为::、.、(、)、[、]、:、,、?、;、、!、、、文件结束EOF。报告特别指出该列表与 docs/generated/design/syntax-reference/grammar.md 第 619-629 行完全一致并在文档中交叉引用了 grammar.md 的消歧小节。对应测试 generic-app-speculation-leaks-no-diagnostic.slang 验证了投机解析不泄漏诊断。5.2 基类判定泛型 / 函数 / 类型都提交泛型读法91b203c9b706slang-parser.cpp第 2960-2996 行的规则比原文档写的类型检查为泛型类型声明更宽CheckTerm之后若declRef是GenericDecl或FunctionDeclBase或AggTypeDeclBase之一则BaseGenericKind设为Generic第 2969-2975 行注释给出了理由函数名或类型名出现在前绝不可能是比较OverloadedExpr只要任一候选属于上述三者就提交泛型读法第 2984-2995 行其余情况才是NonGeneric原样返回基类第 3013-3014 行。即fooT中foo解析为普通变量如foo bar的比较才强制走比较读法。这与 grammar.md 第 631-638 行一致。测试见 generic-app-base-not-generic-is-comparison.slang 与 generic-app-function-type-overload-base-commits.slang。六、修饰符与属性解析解析期/检查期的边界6.1 属性在检查期校验而非解析期7f923f6cd411[unroll]写在函数前而非循环前解析阶段不报任何错——不会出现 unexpected-token。唯一的诊断来自检查器Diagnostics::AttributeNotApplicableE31002attribute unroll is not valid here消息定义于 source/slang/slang-diagnostics.lua 第 2549-2554 行由 source/slang/slang-check-modifier.cpp 第 1245、1550 行触发注意该文件不在本页watched_paths内属源码在监视集合之外的典型案例。这正是解析/检查边界的最好注脚。测试 modifier-validated-at-check-not-parse.slang 用穷举模式断言恰好这一个诊断、无解析期报告。6.2struct [attr] Name的三档版本门控212fa7e5c537Parser::ParseStructslang-parser.cpp第 6370-6394 行仍会解析写在struct关键字之后的括号属性列表但按模块语言版本三档门控语言版本行为 2025静默接受legacy 2025Diagnostics::DeprecatedBracketAttributesPlacementW31204警告 2026Diagnostics::InvalidBracketAttributesPlacementE31205错误但仍继续解析该列表诊断码 31204/31205 定义于 source/slang/slang-diagnostics.lua 第 3001-3016 行。对应的三组测试齐全struct-bracket-attribute-placement-legacy-accepted.slang、struct-bracket-attribute-placement-2025-deprecated.slang、struct-bracket-attribute-placement-2026-rejected.slang。6.3 尖括号注解两处独立的整段丢弃a3a99789ebbb新增小节D3D effect 遗留语法 ... 注解被两处独立逻辑整段丢弃括号内任何内容都不进入 AST声明符级跳过parseDirectAbstractDeclarator第 2640-2681 行需要开关ParserOptions::enableEffectAnnotations由-enable-effect-annotations打开两个解析入口各一处。因在声明符后也可能是泛型实参列表它先做消歧——let和 X :前缀仍按泛型实参列表处理第 2651-2656 行否则在一份临时TokenReader上向前扫描若在下一个之前找到;才判定为注解并提交临时读取器。找不到;就把 token 留给泛型实参路径。语义级跳过_parseOptSemantics第 3966-3978 行无条件执行。在: SV_Position这类语义之后紧随的只可能是注解直接跳过到下一个无开关、无消歧。// 声明符级需要 -enable-effect-annotations Texture2D tex string uiName Tex; int uiOrder 3;; // 语义级不需要开关 struct VSOut { float4 pos : SV_Position string ann hello;; };括号内的;正是区分注解与泛型实参列表的关键也是不带开关时报错令人困惑的原因不传-enable-effect-annotations落入泛型实参读法第一个;报Diagnostics::UnexpectedTokenExpectedTokenTypeE20001unexpected ;, expected 而非任何提及注解被禁用的提示。测试见 effect-annotation-declarator-clause-discarded.slang、effect-annotation-opt-in-required.slang、effect-annotation-generic-list-carve-outs.slang 与 semantic-annotation-skipped-unconditionally.slang。6.4operator 名称仅限函数b56e139d0653规则由UnwrapDeclarator第 2752 行起在唯一必经卡口执行parseDirectAbstractDeclarator记录的isOperatorName标志若调用方未用allowOperatorName显式选择接受则报Diagnostics::OperatorNameOnNonFunctionE20020消息见 source/slang/slang-diagnostics.lua 第 944-949 行。唯一打开开关的地方是ParseDeclaratorDecl第 3584 行的函数分支第 3697 行——即参数列表或泛型已确认该声明符是函数之后。因此V operator(V a, V b) { ... } // 接受 int operator 3; // 拒绝E20020同一名称出现在变量声明符上因为检查在卡口处变量、参数、typedef、属性等情形无需各自测试即被拒绝。畸形operator 垃圾不会重复报错——它已先产生Diagnostics::InvalidOperator。测试对照 operator-name-on-non-function-rejected.slang 与 operator-name-on-function-accepted.slang。七、失败模式与字面量诊断7.1 语句位置的声明白名单bb51b5762505Parser::parseVarDeclrStatement第 7255-7286 行先走普通声明路径解析然后只保留以下形式变量VarDeclBase、DeclGroup、聚合类型AggTypeDecl覆盖 struct/class/enum/interface——测试在基类上做解析器不区分它们、typedef/typealiasTypeDefDecl含其子类TypeAliasDecl见 source/slang/slang-ast-decl.h 第 560 行、using。其余一切如namespace报Diagnostics::DeclNotAllowedE30102namespace is not allowed here.消息见 source/slang/slang-diagnostics.lua 第 1026-1030 行。这不等于整体接受实践中函数体内只有struct真正可用因为语义检查阶段还有第二层独立的嵌套校验validateDeclNestingsource/slang/slang-check-decl.cpp 第 304 行按自身父子规则表报 E31400。两层机制互相知情检查器在decl-nestingAlreadyDiagnosed已置位时跳过自己的诊断——而该标志正是解析器已报错时设置的。它也与容器嵌套检查isDeclAllowed第 5354 行其default分支对ScopeDecl父级返回true是两个不同机制。测试 decl-not-allowed-in-statement.slang 的 CHECK 行验证了 namespace is not allowed here.。7.2 浮点字面量诊断的优先级adb325a74b5aparseFloatingPointLiteralExpr第 8715 行请求getFloatingPointLiteralValue返回值加FloatingPointLiteralType分类并把分类映射为suffixType或诊断坏有效数字 →InvalidFloatingPointLiteralNumber未知后缀 →InvalidFloatingPointLiteralSuffix越界/精度丢失由outIsOutOfRange/outPrecisionLost标志转成FloatLiteralTooSmall、FloatLiteralUnrepresentable、FloatHexLiteralPrecisionLost。原文档写每个字面量至多一条实际更精确单个共享的diagnosed标志第 8734-8794 行由同一个switch的BadSignificand/BadSuffix分支置位守卫if (isOutOfRange !diagnosed)第 8766 行与if (precisionLost !diagnosed)第 8786 行。于是完整优先级是分类报告 → 越界对 → 精度报告。两个分类分支互斥唯一能同时触发两条的情形是分类失败且越界——1e400q不可识别后缀 溢出有效数字此时后缀报告胜出、越界报告被抑制。词法侧依据source/compiler-core/slang-lexer.cpp 第 1282-1285 行把未知后缀归为BadSuffix第 1360-1364 行仍由result_out_of_range置isOutOfRange第 1385 行已在越界时抑制precisionLost。测试见 float-literal-suffix-report-suppresses-range.slang 与 invalid-float-literal-suffix.slang。7.3 整数字面量的z后缀与宽度/无符号扫描70faf67147eeparseIntegerLiteralExpr第 8633 行把后缀拆成宽度部分l/L、ll/LL、z/Z与无符号部分u/U重复任一者置unknownSuffix并报InvalidIntegerLiteralSuffix第 8683-8688 行。随后_determineIntegerLiteralType第 8486 行把后缀对 数值大小映射到基类型指针宽度后缀z选intptr_t带u时选uintptr_t。非十进制字面量完全不看数值大小——0xFFz就是intptr_t第 8589-8592 行十进制z或ll且无u值 ≤INT64_MAX为有符号恰为INT64_MAX 1时类型为无符号但标记signedMinimumIntException允许外层一元-parsePrefixExpr第 9835-9845 行把类型改写回intptr_t/int64_t——这正是INT64_MIN能写出来的机制INT64_MAX 2及以上报Diagnostics::IntegerLiteralTooLargeW40004见 source/slang/slang-diagnostics.lua 第 4151-4157 行并保持无符号。测试覆盖 integer-literal-z-suffix-pointer-width.slang、integer-literal-int64-max-plus-one-boundary.slang、integer-literal-too-large-warns-and-stays-unsigned.slang 与 invalid-integer-literal-suffix.slang。八、治理过程的三点方法论收获除了 12 个具体修复报告还留下了三条可复用的经验假设不是权威Suggested addition is a hypothesis, not authority缺口描述出自观察到行为的 agent而非读过代码的 agent。落笔进文档前必须在受监视源码路径中确认行为否则可能把测试观察到的编译器 bug 写成文档化行为。drift-from-source是最含混的种类文档是反向工程产物、可能固化了 bug因此文档与观察行为矛盾可能是文档错也可能是编译器错。是编译器错时应走escalated-to-finding缺陷通道不要为迁就 bug 而改文档。本次 12 个缺口无一升级说明全部是文档侧问题。受监视路径watched_paths存在盲区两处修复依赖本页监视集合之外的源码——E31002 由slang-check-modifier.cpp发出浮点标志共现逻辑在slang-lexer.cpp本页只通过slang-lexer.h间接引用。报告建议把source/compiler-core/slang-lexer.cpp加入watched_paths使字面量解码材料自洽。此外报告记录了源码漂移现象slang-parser.cpp相对记录提交约漂移8行TryRecover在 483 而非 475UnwrapDeclarator在 2752 而非 2744parseFloatingPointLiteralExpr在 8715 而非 8696。治理时重推导并修正了改写句内的引用但对未触碰小节约 10 处引用保持原样以避免整文件摘要失效——它们需要后续再生成轮次处理。事实上目标文档在治理后已再次再生成当前 docs/generated/design/pipeline/02-parse-ast.md 的行号如TryRecover第 491 行、UnwrapDeclarator第 2760 行、parseFloatingPointLiteralExpr第 8740 行即来自更新后的提交48c746dc1eda1c6e2aa98c17bbdb7a645c24a048。九、如何复现与继续深入治理工作流完全由docs/generated/design/_meta/regenerate.py驱动详见 docs/generated/design/_meta/regenerate.md所有命令在仓库根目录运行# 查看当前各文档的缺口数 python3 docs/generated/design/_meta/regenerate.py gap-status --only-open # 导出某文档的缺口明细 python3 docs/generated/tests/_meta/regenerate.py doc-gaps \ --source-doc docs/generated/design/pipeline/02-parse-ast.md # 收编缺口报告lint 通过后才允许写账本 python3 docs/generated/design/_meta/regenerate.py lint pipeline/02-parse-ast.md python3 docs/generated/design/_meta/regenerate.py mark-gap-intake pipeline/02-parse-ast.md要亲手验证 12 个修复对应的编译行为可直接阅读并运行以下测试解析/AST 主 bundledocs/generated/tests/design/pipeline/02-parse-ast/其 README 的## Doc gaps observed一节说明了每个已退役缺口与当前文档的对应关系覆盖率 bundledocs/generated/tests/coverage/parser/invalid-float-literal-suffix.slang、invalid-integer-literal-suffix.slang、struct-bracket-attribute-placement-2026.slang、decl-not-allowed-in-statement.slang主实现source/slang/slang-parser.cpp、source/slang/slang-ast-builder.h、source/compiler-core/slang-lexer.cpp诊断消息表source/slang/slang-diagnostics.lua。结语这份 gap-intake 报告的价值远超改了几处文档它把 Slang 解析器 12 个最容易写错、写漏的行为——hash-consing 的类型同一性、错误恢复的集合设计、泛型消歧的判定规则、解析/检查边界、三类字面量诊断的精确优先级——用测试证据逐条钉死。对读者而言它是理解pipeline/02-parse-ast.md的最佳勘误表 测试指南对从事编译器文档工作流的团队而言它示范了一条测试反向校订文档、文档再驱动测试更新的闭环修复文档会改变 bundle 的source_doc_digest触发 bundle 重生成并以改进后的文本重测缺口行消失、gap-status把该决策记为retired。【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考