news 2026/9/20 10:02:27

Slang 编译流水线之语义检查:从裸 AST 到类型完备可降级 IR 的完整实现解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Slang 编译流水线之语义检查:从裸 AST 到类型完备可降级 IR 的完整实现解析

Slang 编译流水线之语义检查:从裸 AST 到类型完备可降级 IR 的完整实现解析

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

导读

本文以 Slang 开源编译器(GitHub_Trending/sl/slang)官方设计文档 docs/generated/design/pipeline/03-semantic-check.md 为核心骨架,系统讲解流水线第三阶段——语义检查(Semantic Checking):它把解析器产出的裸 AST 变成名字已解析、类型已附着、conformance 已记录、修饰符已验证、函数体已完整检查的 AST,为 AST → IR 降级做好准备。读完本文,你将掌握SemanticsVisitor一族访问器的文件分工与调用链、DeclRef名称解析机制、泛型约束求解与可微性 conformance 化实现、修饰符冲突组与可见性作用域规则、着色器入口点专项检查,以及错误恢复与诊断的工程细节。

阶段定位:输入、输出与前后衔接

语义检查位于编译流水线的第三环,承接 02-parse-ast.md(解析成 AST),输出交给 04-ast-to-ir.md(AST → IR 降级)。

输入:解析器产生的 AST,其中函数体仍以UnparsedStmt形式保存(尚未解析成语句树),基类列表仍是未检查的Expr,没有任何表达式携带类型。

输出:同一棵 AST,但满足六项不变量:

  1. 名字已解析(names resolved)——每个携带DeclRef的节点都指向规范(canonical)声明;
  2. 类型已附着(types attached)——每个Expr都携带一个Type*
  3. conformance 已记录(conformances recorded)——类型与接口的 witness 表已构建;
  4. 修饰符已验证(modifiers validated)——非法组合与错误位置被拒绝;
  5. 默认 conformance witness 已合成(default conformance witnesses synthesized)——接口未显式实现的成员由编译器补全;
  6. 函数体已完整解析并检查(function bodies fully parsed and checked)——UnparsedStmt变成已检查的Stmt树。

文档用一个最小示例完整演示了这六项:

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基类列表中的IFooT约束中的IFoo解析到同一个声明(names resolved);
  • v.twice()附着类型int,从而使该调用合法(types attached);
  • 记录S : IFoo的 witness 表(conformances recorded);
  • 由于S没有显式实现twice,检查器把接口默认函数体作为 witness 填入该表(default conformance witnesses synthesized);
  • callUnparsedStmt函数体解析并检查成Stmt树(function bodies fully parsed and checked);
  • S::tag上的staticcheckModifier通过isModifierAllowedOnDecl确认 struct 字段可挂static,并通过getModifierConflictGroupKind确认static的互斥组尚未被占用(modifiers validated)——假如同时写了uniform,就会触发冲突诊断。

SemanticsVisitor:检查器的架构骨架

语义检查器实现为一族共享SemanticsContext状态的访问器子类。基访问器声明在 slang-check-impl.h(第 1778 行):

struct SemanticsVisitor : public SemanticsContext

顶层入口是 slang-check.cpp 中的checkTranslationUnit(第 181 行),前端在解析收集完 decls 后,对每个TranslationUnitRequest调用一次。其核心流程(可从源码直接看到):

  1. 构造SharedSemanticsContext,绑定 linkage、module、诊断 sink、已加载模块字典与 translation unit;
  2. 用该上下文实例化SemanticsDeclVisitorBase
  3. 调用visitor.checkModule(translationUnit->getModuleDecl())对翻译单元内所有声明执行主检查;
  4. 调用_collectShaderParams收集着色器参数。

SemanticsVisitor还提供了dispatchStmt/dispatchExpr两个分发入口,内部实例化SemanticsStmtVisitor/SemanticsExprVisitor并统一捕获异常,保证出错时向DiagnosticSink记录内部错误位置。

slang-check-*.cpp家族:按关注点拆分

source/slang/下的slang-check-*.cpp一族文件按职责拆分检查工作,全部通过 slang-check-impl.h 中的SemanticsContext/SemanticsVisitor协作。下表同时给出每个文件自身会发射的代表性诊断,使每一行都成为可写测试的断言目标:

文件关注点代表性诊断(Example rejection)
slang-check.cpp入口点;编排各检查阶段无——它只负责阶段排序,自身诊断仅关于下游编译器加载
slang-check-decl.cppDecl检查——类型、签名、默认值、属性E30200:声明与更早的声明冲突
slang-check-expr.cppExpr检查——类型推断、左值性、转换E30011:对非左值赋值
slang-check-stmt.cppStmt检查——控制流、作用域规则、返回类型验证E30003break出现在循环或switch之外
slang-check-type.cpp解析以Expr形式出现的Type引用E30060:表达式被用在需要类型的位置
slang-check-overload.cpp重载解析;对 lookup 产出的候选排序E40018:指出拒绝某候选的具体实参的 note
slang-check-conformance.cpp验证与合成接口 conformance无——缺失要求的E38100由其在slang-check-decl.cpp中的调用方报告
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解析并规范化TypeDeclRef、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——分别报告E30027E30813或致命的E40002cyclic reference。紧邻其上还有一个刻意为之的良性场景:_isInheritanceInfoBeingComputed的存在使__constraint A == B这类让T.AT.B互为彼此的基类的相等约束,在线性化时被跳过而非报告为环。

与解析器的两阶段交互

解析器把函数与方法体保留为UnparsedStmt节点。检查器遇到时调用parseUnparsedStmt(slang-parser.h),并传入SemanticsVisitor*,使解析器能在解析期间回调检查器来消歧<记号(泛型实参 vs 比较运算符)。函数体解析完毕后,检查器继续在产生的Stmt树上正常推进。

这意味着函数体内部不存在干净的 parse/check 边界:解析与检查按需同步进行。更深入的理由见 docs/design/parsing.md。

名称查找与DeclRef

名称解析产生DeclRef——一个声明加上记录其泛型参数与外围上下文参数如何绑定的替换(substitution)。具体的DeclRefBase运算(DirectDeclRefLookupDeclRef、替换应用)实现在 slang-ast-decl-ref.cpp。算法层面的规则——作用域构造、查找算法、遮蔽、可见性过滤、重载解析——位于 docs/generated/design/name-resolution/ 子目录,建议从 index.md 读起(其下还有lookup.mdoverload-resolution.mdscopes.mdvisibility.md四篇)。DeclRef本身的设计动机见 docs/design/decl-refs.md。

泛型特化与约束

检查器通过三个文件协作实现泛型参数解析:

  • slang-check-constraint.cpp——累积并求解类型/值/witness 约束;
  • slang-check-conformance.cpp——寻找(或合成)类型满足约束所需接口的 witness;
  • slang-check-resolve-val.cpp——泛型解析后验证Val替换。

泛型应用的约束求解路径

解析泛型应用时,TryCheckOverloadCandidateConstraints(slang-check-overload.cpp)把最外层泛型的默认实参与 witness 实参送入约束求解器的不动点(trySolveGenericArguments)——与推断实参走同一路径——只把显式提供的普通实参前缀(OverloadCandidate::explicitGenericArgCount,定义于 slang-check-impl.h)作为固定的调用方输入,避免用户手写的自引用实参被参数的默认值覆盖。求解失败时,代码回退到逐约束线性扫描,重新推导失败的约束以产生精确诊断。

关联类型约束的统一表示

写在关联类型上的约束——无论是associatedtype A : IBarassociatedtype A where A : IBar还是__constraint A : IBar——都被统一记录为外围接口的GenericTypeConstraintDecl需求(是A的兄弟节点,而不是嵌套在A之下)。在这种统一表示下,findWitnessForInterfaceRequirement(slang-check-decl.cpp)满足接口级约束需求的方式是:在This被替换为具体 conforming 类型后,重新检查子类型(或对==约束检查类型相等)关系,而不是去该类型上找成员。conformance 合成已经安装的 witness——例如enum合成的__Tag : __BuiltinIntegerType(包括bool标记情形,此时不存在真正的子类型 witness,用NoneWitness标记编译器信任的约束已满足)——会被该函数顶部的 witness 表早退(early-out)直接采信。

继承列表线性化与良性环

线性化继承列表由getInheritanceInfo/_calcInheritanceInfo(slang-check-inheritance.cpp)计算。计算T.D这类关联类型访问的继承时,引擎会:浮出每个锚点类型所 conforms 接口的接口级__constraint,通过锚点的 conformance witness 重新表达每条约束,并把相反端点作为该访问的基类加入。__constraint A == B这类相等约束使T.AT.B互为基类——一个良性环。引擎通过跳过继承信息仍在计算中的基类(_isInheritanceInfoBeingComputed)来容忍它,把被跳过的进行中祖先DeclRef累积到HashSet<DeclRef<Decl>>* ioSkippedIncompleteFacet输出参数中;某帧在减去自身后若跳过集合非空,则其结果是上下文相关(部分)的,不缓存,由后续根级查询重算。裸This出现在接口__constraint主语位置(表达继承而非被检查的谓词)会在visitGenericTypeConstraintDecl(slang-check-decl.cpp)检查期间被拒绝。

特化失败:急切记录、惰性报告

当泛型无法为某调用特化时,失败原因被急切捕获但惰性报告。约束求解器记录一个GenericArgumentInferenceFailure(slang-check-impl.h)——一个 tagged union,其Kind同时决定存储的载荷与最终发射的诊断:

Kind诊断触发示例
VariadicPackCountMismatchE30433takesTwo(1, 2, 3)void takesTwo<each T>(expand each T args) where countof(T) == 2
GenericArityMismatchE30438实参列表完全无法匹配泛型形参列表的调用
OrdinaryGenericParamNotInferredE30439f(1)void f<T>(int a)——没有任何实参提及T
InterfaceConformanceNotSatisfiedE38029pick(s)T pick<T : IFoo>(T a),而S未实现IFoo
GenericConstraintNotSatisfiedE30440f(1.5f)void f<T>(T a) where T == int;是所有非 conformance 约束的回退项
GenericParamUnificationConflictE30442two(x, y)void two<T>(T a, T b),而AB不相关

每个分支在错误后都附一条携带候选渲染签名(GenericSignatureTried)的 "see declaration of" note;GenericConstraintNotSatisfied分支额外加一条指向where子句的 note。每个Kind只存储有问题的字段(数量、形参Decl*或替换后的子/超类型),昂贵的消息格式化被推迟,使投机性候选永远不必为格式化买单。失败被附着到OverloadCandidate上,仅当重载解析最终选中该失败候选时才转为聚焦的诊断——见CompleteOverloadCandidate(slang-check-overload.cpp)中对candidate.genericInferenceFailure.kindswitch。在此机制之前,所有特化失败都会坍缩成兜底诊断Diagnostics::GenericArgumentInferenceFailed

可微性:作为接口 conformance 记录

可微性被记录为把函数视为类型后的接口 conformance,而非后续阶段再推导的修饰符事实。考虑:

[Differentiable] float f(float x) { return x * x; }

[Differentiable]解析为BackwardDifferentiableAttribute(见 core.meta.slang 中attribute_syntax声明,source_commit对应行 470)。当SemanticsDeclHeaderVisitor::checkDifferentiableCallableCommon(slang-check-decl.cpp 第 15624 行)看到该属性或ForwardDifferentiableAttribute时:

  1. 调用extendContainerDecl合成extension __func_as_type(f) : IForwardDifferentiable<__func_as_type(f)>
  2. 调用addSynthesizedFunc给该 extension 加上接口要求的fwd_diff成员,以kIROp_ForwardDifferentiate作为其实现。

合成出的fwd_diff又被赋予同一对 conformance,这正是高阶微分能通过普通 lookup 解析的原因。

接口类型本身由getForwardDiffFuncInterfaceType/getBackwardDiffFuncInterfaceType(slang-check-decl.cpp 第 10051/10057 行)构建:把基础函数类型与__hasDiffTypeInfowitness 配对——后者正是IForwardDifferentiable<FType>/IBackwardDifferentiable<FType>的要求(这两个接口声明于 core.meta.slang 第 720/739 行,其需求fwd_diffBwdCallable/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助手(emitBinaryExpremitVarExpremitInvokeExpremitVarDeclStmt等)来构建这些例程发射的 AST 片段。只要检查器需要用户未写但语言保证存在的成员,就会调用这套机制。

修饰符验证

修饰符专用检查位于 slang-check-modifier.cpp:哪些修饰符可挂在哪些 decl 上、互斥组合、属性实参类型、以及 Slang 与 GLSL 输入之间的差异。修饰符节点本身定义于 slang-ast-modifier.h。

互斥组与冲突诊断

"互斥"由getModifierConflictGroupKind(slang-check-modifier.cpp 第 1564 行)决定:它把修饰符的ASTNodeType映射到其竞争的组;第二个落入某 decl 上已被占用组的修饰符产生E31202。多数修饰符自成一组,所以static static int g;这种单纯重复是最常见情形;但含多个成员的组值得记住:

  • outinoutrefborrow共享一组;
  • staticuniform共享一组;
  • nointerpolationnoperspectivelinearsamplecentroid共享一组。

GLSL 方言轴:-allow-glsl

这里的方言轴是 GLSL 而非 HLSL。checkModifier(第 1936 行)从-allow-glsl选项(CompilerOptionName::AllowGLSL)或模块上的GLSLModuleModifier计算isGLSLInput,并把它传给isModifierAllowedOnDecl(第 1675 行);被该谓词拒绝的修饰符位置报告为E31201modifier ' ' is not allowed here)。

一个能实际触达它的具体组合:函数参数上的globallycoherent

void f(globallycoherent int x) { }

未加-allow-glsl时,GloballyCoherentModifierHLSLVolatileModifier分支(第 1731 行)要求as<VarDecl>(decl)——而参数是ParamDecl,它派生自VarDeclBase,是VarDecl兄弟而非子类(见 slang-ast-decl.h 第 321、339、597 行)——谓词返回 false,修饰符被拒绝。这是一个解析器乐意产出、因此可达的位置;读者可能尝试的许多其他组合要么在解析器阶段被解决,要么被直接接受,根本到不了E31201

同一个分支最能说明 GLSL 标志"放宽了什么、没放宽什么",因为两个分支很容易读反。两个分支都已经接受 struct 字段:非 GLSL 分支允许父节点是任意StructDeclVarDecl,所以struct G { globallycoherent int a; }无论是否带-allow-glsl都被接受,该标志在此无可观察差异。isGLSLInput额外带来的是上述参数情形(as<ParamDecl>(decl))、是VarDeclBase而非VarDecl的全局声明,以及与既有分支冗余的"本身是全局的 struct 的字段"。实际上只有少量条目按该标志分支。

可见性作用域:类型作用域而非文件作用域

publicinternalprivate与其他修饰符一样被检查,但它们命名的作用域不是源文件isDeclVisibleFromScope(slang-check-expr.cpp 第 1148 行)判定:

修饰符可见于
public任意作用域,包括其他模块
internal声明所在模块内的任意作用域
private声明的类型或命名空间,以及该类型的 extensions

因此private类型作用域而非文件作用域:同一文件中的自由函数不能读取structprivate成员,读取会被E30600拒绝;在没有外围类型可供其限定作用域的位置写private(全局作用域,或接口需求上)会以E30603提前拒绝。

dyn interface限制

validateDynInterfaceUsagevalidateDynInterfaceUseWithInheritanceDecl(slang-check-decl.cpp 第 372/442 行)约束dyn interface可声明的成员与可 conforms 它的类型。两者都由allowExperimentalDynamicDispatch(第 364 行)门控,只有在模块语言版本为 2026 或更高(-std 2026且未传入-enable-experimental-dynamic-dispatch时才生效——因此同一份源码在默认-std下编译结果不同。

门打开时:

  • 接口不得是泛型(E33072),不得声明关联类型(E33073)、泛型方法(E33074)、[mutating]方法(E33075)、[Differentiable]方法(E33076)、或非dyn基接口(E33077);
  • conforming 类型不得通过extension获得 conformance(E33078),不得是泛型(E33082),其字段既不能是 unsized(E33079)、opaque(E33080)也不能非可拷贝(E33081)——动态表示必须是一个可拷贝、定长的盒子。

着色器专用检查

slang-check-shader.cpp 验证入口点:函数的 stage 属性、参数修饰符(inoutinout与阶段专用 intrinsic)、返回类型与 stage 的兼容性、资源绑定规则。失败以引用shader("...")属性或入口点签名的诊断呈现。

以下五项检查专门针对入口点验证(而非通用推断遍历),值得单独说明:

  • 泛型结构体 capability 需求collectGenericStructTypeUses(slang-check-shader.cpp)递归遍历入口点签名类型,找出每个用户自定义泛型结构体(例如Foo<int>,包括嵌套在Optional<...>、数组或ConstantBuffer<...>内部的)并对其[require(...)]做目标校验。通用 capability 推断遍历(SemanticsDeclReferenceVisitor)只为DirectDeclRef记录类型需求;泛型特化是GenericAppDeclRef会被跳过,否则需求会被丢弃。该检查刻意放在这里而非推断遍历中,以免每个命名此类类型的库函数被迫重新声明 capability。带MagicTypeModifier/IntrinsicTypeModifier的内建泛型类型已有更具体的诊断,被过滤掉(但仍被穿过递归)。目标无法满足的需求——SPIR-V 入口点签名中Foo<int>引用[require(cpp)] struct Foo<T>——以E36107报告在入口点上,后随 "see using of 'Foo'" note。
  • 未特化的泛型入口点void main<T>(...)这类真正未特化的泛型入口点会降级为IRGeneric而非IRFunc,过去会在链接期崩溃。createSpecializedGlobalAndEntryPointsComponentType(slang-check-shader.cpp)现在用Linkage::isSpecialized结合特化实参字符串是否存在来判断,只在真正未特化时调用diagnoseGenericEntryPoint(第 3964 行)对入口点名发射E38014
  • 冲突的深度输出。片段入口点最多写一个深度系统值。由于逐参数语义检查孤立地看待每个 semantic,冲突被单独检测:collectDepthOutputSemantics(slang-check-shader.cpp,source_commit对应第 536 行)遍历每个out/inout参数与返回类型——解开Conditional<T>与数组包装、递归进入 struct 字段,因此out DepthOut a[1]字段上的 semantic 也能被触达——收集计数大于一即产生Diagnostics::MultipleDepthOutputSemanticsE30705),把第二个贡献者命名为与第一个冲突。
  • 系统值 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::UnhandledModOnEntryPointParameterE38010,消息点名修饰符与参数并说明它将被忽略)。只有[[vk::binding(...)]]情形被门控——_allTargetsSupportVkBindingOnEntryPointParameters(第 1580 行)遍历 linkage 的全部目标、isVkBindingCompatibleEntryPointParameterType(第 920 行)检查参数自身类型——使它只在该属性真会被丢弃时触发。[[vk::push_constant]]register()packoffset()出现在入口点参数上时无条件诊断。

失败模式:错误恢复与高价值诊断

所有语义检查错误都流经线程进SemanticsContextDiagnosticSink。检查级恢复的一般策略是"用占位类型继续",避免一个错误级联爆炸:未解析的声明变成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::UndefinedIdentifierE30015)上,而非发射游离 note。findClosestInScopeName(第 5327 行)把预算定得很具体:长度小于 3 或大于 256 的名字不给出任何建议;允许距离为min(3, max(1, length / 3))——大约每三个字符一次编辑,下限 1 上限 3;核心模块声明与作用域无法访问的内容被跳过;两个不同名字打成平手时抑制建议,使输出不依赖作用域遍历顺序。于是myLongVariableNam建议myLongVariableName,而ac不会建议作用域内的absqr不会建议核心模块的sqrt
  • 被丢弃的[NoDiscard]结果maybeDiagnoseDiscardedNoDiscardResult(slang-check-stmt.cpp)在[NoDiscard]标记函数的调用结果被丢弃——f();写成裸表达式语句——时以E30059触发,递归穿过逗号、三元选择与短路形式以找到被丢弃的子表达式。裸丢弃的构造函数调用被刻意排除。

诊断基础设施整体见 docs/generated/design/cross-cutting/diagnostics.md。

收尾:checkModule与降级就绪

checkModule把翻译单元中每个Decl驱动经过DeclCheckState序列直至CapabilityChecked;没有单独的 errored 状态,因此恢复表现为"诊断 + 错误类型/错误表达式就地替换"。处理完毕的 AST 即可交给 IR 降级(见 04-ast-to-ir.md)。从整体流水线视角看,语义检查正是 docs/generated/design/pipeline/overview.md 所描绘的"parse → semantic check → lower → IR passes → emit"链条上承上启下的一环,其驱动文件即本文反复引用的slang-check.cppslang-check-*.cpp家族。

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 10:01:43

基于虚拟机的Win10教学课件:安装、优化与排错实践

简介&#xff1a;这套Windows 10操作系统应用课件面向计算机文化基础课程的学习者与教学者&#xff0c;以任务驱动方式讲解桌面组成、任务栏按钮、窗口操作、任务栏与菜单设置、文件与文件夹管理等内容&#xff0c;适合高校或职业院校作为课堂演示、课前预习或课后练习的配套材…

作者头像 李华
网站建设 2026/9/20 10:00:30

C盘爆满怎么办?软件搬家全攻略与盘符迁移实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 10:00:21

Transformer多模态异常检测:工业场景下的可解释对齐与鲁棒判别

简介&#xff1a;本资源是一套基于Transformer架构的多模态异常检测完整实践方案&#xff0c;面向具备Python与深度学习基础的算法工程师、研究生及进阶学习者&#xff0c;聚焦工业监控、系统运维等场景下的跨模态异常识别问题。压缩包共314个文件&#xff0c;含164个npy格式多…

作者头像 李华
网站建设 2026/9/20 9:59:29

从像素跳动到稳定动画:Manim中离散运动的相位累加器实现

最近在调研数学动画视频软件时&#xff0c;我把一个不起眼但特别影响观感的功能单独拎了出来研究——像素跳动。别看它名字朴素&#xff0c;真要做好&#xff0c;整个动画的质感和节奏感立刻不一样&#xff1b;做不好&#xff0c;再漂亮的公式推导也会被观众当成“屏幕自己在那…

作者头像 李华
网站建设 2026/9/20 9:58:51

Build Tools for Visual Studio 2022:命令行编译环境与v100报错解决

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 9:58:33

OpCore-Simplify:从硬件报告到可启动OpenCore EFI的实战指南

OpCore-Simplify&#xff1a;从硬件报告到可启动OpenCore EFI的实战指南 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify 第一次手工配 OpenCore 的人大…

作者头像 李华