Slang 语义检查阶段深度解析:从 AST 到类型完备 IR 前置状态
【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang
本篇技术指南聚焦 Slang 着色语言编译器前端流水线中的**语义检查(Semantic Checking)**阶段,即把解析器产出的原始 AST 转换为"名称已解析、类型已附加、conformance 已记录、函数体已完整检查"的可用 AST 的中间环节。文章以 docs/generated/design/pipeline/03-semantic-check.md 为骨架,结合source/slang/下slang-check-*.cpp系列源码逐一印证实现细节。读完本文,你将掌握语义检查的输入输出契约、SemanticsVisitor家族的职责划分、两遍式解析交互、泛型约束求解、隐式代码合成、修饰符校验、着色器入口点专项检查与错误恢复机制,并能沿着给出的源码路径继续深入。
输入与输出:语义检查的契约
语义检查阶段位于解析(02-parse-ast.md)与 AST→IR 下降(04-ast-to-ir.md)之间,其契约非常清晰:
- 输入:解析阶段产出的 AST,其中函数体仍以
UnparsedStmt形式存在,尚未进入语义检查的视野。 - 输出:同一棵 AST,但完成五项核心加工:
- 名称解析——每个携带
DeclRef的节点都指向其规范声明(canonical decl); - 类型附加——每个
Expr都携带Type*; - conformance 记录——类型与接口的满足关系被记录为 witness 表;
- 默认 conformance witness 合成——接口中带默认实现的方法被自动补全;
- 函数体完整检查——
UnparsedStmt被解析并检查为完整的Stmt树。
- 名称解析——每个携带
文档用一个具体例子说明这五项加工(03-semantic-check.md):
interface IFoo { int base(); int twice() { return base() * 2; } } struct S : IFoo { int base() { return 6; } static const int tag = 1; } int call<T : IFoo>(T v) { return v.twice(); }- 检查器在
S的基类列表和T的约束中把IFoo解析到同一个 decl(名称解析); - 给
v.twice()附上类型int,使调用合法(类型附加); - 记录
S : IFoo的 witness 表(conformance 记录); - 表中
twice槽位填入S从未写明的接口默认实现 witness(默认 witness 合成); - 把
call的UnparsedStmt展开成检查过的Stmt树(函数体完整检查); - 片段中唯一的修饰符是
S::tag上的static:checkModifier通过isModifierAllowedOnDecl询问static能否修饰结构体字段(可以),再通过getModifierConflictGroupKind检查该 decl 上是否已有其他修饰符占用static的冲突组(例如uniform会占用),结果无人占用(修饰符校验)。
最终结果即为 AST→IR 下降阶段的输入。
SemanticsVisitor:检查器的整体架构
语义检查器实现为一个visitor 子类家族,它们通过SemanticsContext共享状态。基类 visitor 位于 slang-check-impl.h,声明形式为:
struct SemanticsVisitor : public SemanticsContext顶层入口是 slang-check.cpp 中的checkTranslationUnit,前端在解析收集完 decls 后,对每个TranslationUnitRequest调用一次。从源码可见其编排逻辑(slang-check.cpp#L181-L203):先构造SharedSemanticsContext(挂载 linkage、module、诊断 sink、已加载模块字典),再创建SemanticsDeclVisitorBasevisitor,调用visitor.checkModule(translationUnit->getModuleDecl())完成主检查,最后收集 shader 参数。
文件与职责分工
source/slang/下的slang-check-*.cpp家族按关注点切分工作,全部通过SemanticsContext/SemanticsVisitor协作。下表每行都给出该文件自身会发出的一个示例诊断,使"职责"成为可被测试验证的声明而非标签:
| 文件 | 职责 | 示例拒绝诊断 |
|---|---|---|
| slang-check.cpp | 入口点;编排各检查阶段 | 无;它只负责阶段编排,自身诊断仅涉及下游编译器加载 |
| slang-check-decl.cpp | Decl检查——类型、签名、默认值、属性 | E30200,声明与更早声明冲突 |
| slang-check-expr.cpp | Expr检查——类型推断、lvalue 性、转换 | E30011,赋值目标不是左值 |
| slang-check-stmt.cpp | Stmt检查——控制流、作用域规则、返回类型校验 | E30003,break出现在循环/switch之外 |
| slang-check-type.cpp | 解析以Expr形式出现的Type引用 | E30060,在需要类型的位置使用了表达式 |
| slang-check-overload.cpp | 重载决议;为 lookup 产出的候选排序 | E40018,指出拒绝某候选的参数 note |
| slang-check-conformance.cpp | 验证并合成接口 conformance | 无;缺失需求由slang-check-decl.cpp中的调用方报告为E38100 |
| slang-check-conversion.cpp | 隐式转换排序与强制转换点检查 | E30523,初始化列表元素过多 |
| slang-check-inheritance.cpp | 继承与 extension 查找;facet 计算 | E30815,循环extension(见下文) |
| slang-check-modifier.cpp | 校验修饰符组合与属性参数 | E31202,同一 decl 上出现同一独占组的两个修饰符 |
| slang-check-constraint.cpp | 泛型约束求解(where子句、witness 推断) | E30433,pack 数量不满足countof(...)约束 |
| slang-check-resolve-val.cpp | 解析并规范化Type、DeclRef与 witness 值 | 无;坏的解析结果在使用点报告 |
| slang-check-shader.cpp | 入口点检查——阶段特定签名、参数规则 | E38007,入口点缺少 stage |
容易被误读的 E30815:并非通用循环检测器
表中的E30815比"循环 extension"字面含义窄得多,极易过度解读。它不是对 extension 目标类型的通用环检测:只有同一个ExtensionDecl在自身继承信息仍被计算时被重入才会触发。getInheritanceInfo(DeclRef<ExtensionDecl>)会把一个命名为该 extension 的InheritanceCircularityInfo节点压入链表栈并递归;_checkForCircularityInExtensionTargetType(slang-check-inheritance.cpp 约 254 行)在入口处遍历该栈,发现同一Decl*已存在时报告CircularityInExtension并返回空InheritanceInfo,从而终止递归而非栈溢出。
因此,从不重入同一 extension 的环属于其他诊断:读者最先想到的形状(相互引用的 extension 目标、extension 内自引用的typealias、经由This到达的 extension)分别报告E30027、E30813或致命E40002循环引用。还需注意其上方刻意为之的良性情形:_isInheritanceInfoBeingComputed的存在使__constraint A == B这类让T.A与T.B互为基类的相等约束在线性化时被跳过,而非报为环。
与解析器的两遍式交互
解析器将函数与方法体留作UnparsedStmt节点(见 02-parse-ast.md)。检查器遇到这类节点时,以SemanticsVisitor*调用parseUnparsedStmt(slang-parser.h),使解析器能在解析期回调检查器来消歧<令牌(泛型实参 vs 小于号)。函数体解析完毕后,检查器继续在生成的Stmt树上正常推进。
这种交错意味着函数体内部不存在干净的"解析/检查"分界线:解析与检查按需同步进行。其深层设计理由见 docs/design/parsing.md。
名称查找与 DeclRef
名称解析产出DeclRef——一个声明加上记录其泛型参数与外层上下文参数绑定方式的 substitution。DeclRefBase的具体操作(DirectDeclRef、LookupDeclRef、substitution 应用)实现在 slang-ast-decl-ref.cpp;而作用域构造、查找算法、遮蔽、可见性过滤与重载决议等算法规则位于专门的 docs/generated/design/name-resolution 子树,建议从 name-resolution/index.md 起步阅读;decl-refs自身的深层理由见 docs/design/decl-refs.md。
泛型特化与约束
泛型参数决议由三个文件协同完成:
- slang-check-constraint.cpp——累积并求解类型/值/witness 约束;
- slang-check-conformance.cpp——寻找(或合成)类型满足接口约束的 witness;
- slang-check-resolve-val.cpp——泛型决议后校验
Valsubstitution。
求解器的固定点与失败回退
解析泛型应用时,TryCheckOverloadCandidateConstraints(slang-check-overload.cpp)把最外层泛型的默认参数与 witness 参数送入约束求解器的固定点算法(trySolveGenericArguments)——与推断参数走同一条路径——仅把用户显式提供的普通参数前缀(OverloadCandidate::explicitGenericArgCount,见 slang-check-impl.h)作为固定调用方输入,以免用户手写的自引用参数被参数的默认值覆盖。求解失败时,代码回退到逐约束线性扫描,重新推导失败的约束以发出精确诊断。
关联类型约束的统一表示
写在关联类型上的约束——无论写作associatedtype A : IBar、associatedtype A where A : IBar还是__constraint A : IBar——都被统一记录为外层接口的GenericTypeConstraintDecl需求(A的兄弟节点),而非嵌套在A之下。在这种统一表示里,findWitnessForInterfaceRequirement(slang-check-decl.cpp)通过重新检查子类型(或==约束的类型相等)关系来满足接口级约束需求——此时This已被替换为具体满足类型——而不是在该类型中寻找某个成员。已被 conformance 合成安装的 witness(例如enum合成出的__Tag : __BuiltinIntegerType,包括bool标签的情形——此时不存在真正的子类型 witness,用NoneWitness标记编译器信任的约束已被满足)会在该函数顶部的 witness 表早退逻辑中被直接认可。
继承线性化与良性环
线性化继承列表由 slang-check-inheritance.cpp 的getInheritanceInfo/_calcInheritanceInfo计算。计算T.D这类关联类型访问的继承时,引擎会浮现各锚点类型所满足接口的接口级__constraint,通过锚点的 conformance witness 重新表达每条约束,并把对端端点加入该访问的基类列表。__constraint A == B使T.A与T.B互为基类——一个良性环:引擎通过_isInheritanceInfoBeingComputed跳过继承信息仍在计算的基类,把跳过的进行中祖先DeclRef累积到HashSet<DeclRef<Decl>>* ioSkippedIncompleteFacet出参中;减去自身后跳过集非空的帧是上下文相关的(partial),不缓存,由后续根级查询重算。接口__constraint上裸This主体(表达的是继承而非被检查的谓词)在visitGenericTypeConstraintDecl(slang-check-decl.cpp)检查期间被拒绝。
泛型失败原因的惰性捕获
当泛型无法为某调用特化时,失败原因急切捕获、惰性报告。约束求解器记录一个GenericArgumentInferenceFailure(slang-check-impl.h)——一个带标签的联合,其Kind同时选择存储的负载与最终发出的诊断:
Kind | 诊断 | 触发场景 |
|---|---|---|
VariadicPackCountMismatch | E30433 | takesTwo(1, 2, 3)对void takesTwo<each T>(expand each T args) where countof(T) == 2 |
GenericArityMismatch | E30438 | 实参列表根本无法匹配泛型形参列表的调用 |
OrdinaryGenericParamNotInferred | E30439 | f(1)对void f<T>(int a)——没有实参提及T |
InterfaceConformanceNotSatisfied | E38029 | pick(s)对T pick<T : IFoo>(T a),而S未实现IFoo |
GenericConstraintNotSatisfied | E30440 | f(1.5f)对void f<T>(T a) where T == int;所有非 conformance 约束的兜底 |
GenericParamUnificationConflict | E30442 | two(x, y)对void two<T>(T a, T b),实参类型A与B无关 |
每个分支在错误后都附带一条携带候选渲染签名(GenericSignatureTried)的 "see declaration of" note;GenericConstraintNotSatisfied分支额外加一条指向where子句的 note。每个Kind只存储出问题的字段(计数、参数Decl*或替换后的子/超类型);昂贵的消息格式化被推迟,使投机性候选永不为此买单。失败被挂到OverloadCandidate上,仅当重载决议最终选中该失败候选时才转换为聚焦诊断——见CompleteOverloadCandidate中对candidate.genericInferenceFailure.kind的switch(slang-check-overload.cpp)。在该机制之前,所有特化失败都会塌缩进笼统的Diagnostics::GenericArgumentInferenceFailed。
可微性即接口 conformance
可微性被记录为将函数视为一个类型时的接口 conformance,而非让后续阶段重新推导的修饰符事实。考虑:
[Differentiable] float f(float x) { return x * x; }[Differentiable]解析为BackwardDifferentiableAttribute(见 core.meta.slang 中的attribute_syntax声明,约 470 行)。当SemanticsDeclHeaderVisitor::checkDifferentiableCallableCommon(slang-check-decl.cpp,约 14950 行)看到该属性或ForwardDifferentiableAttribute时,它调用extendContainerDecl合成extension __func_as_type(f) : IForwardDifferentiable<__func_as_type(f)>,再用addSynthesizedFunc为该 extension 提供接口要求的fwd_diff成员,以kIROp_ForwardDifferentiate作为实现。合成出的fwd_diff又被赋予同一对 conformance——这正是高阶微分能经由普通 lookup 解析的原因。
接口类型本身由getForwardDiffFuncInterfaceType与getBackwardDiffFuncInterfaceType(约 10014、10020 行)构建,它们把基础函数类型与IForwardDifferentiable<FType>/IBackwardDifferentiable<FType>要求的__hasDiffTypeInfowitness 配对——这些接口声明在 core.meta.slang 约 720、739 行,其需求(fwd_diff、BwdCallable/MinimalContext关联类型、apply_bwd)正是检查器必须供给的内容。
由于该事实现存在于 witness 表,"此被调用者是否可微?"成为子类型查询而非修饰符查找:isFuncForwardDifferentiable与isFuncBackwardDifferentiable(约 5467、5476 行)返回tryGetSubtypeWitness产出的SubtypeWitness*,取代了早先的布尔谓词doesCalleeHaveFwdDiff/doesCalleeHaveBwdDiff。返回 witness 而非bool至关重要,因为调用方需要该 witness 来构建并特化导数调用。
写在接口需求上的[Differentiable]注解与上文关联类型约束的处理方式相同——作为外层接口的需求,而非嵌套在成员之下。_moveInterfaceDifferentiabilityRequirementToInterface(约 14863 行)先在拥有其类型所引用泛型环境的 callable 下创建GenericTypeConstraintDecl,再用liftDeclFromGenericContainers将其提升为接口下独立的泛型需求。显式拼写__func_extension fwd_diff(foo)(...)通过_funcExtensionForwardDiff/_funcExtensionBackwardDiff(约 15981、16016 行)到达同一表示:改写为extension foo : IForwardDifferentiable<foo>,用户函数体作为fwd_diff成员。
完整的概念模型(接口、witness 表、存在类型)见 docs/design/interfaces.md 与 docs/design/existential-types.md。
隐式代码合成
部分声明在检查期(而非解析期)获得成员:默认 conformance witness、生成的比较/构造方法、若干内建 conformance。合成决策主要位于 slang-check-decl.cpp——例如默认构造函数的_synthesizeCtorSignature与接口需求的trySynthesize*RequirementWitness系列——而 slang-ast-synthesis.cpp 提供ASTSynthesizer助手(emitBinaryExpr、emitVarExpr、emitInvokeExpr、emitVarDeclStmt……)来构建这些例程发出的 AST 片段。每当检查器需要用户未写出但语言保证存在的成员时,即调用该机制。
修饰符校验
修饰符专项检查位于 slang-check-modifier.cpp:哪些修饰符允许出现在哪些 decl 上、互斥组合、属性参数类型,以及 Slang 与 GLSL 输入的差异。修饰符节点本身定义在 slang-ast-modifier.h。
互斥组与 E31202
"互斥"由getModifierConflictGroupKind(约 1564 行)裁决:把修饰符的ASTNodeType映射到其竞争的组;第二个落入已占用组的修饰符产生E31202。多数修饰符自成一组,因此普通重复(static static int g;)是常见情形;但值得记住的多成员组有:
out、inout、ref、borrow共享一组;static与uniform共享一组;nointerpolation、noperspective、linear、sample、centroid共享一组。
GLSL 方言轴:globallycoherent的可达路径
这里的方言轴是GLSL 而非 HLSL。checkModifier(约 1936 行)从-allow-glsl选项(CompilerOptionName::AllowGLSL)或模块上的GLSLModuleModifier计算isGLSLInput,并把它传给isModifierAllowedOnDecl(约 1675 行);被该谓词拒绝的位置上的修饰符报告为E31201(modifier ' ' is not allowed here)。
一个具体可达的实例:函数参数上的globallycoherent,如:
void f(globallycoherent int x) { }不带-allow-glsl时,GloballyCoherentModifier与HLSLVolatileModifier的分支(约 1731 行)要求as<VarDecl>(decl)——而参数是ParamDecl,它从VarDeclBase派生,是VarDecl的兄弟而非其派生类(slang-ast-decl.h 约 321、339、597 行)——于是谓词返回 false,修饰符被拒绝。这是解析器乐于产出的位置,正因如此它可达;读者可能尝试的许多其他组合要么在解析器处被解决,要么被普通接受,永远不会到达E31201。
同一分支也是 GLSL 标志"放宽了什么、没放宽什么"的最佳示例,因为两个分支极易读反。两个分支都已接受结构体字段:非 GLSL 分支允许父节点为任意StructDecl的VarDecl,所以struct G { globallycoherent int a; }无论是否带-allow-glsl都被接受,标志在该处无可观察差异。isGLSLInput真正新增的是上述参数情形(as<ParamDecl>(decl))、非VarDecl的全局VarDeclBase声明,以及与既有分支冗余的、本身就是全局的结构体字段。只有少数几个条目分支依赖该标志。
可见性作用域
public、internal、private与其他修饰符一样被检查,但它们命名的作用域不是源文件。isDeclVisibleFromScope(slang-check-expr.cpp,约 1144 行)裁决该问题:
| 修饰符 | 可见范围 |
|---|---|
public | 任意作用域,包括其他模块 |
internal | 声明所在模块内的任意作用域 |
private | 声明所在类型或命名空间,以及该类型的 extension |
因此private是类型作用域而非文件作用域:同一文件中的自由函数不能读取struct的private成员,读取被E30600拒绝。在无处可作用的位置书写private——全局作用域或接口需求上——被提前以E30603拒绝。
dyn interface限制
validateDynInterfaceUsage与validateDynInterfaceUseWithInheritanceDecl(slang-check-decl.cpp,约 372、442 行)约束dyn interface可声明什么、什么可以满足它。两者都受allowExperimentalDynamicDispatch(约 364 行)门控,且仅在模块语言版本为 2026 或更高(-std 2026)且未传入-enable-experimental-dynamic-dispatch时生效——因此同一份源码在默认-std下编译结果不同。
门打开时:接口不可为泛型(E33072),不可声明关联类型(E33073)、泛型方法(E33074)、[mutating]方法(E33075)、[Differentiable]方法(E33076)或非dyn基接口(E33077);满足类型不得通过extension获得 conformance(E33078)、不得为泛型(E33082),其字段既不能是 unsized(E33079)、opaque(E33080)也不能是不可复制(E33081)——动态表示必须是可复制的定长 box。
着色器专项检查
slang-check-shader.cpp 校验入口点:函数的 stage 属性、参数修饰符(in、out、inout与阶段特定内建)、返回类型与 stage 的兼容性、资源绑定规则。失败以引用shader("...")属性或入口点签名的诊断形式浮出。
值得单独点名的五个检查,均针对入口点校验而非通用推断遍历:
- 泛型结构体能力需求。
collectGenericStructTypeUses递归扫描入口点签名类型,找出每个用户定义的泛型结构体(例如Foo<int>,包括嵌套在Optional<...>、数组或ConstantBuffer<...>内的),并对照目标校验其[require(...)]。通用能力推断遍历(SemanticsDeclReferenceVisitor)只为DirectDeclRef记录类型需求;泛型特化是GenericAppDeclRef会被跳过,否则需求将丢失。该检查刻意放在此处而非推断遍历中,以避免迫使每个命名此类类型的库函数重新声明这些能力。携带MagicTypeModifier/IntrinsicTypeModifier的内建泛型类型已有更具体的诊断,被过滤(但仍递归穿过)。目标无法提供的能力需求——SPIR-V 入口点签名中出现Foo<int>,而[require(cpp)] struct Foo<T>——以E36107报在入口点上,并附see using of 'Foo'note。 - 未特化的泛型入口点。如
void main<T>(...)这类真正未特化的泛型入口点会下降到IRGeneric而非IRFunc,过去会在链接期崩溃。createSpecializedGlobalAndEntryPointsComponentType现在结合Linkage::isSpecialized与特化参数字符串的存在性作出判定,仅对真正未特化的情形调用diagnoseGenericEntryPoint(约 3964 行)发出E38014。 - 冲突的深度输出。片段入口点最多写一个深度系统值。由于逐参数语义检查孤立地看待每个 semantic,冲突需单独检测:
collectDepthOutputSemantics(约 536 行)遍历每个out/inout参数与返回类型——解开Conditional<T>与数组包装、递归进入结构体字段,因此out DepthOut a[1]字段上的 semantic 也能被触及——收集计数大于 1 时产生Diagnostics::MultipleDepthOutputSemantics(E30705),把第二个贡献者命名为与第一个冲突。 - 系统值 semantic 的类型兼容性。
isSemanticTypeCompatible(约 112 行)决定声明的类型能否携带给定的系统值 semantic:两类型同形(同为标量,或同为元素数相等的向量)且标量元素类型同属一类别(整数、浮点或 bool)即匹配。这接纳int3携带uint3semantic 之类的符号强转,同时拒绝跨类别者(如float gi : SV_GroupIndex)与形状不匹配者(如float pos : SV_Position);两者均报E30701,消息列出该 semantic 可接受的类型。 - 入口点参数上被忽略的绑定修饰符。Slang 在某些位置静默忽略
[[vk::binding(...)]]、[[vk::push_constant]]、register()与packoffset(),无论哪种方向都会误导用户。入口点参数检查因此对每个此类修饰符报告Diagnostics::UnhandledModOnEntryPointParameter(E38010,消息点名修饰符与参数并说明该修饰符将被忽略)。源码(slang-check-shader.cpp#L2434-L2466)证实:只有[[vk::binding(...)]]情形受门控——由_allTargetsSupportVkBindingOnEntryPointParameters(约 1580 行,覆盖 linkage 的全部 target)与isVkBindingCompatibleEntryPointParameterType(约 920 行,针对参数自身类型)双重把关,仅在该属性确实会被丢弃处触发;而[[vk::push_constant]]、register()、packoffset()三个分支无条件诊断入口点参数上的出现。
失败模式与诊断恢复
所有语义检查错误都流经SemanticsContext中贯穿的DiagnosticSink。检查级恢复通常是"以占位类型继续",使单个错误不致级联:未解析的 decl 变为ErrorType类型,重载决议返回合成的errorExpr而非中止。诊断力求点名出错的源构造:当ExpectATypeRepr(slang-check-type.cpp)发现不表示类型的表达式时,它用表达式的实际类型(以及可用时的被引用名字)构造Diagnostics::ExpectedAType消息。
本阶段的若干诊断不止点名构造,还指引用户可能的修复:
- 逐候选参数不匹配。调用未匹配任何重载时,诊断现在列出每个候选签名及拒绝它的具体参数。
slang-check-overload.cpp在候选上记录出错的参数索引与期望/实际类型,再对每个候选发出Diagnostics::OverloadCandidateArgumentTypeMismatchnote。以两个float调用void g(A, int)/void g(B, float):调用点报E39999,随后每个候选一条E40011candidate: <signature>note,每条后跟E40018note,读作argument 0 does not match: expected 'A', got 'float'。候选按渲染签名串去重(而非按Decl*——那会把foo<float>与foo<int>等不同特化错误折叠);至多打印十个唯一候选,余者以E40015"N more overload candidates" note 汇总。 - 未定义标识符上的"Did you mean ...?"。名字解析失败时,
slang-check-expr.cpp遍历作用域内候选,通过StringUtil::calcLevenshteinDistanceCaseInsensitive为既有Diagnostics::UndefinedIdentifier(E30015)附加一个保守的相似名建议,而非发出独立 note。findClosestInScopeName(约 5216 行)把预算定死:长度小于 3 或大于 256 的名字不提供建议;允许距离为min(3, max(1, length / 3))——约每三个字符一次编辑,下限 1、上限 3;跳过 core-module 声明与作用域无法访问的内容;两个不同名字距离打平则抑制建议,避免输出依赖作用域遍历顺序。于是myLongVariableNam建议myLongVariableName,而ac不会建议作用域内的ab,sqr不会建议 core module 的sqrt。 - 丢弃的
[NoDiscard]结果。maybeDiagnoseDiscardedNoDiscardResult(slang-check-stmt.cpp)在[NoDiscard]函数的调用结果被丢弃——f();写成裸表达式语句——时以E30059触发,递归穿过逗号、三元选择与短路形式以定位被丢弃的子表达式。裸的丢弃构造函数调用被刻意排除。
诊断基础设施详见 docs/generated/design/cross-cutting/diagnostics.md。
检查状态机:没有"errored"状态
checkModule驱动翻译单元中的每个Decl沿DeclCheckState序列推进直至CapabilityChecked(DefinitionChecked与CapabilityChecked定义见 slang-ast-support-types.h 约 556、561 行);不存在单独的 errored 状态,因此恢复被表达为诊断加上就地替换的错误类型/表达式。AST 至此已为 IR 下降(04-ast-to-ir.md)就绪。
延伸阅读
- 本阶段的流程定位:前序 02-parse-ast.md、后续 04-ast-to-ir.md,以及流水线总览 docs/generated/design/pipeline/overview.md;
- 名称解析与重载决议算法:docs/generated/design/name-resolution/index.md;
- 接口、witness 表与存在类型模型:docs/design/interfaces.md、docs/design/existential-types.md;
- 解析/检查交错的深层理由:docs/design/parsing.md;
- 诊断基础设施:docs/generated/design/cross-cutting/diagnostics.md;
- 核心实现目录:source/slang 下的
slang-check-*.cpp家族。
【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考