Slang 语句 AST 参考指南:Stmt 类层次结构、解析流程与诊断错误码全解析
2026/9/17 15:26:59 网站建设 项目流程

Slang 语句 AST 参考指南:Stmt 类层次结构、解析流程与诊断错误码全解析

【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang

导读

本文是 Slang 编译器源码中语句(Statement)AST 体系的权威参考指南,面向需要阅读或修改解析器(parser)、语义检查器(checker)或 IR 降级(IR lowering)代码的贡献者。文章完整梳理了Stmt的所有具体子类、Parser::ParseStatement的调度与两阶段解析策略、switch/for/__target_switch/defer等语句的底层解析行为,以及与之配套的E20001E20004E20101E29110E36105E36109六个用户可见诊断错误码。读完本文,你将能够在源码中快速定位任意语句节点的定义、解析入口与相关测试,并理解 HLSL 输入下for循环变量泄漏等微妙行为。

文档来源与定位

本文主体对应仓库中的自动生成文档 docs/generated/design/ast-reference/statements.md,并依据 docs/generated/design/_meta/gap-intake/ast-reference/statements.md.gap-intake.md 中记录的 8 项缺口(gap)处理结果,将 7 项已修复的文档缺口(诊断码、defer可观察语义、switch单作用域结论等)与 1 项暂缓缺口(HLSL 输入语言选择机制)一并整合进正文,同时结合 source/slang/slang-parser.cpp、source/slang/slang-ast-stmt.h 等源码与docs/generated/tests/design/ast-reference/statements/下的 bundle 测试做交叉印证。

语句 AST 的整体框架

类的声明位置

语句(Statement)类全部声明在 source/slang/slang-ast-stmt.h 中;而所有语句的抽象根Stmt并不在该文件里——它是 source/slang/slang-ast-base.h 中的抽象基类之一(与ExprDecl等并列)。文档对Stmt本身的说明位于 base.md。

解析入口

语句的解析入口是Parser::ParseStatement(slang-parser.cpp),它是一个基于关键字前瞻(keyword-lookahead)的调度器,根据下一个 token 选择对应的Parse*Statement/parse*Stmt辅助函数;Parser::parseBlockStatement(slang-parser.cpp)则负责解析{ ... }代码块。

两者都接收一个AllowCaseDefaultStatements参数,这正是case/default写在switch体外时能先被解析成节点、再被诊断(而非直接报"unexpected token")的原因,参见 keywords-and-builtins.md。

两阶段解析策略

函数体并不会在解析声明的同时被解析:解析器运行在ParsingStage::DeclParsingStage::Body两个阶段之一(ParsingStage枚举位于 slang-parser.cpp)。parseOptBody(slang-parser.cpp)在遇到{限定的函数体时,不递归进入parseBlockStatement,而是将其记录为一个UnparsedStmtparseUnparsedStmt(slang-parser.cpp)稍后以stage = ParsingStage::Body重新进入,把这些 token 真正变成BlockStmt。整体策略参见 02-parse-ast.md。

家族层次结构(Family hierarchy)

语句节点沿两个轴划分:其一,是否是一个ScopeStmt分组(结构基类,只有部分子类真正持有ScopeDecl);其二,是否可以作为break(循环与switch)或continue(仅循环)的目标。此外还有一组ChildStmt,保存对某个外围BreakableStmt的引用。

BreakableStmt携带一个UniqueStmtIDNode* uniqueIDChildStmt通过targetOuterStmtID引用它。UniqueStmtIDNode在头文件中被声明为Decl子类,仅仅是为了序列化的便利——它并不是真正的声明;虽然不在Stmt层次内、也不被当作语句解析,但由于头文件用FIDDLE()宏声明它,它仍会出现在下文## Nodes表中作为辅助节点。

节点总表(Nodes)

下表是全部语句节点的速查表,字段与文法一应俱全:

ClassParentKey fieldsGrammarSummary
SeqStmtStmtstmts: List<Stmt*>(none)扁平的语句序列,用于在只允许单条语句的位置(如{ ... }块的若干语句)填充多条语句。
LabelStmtStmtlabel: Token,innerStmt: Stmt*labeled stmtlabel:后跟一条语句。
BlockStmtScopeStmtbody: Stmt*,closingSourceLoc: SourceLocblock{ ... };为块级声明引入一个ScopeDecl
UnparsedStmtStmttokens: List<Token>,sourceLanguage: SourceLanguage,currentScope: Scope*,outerScope: Scope*(none)ParsingStage::Decl阶段以原始 token 捕获的{ ... }函数体,按需重新解析。
EmptyStmtStmt(无附加状态)empty stmt;
DiscardStmtStmt(无附加状态)discarddiscard(片元着色器像素丢弃)。
DeclStmtStmtdecl: DeclBase*decl stmt在语句位置使用声明(例如函数体内的int x = 1;)。
IfStmtStmtpredicate: Expr*,positiveStatement: Stmt*,negativeStatement: Stmt*,afterLoc: SourceLocifif (...) ... else ...;也是if (let x = ...)脱糖后的节点。
SwitchStmtBreakableStmtcondition: Expr*,body: Stmt*switchswitch (cond) { ... }
TargetCaseStmtChildStmtcapability: int32_t,capabilityToken: Token,body: Stmt*__target_switch__target_switch内的case <capability>:(或default:);capability保存一个CapabilityName代码。
TargetSwitchStmtBreakableStmttargetCases: List<TargetCaseStmt*>__target_switch按 capability 集合静态分发。
StageSwitchStmtTargetSwitchStmt(继承)__stage_switch按管线阶段静态分发。
IntrinsicAsmStmtStmtasmText: String,args: List<Expr*>__intrinsic_asm内联 intrinsic 汇编语句,供核心模块 intrinsic 使用。
CaseStmtCaseStmtBaseexpr: Expr*,exprVal: Val*caseswitch内的case <expr>:
DefaultStmtCaseStmtBase(无附加状态)defaultswitch内的default:
GpuForeachStmtScopeStmtdevice: Expr*,gridDims: Expr*,dispatchThreadID: VarDecl*,kernelCall: Expr*__GPU_FOREACH宿主机侧对网格的计算式 foreach,写法为__GPU_FOREACH(device, gridDims, LAMBDA(...) { ... })
ForStmtLoopStmtinitialStatement: Stmt*,sideEffectExpression: Expr*,predicateExpression: Expr*,statement: Stmt*forfor (init; cond; step) body;循环变量限定在函数体内作用域。
UnscopedForStmtForStmt(继承)for语法与ForStmt相同,仅对 HLSL 输入产生——此时循环变量泄漏到外围作用域。
WhileStmtLoopStmtpredicate: Expr*,statement: Stmt*whilewhile (cond) body
DoWhileStmtLoopStmtstatement: Stmt*,predicate: Expr*do-whiledo body while (cond);
CompileTimeForStmtScopeStmtvarDecl: VarDecl*,rangeBeginExpr: Expr*,rangeEndExpr: Expr*,body: Stmt*;外加检查期填充的rangeBeginVal/rangeEndValIntVal*compile-time for编译期展开的基于范围的循环;不产生运行时循环。
BreakStmtJumpStmttargetLabel: Tokenbreakbreak(可带目标标签)。
ContinueStmtJumpStmt(继承)continuecontinue;与break不同,不带标签。
ReturnStmtStmtexpression: Expr*returnreturn(可带表达式)。
DeferStmtStmtstatement: Stmt*deferdefer Sstatement保存被延迟的语句。
ThrowStmtStmtexpression: Expr*throw可错误函数(errorable function)中的throw eParseThrowStatement本身不消费尾随;)。
CatchStmtStmterrorVar: ParamDecl*,tryBody: Stmt*,handleBody: Stmt*do-catchdo { ... } catch (e) { ... }tryBody是被保护体,handleBody是处理器;errorVar == null表示 catch-all。
ExpressionStmtStmtexpression: Expr*expression stmt以副作用为目的的表达式(f();a = b;)。
RequireCapabilityStmtStmtrequiredCaps: List<Token>__requireCapability__requireCapability(...);语句级 capability 要求,作用域限定于所在函数。
UniqueStmtIDNodeDecl(无解析状态)(none)综合产生的身份辅助节点,为语句提供稳定的唯一 id;用于序列化与控制流追踪,而非被当作语句解析。

重点节点详解(Notable nodes)

BlockStmt 与 SeqStmt:作用域从何而来

ScopeStmt是所有可能拥有词法作用域的控制流分组的结构基类。它唯一的字段scopeDecl: ScopeDecl*是块内局部声明所挂载的容器声明,名字查找从语句出发向外遍历时正是通过它找到这些声明。只有四个解析例程会填充它:

  • Parser::parseBlockStatementBlockStmt填充(slang-parser.cpp);
  • Parser::ParseForStatement为有作用域的for填充(slang-parser.cpp);
  • parseGpuForeachStmt(slang-parser.cpp);
  • parseCompileTimeForStmt(slang-parser.cpp)。

SwitchStmtTargetSwitchStmt通过BreakableStmt继承ScopeStmt,但scopeDecl保持为 null——switch的词法作用域取自解析器为其函数体构建的BlockStmtWhileStmt/DoWhileStmt则不引入自己的作用域。

由此得出一个用户可见的重要语义:整个switch函数体是一个单一作用域。在case 1:下声明的变量在case 2:default:下依然在作用域内,并且会在函数体其余部分遮蔽函数体外同名变量。这是 gap-intake 修复7050023ee030的结论,其证据链为:slang-parser.cpp 用parseBlockStatement(AllowCaseDefaultStatements::Allow)解析整个switch函数体,ParseCaseStmt/ParseDefaultStmt:6592:6602)只读取case <expr> :/default :,因此标签下的声明是同一个BlockStmt作用域内的兄弟节点。bundle 测试 switchstmt-body-block-scope.slang 固化了这一可观察行为:default:中的读取解析到case 1:的声明而非外层变量。

提示:SwitchStmt::body永远是BlockStmt——ParseSwitchStmt(slang-parser.cpp)用parseBlockStatement解析{ ... },该块的body通常是混合了CaseStmtDefaultStmt与其他语句的SeqStmt。case 标签并不是其下语句的父节点:ParseCaseStmtParseDefaultStmt(slang-parser.cpp)只读取case <expr> :/default :,后续语句作为兄弟留在序列中,所以每个CaseStmt/DefaultStmt只是序列里的一个标记。SwitchStmt内的BreakStmt通过BreakableStmt::uniqueID匹配。

BlockStmt是最简单的ScopeStmt——一个body单条Stmt{ ... }块。SeqStmt则相反,是一个没有自身作用域的扁平容器,存在的意义就是让多条语句能填入只放得下一条Stmt的槽位。Parser::parseBlockStatement正是这样使用它:块中第一条语句直接成为body,第二条出现时创建一个SeqStmt并把两条都移进去;空块获得EmptyStmt作为body而非 null。另外两处会构建SeqStmtParser::parseIfLetStatement(slang-parser.cpp)与parseTargetSwitchStmtImpl(slang-parser.cpp)中每个 case 的函数体循环。

UnparsedStmt:函数体的第一形态

UnparsedStmt并非什么奇特构造——它就是函数体在解析第一阶段最普通的表现形式。当解析器在期望函数体的位置遇到{时,parseOptBody(slang-parser.cpp)不递归进入parseBlockStatement,而是把直到匹配}的 token 复制进UnparsedStmt::tokens(以合成的文件结束 token 结尾),并记录当时生效的两个作用域currentScopeouterScope。像 slang-parser.cpp 处的函数声明解析器这样的调用方,会把结果当作该 decl 的body

被捕获的作用域正是延迟(deferral)安全性的关键:当函数体最终被需要时,parseUnparsedStmt(slang-parser.cpp)在一个以stage = ParsingStage::Body配置的全新Parser上重新安装这两个作用域并调用parseBlockStatement,于是函数体在与其原始位置完全相同的查找环境中被解析。结果是普通的BlockStmtUnparsedStmt不会存活到后续阶段。触发重新解析的调用点位于 checker 中(在本文 watched paths 之外)。

IfStmt:谓词与两分支

IfStmt持有谓词与两个分支的原始Stmt*。没有elseif,其negativeStatement槽位为 null。afterLoc字段是解析器消费完两个分支后正在查看的位置(Parser::parseIfStatement末尾的tokenReader.peekLoc(),slang-parser.cpp),即整个if之后的第一个 token。

if let没有专属节点。当ParseStatementif后两个 token 处看到let时,调用Parser::parseIfLetStatement,在解析期把该形式脱糖成一个SeqStmt:其中包含一个DeclStmt,绑定合成的$OptVar,后随一个普通IfStmt;另一个合成的LetDecl(绑定到$OptVar.value)被前置到正分支。因此追踪if let的读者应在 AST 中找SeqStmt,而不是某个独立的类。

循环家族(Loop family)

ForStmtWhileStmtDoWhileStmt都派生自LoopStmt,后者派生自BreakableStmt,再往上派生自ScopeStmt——因此每个循环都可break,但只有for拥有自己的ScopeDecl

ForStmt::statement是循环体;initialStatement按语句(而非表达式)解析,以便DeclStmt能引入循环变量;Parser::ParseForStatement(slang-parser.cpp)会诊断该位置任何既非DeclStmt也非ExpressionStmt的内容,同时保留已构建的循环节点以便解析恢复。写在该位置的块——for ({ int i = 0; } n < 3; n = n + 1)——正是触发它的形态,用户看到的是E20001,"unexpected statement, expected expression",报告在该违规语句上。这是 gap-intake 修复81ec1b4a473c的结论:证据在 slang-parser.cpp,ParseStatement解析初始语句,任何非DeclStmt/ExpressionStmt的内容触发Diagnostics::UnexpectedTokenExpectedTokenType,其actualToken = "statement"expectedToken = "expression";对应错误码定义于 slang-diagnostics.lua(id 20001,"unexpected ~actualToken, expected ~expectedToken")。bundle 测试 forstmt-init-non-expression-rejected.slang 正是用for ({ int i = 0; } n < 3; n = n + 1)固化 "unexpected statement, expected expression" 输出。

UnscopedForStmt是 HLSL 兼容形态:同一函数在getSourceLanguage()SourceLanguage::HLSL时创建它而非ForStmt。这里有一个容易踩坑的重要事实:输入语言由输入文件的扩展名决定,不能用-lang选择。在OptionsParser::addInputPath(slang-options.cpp)中,扩展名测试先于语言值生效:以.slang结尾的路径直接进入addInputSlangPath,因此命令行上的-lang hlsl(只设置CompilerOptionName::Language)永远不会到达它;语言值只用于扩展名未决定的情况,否则findSourceLanguageFromPath也会从扩展名推导。所以同一份源码编译为foo.hlsl时循环变量泄漏,编译为foo.slang时则不会,且给.slang编译加-lang hlsl也不会改变这一点。在 HLSL 情形下解析器会填充scopeDecl但从不压栈该作用域,于是循环变量泄漏进外围作用域。

关于"用户如何选择 HLSL 输入"这一缺口(gap9f664f7cf462)被暂缓(deferred):watched_paths中没有决定输入源语言的内容——slang-parser.cpp 只读取getSourceLanguage(),而Parser是从其调用方(slang-parser.h,同样未受监视)收到该值的。真正回答该问题的扩展名表位于 slang-options.cpp({".hlsl", SLANG_SOURCE_LANGUAGE_HLSL, SLANG_STAGE_NONE}),不在watched_paths内,且 bundle 中没有编译 HLSL 输入的测试。若把watched_paths扩展到source/slang/slang-options.cpp即可闭环此缺口。

do语句由Parser::ParseDoStatement(slang-parser.cpp)解析,它先解析函数体、再决定构建什么:后续有while则产生DoWhileStmt,后续有catch则产生CatchStmt,其余情况报错。

ReturnStmt

return既是控制流终结符,也是函数结果的载体:ReturnStmt::expression是被返回的Expr*,裸return(void 函数中)则为 null。checker 会把该表达式与外围函数声明的返回类型比对。

CompileTimeForStmt:编译期展开的循环

基于范围的循环,其边界必须是编译期常量(rangeBeginValrangeEndVal是由检查阶段填充的IntVal*)。解析器从$for (name in Range(...))语法产生它:parseCompileTimeStmt(slang-parser.cpp)消费$parseCompileTimeForStmt(slang-parser.cpp)消费其余部分。

Range在该位置是必需的、字面意义上的关键字:解析器用ReadToken("Range")读取它(slang-parser.cpp),其失败路径是Unexpected(parser, expected)(slang-parser.cpp)→Diagnostics::UnexpectedTokenExpectedTokenName,即E20004,"unexpected identifier, expected 'Range'"(定义于 slang-diagnostics.lua,id 20004,消息 "unexpected ~actualToken, expected '~expectedTokenName'")。这是 gap-intake 修复acf54503f940的结论。

两种拼写及其迭代值(均半开区间):

  • 单参形式$for (i in Range(4))rangeBeginExpr保持 null,迭代 0、1、2、3(共 4 次)。bundle 测试 compiletimefor-unrolls.slang 用Range(4)求和 0+1+2+3 固化该行为。
  • 双参形式$for (i in Range(2, 5)):逗号把第一个参数移入rangeBeginExpr,迭代 2、3、4。测试 compiletimefor-two-argument-range.slang 固化之。
  • 其他标识符出现在该位置则报E20004,由测试 compiletimefor-range-keyword-required.slang("unexpected identifier, expected 'Range'")固化。

循环变量是解析器创建的VarDecl,被加入语句自身的ScopeDecl,因此函数体可以引用它。

TargetSwitchStmt、StageSwitchStmt、TargetCaseStmt

TargetSwitchStmt是编译期按 capability 匹配的静态分发;每个TargetCaseStmt携带一个 capability 代码和一个函数体。StageSwitchStmt形状相同但按管线阶段(pipeline stage)分发,两者共享同一解析器:parseTargetSwitchStmtparseStageSwitchStmt(slang-parser.cpp)仅在调用parseTargetSwitchStmtImpl前分配哪个节点上不同。

该共享实现有两个从表格中看不到的细节:

  1. capability 名字在解析期就被解析findCapabilityName把 token 映射为CapabilityName,存入TargetCaseStmt::capabilityint32_t);未识别的名字立即被诊断为Diagnostics::UnknownTargetName——E29110,"unknown target name ' '"(slang-diagnostics.lua,id 29110,消息 "unknown target name '~name'")。该 case 仍会被记录,但capability留在CapabilityName::Invalid,于是语义检查在同一标签上再报第二个错误:E36109,"'Invalid' cannot be used as a target_switch case."(slang-diagnostics.lua,id 36109,消息 "'~capability' cannot be used as a target_switch case.")。这是 gap-intake 修复8d82d193316d的结论:证据在 slang-parser.cpp(诊断UnknownTargetName后仍存储capability = (int32_t)CapabilityName::Invalid),而该Invalid存储值正是 checker 在 slang-check-stmt.cpp(在本文 watched paths 之外)添加Diagnostics::InvalidTargetSwitchCase的原因。bundle 测试 targetcase-unknown-name-rejected.slang 在同一标签上固化了两条消息。default:标签被记录为capabilityToken内容已清空的TargetCaseStmt
  2. 堆叠标签case a: case b: ...每个标签产生一个TargetCaseStmt,全部指向同一个函数体Stmt,因此遍历targetCases的消费者会多次看到该函数体。

DeferStmt:何时真正执行由 IR 降级决定

Parser::ParseDeferStatement(slang-parser.cpp)读取defer关键字后跟一条Stmt——因此延迟块不需要尾随分号——并存入DeferStmt::statement。节点不携带其他状态;延迟语句真正运行的时机由 IR 降级(IR lowering)决定,而非 AST。

可观察的规则(gap-intake 修复20449a33ee45的结论)是:延迟语句在封闭作用域退出时运行——即包含该defer的块结束时,也包括函数通过return提前离开该作用域的情形。注意:breakthrow虽出现在建议措辞中,但没有任何 bundle 测试固化它们,因此文档有意没有写入。相关 bundle 测试:

  • deferstmt-scope-exit-order.slang:固化 body → deferred → after-block 的顺序;
  • deferstmt-at-function-entry.slang 与 deferstmt-runs-on-early-return.slang:固化提前return时延迟打印落在被调用方最后一条打印与调用方下一条打印之间。

降级的完整说明参见 04-ast-to-ir.md。

ThrowStmt 与 CatchStmt:可错误函数模型的两半

CatchStmt同时持有被保护体(tryBody)与处理器(handleBody);错误参数是ParamDecl,使 catch 处理器拥有完整类型的局部变量。errorVar为 null 表示不绑定错误值的 catch-all。

注意语句级拼写是do { ... } catch (e) { ... }——没有try语句;语句位置的try会被路由到Parser::ParseExpressionStatement,因为try是表达式关键字。一个do后面可以跟多个catch子句,Parser::ParseDoCatchStatement(slang-parser.cpp)会将其链接起来:当下一个 token 是catch时循环继续,把刚构建的CatchStmt作为下一个的tryBody,于是n个 catch 子句变成n层嵌套的CatchStmt,最外层被返回。

DeclStmt、ExpressionStmt 与 EmptyStmt:样板包装器

这些样板包装器的存在是为了让语句文法保持统一。DeclStmt允许任意DeclBase出现在语句位置(典型用途是局部变量声明);ExpressionStmt允许任意Expr以副作用为目的使用。checker 分别校验每种类型。EmptyStmt是解析器为裸;产出的节点,因此空的语句槽位(例如循环体只有;)仍有具体的Stmt

前两者的选择需要回溯(backtracking):对以标识符或::开头的语句,ParseStatement先试探性解析一个类型,若随后是标识符则回卷 token 读取器,通过Parser::parseVarDeclrStatement(slang-parser.cpp)把整条重新解析为声明;否则回卷并调用ParseExpressionStatement

if之后紧跟;是可疑而非非法:该情形仍产出EmptyStmt,但同时报告Diagnostics::UnintendedEmptyStatement——警告E20101,"potentially unintended empty statement at this location; use {} instead."。这是 gap-intake 修复df583f6f8952的结论:证据在 slang-parser.cpp——只有当父节点是IfStmt时才抛出该诊断,随后仍构建EmptyStmt;条目以warning(...)声明(slang-diagnostics.lua,id 20101),bundle 测试 emptystmt-after-if-diagnosed.slang 固化了该打印文本。因为是警告而非错误,程序仍会继续编译。

LabelStmt、BreakStmt、ContinueStmt 与 DiscardStmt

Slang 支持带标签语句与带标签 break。LabelStmt把标签 token 附着到内部语句上,ParseStatement看到标识符紧跟:时选择Parser::parseLabelStatement(slang-parser.cpp);BreakStmt::targetLabel可选地命名要跳出的封闭带标签循环或switchtargetLabelBreakableStmt::uniqueID的解析由语义检查完成。ContinueStmt是重启最近封闭循环的兄弟JumpStmt,且不接受标签。DiscardStmt是仅限片元着色器的控制流语句(discard),丢弃当前像素,不携带操作数。

RequireCapabilityStmt:语句级 capability 断言

断言外围函数要求列出的 capability 原子。Parser::ParseRequireCapabilityStatement(slang-parser.cpp)识别__requireCapability关键字,并在读取每个名字时通过findCapabilityName校验;无法解析的名字被丢弃并诊断为Diagnostics::UnknownCapability——E36105,"unknown capability name ' '."(slang-diagnostics.lua,id 36105,消息 "unknown capability name '~capability'.")。

函数体内可接受的拼写是__requireCapability(hlsl);。gap-intake 修复98d151850f5e给出的细节:名字在while (true)循环中读取,遇到逗号时AdvanceIf(this, TokenType::Comma)(slang-parser.cpp)继续,因此一条语句可以列出多个原子;语句以ReadToken(TokenType::RParent)/ReadToken(TokenType::Semicolon)(slang-parser.cpp)收尾,即)之后必须跟;。只有被接受的 token 才以原始Token存入requiredCaps,交由 targets.md 所述 capability 系统解释。相关测试: requirecapability-in-function.slang 在函数体内编译__requireCapability(hlsl);;requirecapability-unknown-name-rejected.slang 固化该消息。

诊断错误码速查

本文(连同 gap-intake 报告)共涉及六个用户可见的诊断码,全部定义于 source/slang/slang-diagnostics.lua,可在 slang-diagnostics-helpers.lua 的 E20001–E20012 注释范围内对照:

错误码类别消息触发位置
E20001errorunexpected statement, expected expressionfor头部的初始位置出现非声明/非表达式语句
E20004errorunexpected identifier, expected 'Range'$forRange位置出现其他标识符
E20101warningpotentially unintended empty statement at this location; use {} instead.if后紧跟裸;
E29110errorunknown target name ' '__target_switch中出现未识别的 capability 名
E36105errorunknown capability name ' '.__requireCapability中出现无法解析的名字
E36109error' ' cannot be used as a target_switch case.同一标签在E29110之后由 checker 追加

需要说明的边界:gap-intake 报告指出,诊断 id、严重级别与消息文本都位于 source/slang/slang-diagnostics.lua,但它不在本文页面的watched_paths中,因此这五个新增代码(E20001E20004E20101E29110E36105E36109)目前是未被追踪的;将其加入watched_paths可以闭环验证,04-ast-to-ir.md 已在相同条件下引用这些代码。此外,本文指向的 04-ast-to-ir.md 完全未提及DeferStmt,该指针目前是悬空的,值得在该页开一个缺口。

扩展阅读

  • base.md —StmtModifiableSyntaxNode基类。
  • declarations.md —DeclStmt包装任意DeclBase
  • expressions.md — 语句中的每个Expr*槽位。
  • 02-parse-ast.md — 两阶段函数体解析。
  • scopes.md —ScopeStmt持有的ScopeDecl如何参与查找。
  • 04-ast-to-ir.md —DeferStmtThrowStmtCompileTimeForStmtTargetSwitchStmt的 IR 降级。
  • grammar.md#statements — 语句文法产生式。
  • 验证用 bundle 测试目录:docs/generated/tests/design/ast-reference/statements/。

【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询