- 编译器
- 图形学
- 编程语言
【免费下载链接】slang
Making it easier to work with shaders
本文以 Slang 编译器仓库中
docs/generated/design/name-resolution/overload-resolution.md的评审与修复记录(remediation report)为核心骨架,完整呈现 Slang 编译器(slang)重载解析(overload resolution)的候选过滤管道、转换成本(conversion cost)排名、部分泛型应用、内置运算符快速路径等核心机制,并逐条还原评审发现的 7 个问题如何被源码证据证实、修复、拒绝或延期。读者将理解:LookupResult中的多候选如何收敛为唯一最优解(或结构化歧义诊断)、convertToBuiltinArithmeticOp快速路径在何种条件下绕过整个重载解析、ConversionCost各档位的真实语义,以及这套"生成文档-评审-修复"工作流的运作规则。
背景:一份设计文档如何与源码对齐
Slang 仓库采用一套半自动化的设计文档生成工作流。docs/generated/design/下的页面由 LLM 依据各文档专属的生成提示(prompt)与通用契约(_common.md)自动生成,随后经历三个阶段:review(评审)→ remediation(修复)→ mark-fresh / mark-remediated(台账刷新)。
- 评审阶段产出评审报告,如 overload-resolution.md.review.md,其中
## Findings表格构成修复阶段的工作队列; - 修复阶段由与生成文档同模型族的 Claude 模型依据 _remediate.md 契约逐条处理:每个 Finding 必须且只能选择
fixed、rejected-bogus、rejected-out-of-scope、deferred、escalated五种动作之一; - 修复报告本身同样带 front-matter(含
actions计数),并记录target_doc_source_commit_before/after,用于台账追踪。
本篇文章的主体,正是该工作流针对重载解析设计文档的一轮完整修复记录:overload-resolution.md.remediation.md。该记录共 7 个 Finding:5 个fixed、1 个rejected-out-of-scope、1 个deferred。表面看这是一份"元文档",但其每条 Finding 都锚定在 Slang 编译器真实的语义检查源码上,逐条阅读即可还原出重载解析机制的精确行为。
修复记录总览:7 个 Finding 的去向
| Finding ID | 动作 | 涉及机制 | 源码证据 |
|---|---|---|---|
| F-001 | fixed | 内置运算符快速路径的绕过范围 | slang-check-expr.cpp |
| F-002 | fixed | 候选来源的辅助函数职责划分 | slang-check-overload.cpp |
| F-003 | fixed | kConversionCost_RankPromotion语义 | slang-ast-support-types.h |
| F-004 | fixed | 部分泛型应用的唯一闭合路径 | slang-check-overload.cpp |
| F-005 | fixed | 删除不被支持的边缘场景条目 | slang-check-overload.cpp |
| F-006 | rejected-out-of-scope | watched_paths_digest刷新权限 | _remediate.md |
| F-007 | deferred | core.meta.slang未纳入 watched_paths | manifest.yaml |
下文按机制主题组织,而非按 Finding 序号——因为每个 Finding 本质上都在修正一个可验证的编译器行为事实。
一、内置运算符快速路径:绕过重载解析的真实边界(F-001)
目标文档引言最初声称:对数值标量、向量、矩阵的内置运算符会被重写,"永远不进入重载解析"。评审(F-001)指出该表述过度绝对:convertToBuiltinArithmeticOp对若干情形显式返回 null,随后这些操作数会恢复走普通重载解析路径。
修复后的文档将该"绕过"限定为:convertToBuiltinArithmeticOp接受的操作数形态(numeric scalar / vector / matrix 上的算术、比较、位运算、移位、一元运算),并点名了被拒绝、回落到通用路径的几种情形。源码印证了这一点:
- GLSL 作用域下的矩阵运算符与向量相等比较:在 slang-check-expr.cpp 的一元分支中,
isGLSLOperatorScope() && as<MatrixExpressionType>(uOperandType)时直接返回nullptr;二元分支同理。原因是glsl模块拥有矩阵运算符语义——其operator*重载使mat * mat成为代数矩阵乘积,不能由快速路径统一改写; - 混合类型移位:
a << b保持a的类型、独立转换移位量,这种不对称性无法用公共类型规则建模,因此convertToBuiltinArithmeticOp对 mixed-type shift 返回 null(对应 slang-check-expr.cpp 附近的回落逻辑)。
快速路径的运作方式:SemanticsExprVisitor::visitInvokeExpr在收集任何候选之前先尝试两次重写——先convertToLogicOperatorExpr处理短路&&/||,再convertToBuiltinArithmeticOp。成功后表达式被替换为完全检查过的BuiltinOperatorExpr(节点定义于 slang-ast-expr.h),携带一个BuiltinOperationKind和 1 或 2 个操作数,重载解析完全不参与。
getBuiltinOperationKindFromString(opText, arity)(slang-ast-support-types.h)在创建节点时一次性把运算符名字符串映射为 kind,arity参数区分一元-(Neg)与二元-(Sub);后续所有消费者——常量折叠(BuiltinOperationIntVal)、IR 降低、for 循环 trip-count 推断——都直接读 kind,不再重新解析名字。
快速路径还负责两类诊断而非回落的情形:浮点操作数上的位运算/移位,以及浮点操作数上的一元~,都会直接报Diagnostics::BitwiseOperatorRequiresIntegerOperands(slang-diagnostics.lua)——否则用户会看到令人困惑的 "no overload foroperator~"。
一个值得注意的细节:运算符解析缓存已不存在。旧版本曾在TypeCheckingCache::resolvedOperatorOverloadCache中按操作数类型记忆化重载结果,该缓存已被移除;TypeCheckingCache现在只持有conversionCostCache(一个Dictionary<BasicTypeKeyPair, ConversionCost>,由canCoerce查询填充)。快速路径取代了缓存:与其记住"某对操作数类型的答案",不如让常见情形根本不提问。ResolvedOperatorOverload结构体(slang-check-impl.h)是删除缓存的遗留物,全source/已无引用,其注释仍在描述已删除的缓存,不应被当作当前行为文档。
二、候选来源:每个辅助函数的真实职责(F-002)
评审发现文档混淆了两个候选来源函数的行为。修复后的描述与源码一致:
AddDeclRefOverloadCandidates(slang-check-overload.cpp)是"逐条分派器":接收单个LookupResultItem,按被命名的声明种类分派——函数别名、可调用对象、聚合类型、泛型、typedef、泛型类型参数、函数值参数等;AddOverloadCandidates(slang-check-overload.cpp)才是"结果迭代器":遍历整个LookupResult,对每个条目调用上述分派器。
函数值(first-class function value)候选的细节也被纠正:
AddFuncOverloadCandidate(FuncType*, ..., baseCost)(slang-check-overload.cpp)设置funcType、flavor = Expr,但不保留表达式;- 兄弟函数
AddFuncExprOverloadCandidate(slang-check-overload.cpp)额外把实际的函数值表达式存入exprVal; - 函数类型参数
ParamDecl走的是后者(slang-check-overload.cpp),且仅当其声明类型是FuncType时才可达——源码拼写为functype,例如 hlsl.meta.slang 中的Reduce(functype(T, T) -> T combineOp)参数。
其他候选来源家族包括:AddCtorOverloadCandidate(slang-check-overload.cpp,处理经ConstructorDecl的调用,结果Type被传入以便构造构造器调用表达式)、AddHigherOrderOverloadCandidate(slang-check-overload.cpp,处理__fwd_diff、__bwd_diff这类包装 callee 的算子)。每个辅助函数都接受一个baseCost,由AddOverloadCandidate累加到候选的conversionCostSum;普通查找、函数值、高阶入口均传kConversionCost_None(slang-check-overload.cpp),当前唯一非平凡baseCost是inferGenericArguments为推断出的泛型实参报告的成本,它被转发给特化候选(slang-check-overload.cpp)。
三、候选结构:OverloadCandidate 与失败记录
OverloadCandidate(slang-check-impl.h)是解析器正在评估的单个候选,关键字段包括:
Flavor flavor:Func、Generic、UnspecializedGeneric、Expr之一;Status status:管道进度,取值GenericArgumentInferenceFailed、Unchecked、ArityChecked、FixityChecked、TypeChecked、DirectionChecked、VisibilityChecked、Applicable;Flags flags:目前仅IsPartiallyAppliedGeneric = 1 << 0;LookupResultItem item:查找返回的DeclRef+ breadcrumb 链;exprVal(仅Flavor::Expr,如作为实参传入的函数值)、funcType、resultType;conversionCostSum:由TryCheckOverloadCandidateTypes累加的逐实参隐式转换成本;subst:推断出的替换,泛型候选借此避免在CompleteOverloadCandidate中重跑推断;explicitGenericArgCount(slang-check-impl.h):调用方显式提供的前导普通泛型实参数(其余由参数默认值补齐);TryCheckGenericOverloadCandidateTypes记录该边界,初始为-1(尚未计算),TryCheckOverloadCandidateConstraints将该前缀交给泛型约束求解器,使默认值与 witness 实参由求解器的 fixpoint 解析而非线性遍历;argMismatchArgIndex/argMismatchExpectedType/argMismatchActualType(slang-check-impl.h):首个类型检查失败的实参,供 "no applicable overload" 诊断指名出错实参与双方类型。
GenericArgumentInferenceFailure(slang-check-impl.h)是记录"泛型实参推断为何失败"的带标签联合体,Kind取None、VariadicPackCountMismatch、GenericArityMismatch、OrdinaryGenericParamNotInferred、InterfaceConformanceNotSatisfied、GenericConstraintNotSatisfied、GenericParamUnificationConflict。格式化的责任被刻意推迟到CompleteOverloadCandidate,使被探测后丢弃的候选永不为此付费。每个 payload 必须可平凡拷贝且默认构造memset整个对象——因为OverloadCandidate值会在标准库算法中被拷贝,按kind切换的拷贝会触发 GCC 的-Werror=maybe-uninitialized(slang-check-impl.h)。
四、转换成本:RankPromotion 的真实语义(F-003)
ConversionCost定义为unsigned int(slang-ast-support-types.h),具体档位以kConversionCost_*枚举给出。评审发现文档把kConversionCost_RankPromotion解释为"保持秩的数值提升"(rank-preserving),而源码注释(slang-ast-support-types.h)把它归入"无损且保持值 'kind' 不变"的转换类别,并未声称保持秩。修复后的语义为:"在同一转换 kind 内的无损高秩提升"(lossless promotion to a higher rank within the same conversion kind)。
几个关键阈值与语义(按源码声明顺序,数值以当前仓库为准):
| 常量 | 数值 | 语义 |
|---|---|---|
kConversionCost_None | 0 | 恒等 |
kConversionCost_GenericParamUpcast/kConversionCost_LambdaToFunc | 1 | 经泛型参数上转型 / lambda 用作Func值 |
kConversionCost_MatrixLayout | 5 | 元素类型、行列数一致但布局不同的 matrix 转换 |
kConversionCost_GetRef | 5 | 从左值源产生Ref<T>结果;源非左值时拒绝 |
kConversionCost_ImplicitDereference | 10 | 解引用指针类值 |
kConversionCost_BoolToInt | 120 | bool→ int,刻意便宜以打破歧义 |
kConversionCost_RankPromotion | 150 | 同一转换 kind 内的无损高秩提升 |
kConversionCost_NoneToOptional/ValToOptional/NullPtrToPtr/PtrToVoidPtr | 150 | 各类 optional / 空指针 / void 指针转换 |
kConversionCost_FailedOptionalConstraint | 150 | 每个求解 witness 为NoneWitness的泛型约束加价(slang-check-constraint.cpp),候选仍可行、仅排名更差 |
kConversionCost_UnsignedToSignedPromotion | 200 | 无符号提升到更宽有符号 |
kConversionCost_SignedToUnsignedConversion | 250 | 有符号 → 同宽/更宽无符号 |
kConversionCost_SameSizeUnsignedToSignedConversion | 300 | 同宽无符号 → 有符号 |
kConversionCost_IntegerToFloatConversion/PtrToBool | 400 | int → float / 指针 → bool |
kConversionCost_IntegerTruncate | 450 | int → 更窄 int |
kConversionCost_IntegerToHalfConversion/ParameterPack/Default | 500 | int → half / 绑定参数包 / 用户定义转换默认值 |
kConversionCost_GeneralConversion | 900 | 隐式转换上限:canConvertImplicitly拒绝 ≥ 此值(slang-check-conversion.cpp) |
kConversionCost_Explicit | 90000 | 仅显式强转,绝不隐式接受 |
kConversionCost_LValueCast | 800 | 源为左值时的加价(slang-check-conversion.cpp),使接受实参自身类型的重载胜过需转换的重载 |
kConversionCost_Impossible | 0xFFFFFFFF | "不存在转换";候选在被排名前就已拒绝,永不参与求和 |
文档还专门标注了两个已声明但从未被使用的档位:kConversionCost_MutablePtrToConstPtr(20)与kConversionCost_ScalarToCoopVector(1)——树中除定义外无任何引用,因此Ptr<int>到Ptr<int, Access::Read>根本不是隐式转换,而是被建议为显式强转的类型不匹配。
几种没有独立成本档位的转换形态同样影响候选胜出:
Optional<T>→Optional<U>协变:内层类型不同时,_coerce递归探测T→U并报告innerCost + 1(slang-check-conversion.cpp),+1保证精确匹配(成本 0)严格更便宜,同时让协变形式排在直接非Optional转换之下;- 参数组自动解引用:
ConstantBuffer<X>/ParameterBlock<X>源先隐式解引用到X再继续转换(slang-check-conversion.cpp),收取kConversionCost_ImplicitDereference(10);源码中留有TODO(slang-check-conversion.cpp)记录后果:该分支把所有参数组强转都汇入解引用路径,接受参数组类型的用户自定义初始化器(DescriptorHandle情形)永远不会被转换搜索考虑; - 被刻意拒绝的初始化器形态:
float4(f2, f)过去会因廉价的标量到向量提升(kConversionCost_ScalarToVector)而解析为把标量f提升成float2并静默复制。修复不是改成本,而是显式声明:vector<T,4>在 core.meta.slang 中直接声明(vector<T,2>, T)与(T, vector<T,2>)形态并标记[deprecated](Slang ≤ 2025)与[RemovedSince(2026, ...)]。默认语言版本早于 2026,因此今天写float4(f2, f)仍解析到其中一者、获得旧的复制语义并报告弃用警告;以 2026 或更高语言版本编译则因[RemovedSince]变成错误。
最后,_coerce中还有一处纯粹的性能守卫(slang-check-conversion.cpp):在非显式CoercionSite,当toType是非bool的标量BasicExpressionType且fromType是引用GenericTypeParamDeclBase的 decl-ref 时,_coerce立即返回失败,避免对每个被拒候选都跑一次完整递归搜索(AddTypeOverloadCandidates,slang-check-conversion.cpp)。bool被排除是因为core.meta.slang确实声明了__init<T : __EnumType>(T);DeclRefType标量如BFloat16因声明了泛型初始化器而留在搜索路径上。该守卫只改变耗时、不改变解析结果。
五、部分泛型应用:唯一的闭合路径(F-004)
目标文档最初推测:显式类型标注(type ascription)或后续的GenericAppExpr<...>/ 另一个实参可以"闭合"PartiallyAppliedGenericExpr。评审核实后确认:语义层只有一条闭合路径——把该部分应用作为后续调用的 callee。
- 当调用实参只固定泛型的一部分参数时,
TryCheckGenericOverloadCandidateTypes(slang-check-overload.cpp)可能产出部分特化候选,并在四处设置IsPartiallyAppliedGeneric标志(slang-check-overload.cpp); CompleteOverloadCandidate在标志置位时把结果包进PartiallyAppliedGenericExpr(slang-check-overload.cpp);PartiallyAppliedGenericExpr携带baseGenericDeclRef与已提供的普通实参前缀providedOrdinaryArgs;witness 实参刻意不存在节点上,待剩余普通实参推断后再形成(slang-ast-expr.h);- 唯一补全剩余空位的路径:
AddOverloadCandidates识别PartiallyAppliedGenericExpr,把baseGenericDeclRef+providedOrdinaryArgs交给addOverloadCandidatesForCallToGeneric,由调用点推断一次性解出剩余普通实参与全部 witness 实参(slang-check-overload.cpp); - slang-check-expr.cpp 对部分应用的
GetBaseExpr只是解包 base,不构成第二条推断路径。
修复还补充了约束步骤对标志的短路处理:TryCheckOverloadCandidateConstraints在标志置位时提前返回(slang-check-overload.cpp),因为后续的重载解析轮次(把部分应用作用于实际实参)才会提供约束检查所需的信息。
六、被删除的边缘场景:为什么"函数值 vs 声明的可调用对象"站不住(F-005)
文档原有一条"边缘场景与失败模式"条目,声称:一等函数值与声明的可调用对象平票时,因声明在作用域上更近而胜出。评审(F-005)证实该断言不被源码支持,修复动作是直接删除该条目。证据链如下:
AddFuncExprOverloadCandidate(slang-check-overload.cpp)创建的真正表达式候选从不设置candidate.item;- 而
CompareLookupResultItems(slang-check-overload.cpp)解引用left.declRef.getDecl(),CompareOverloadCandidates(slang-check-overload.cpp)按left->item.declRef排名——两者都要求候选携带 decl-ref; - 文档专属提示要求的边缘场景清单(name-resolution-overload-resolution.md)并不包含该情形。
这展示了修复工作的纪律:不能为了凑满"边缘场景"清单而保留一个无源码依据的行为断言。删除而非"修补",因为真正的问题是没有可达的产出路径能让这两类候选同时进入 decl-ref 比较器。
七、工作流边界:两个非 fix 动作的规则含义
F-006:digest 刷新被拒绝(rejected-out-of-scope)
评审发现目标文档 front-matter 的watched_paths_digest与 resolve 出的 watched 文件集哈希不一致。修复动作是rejected-out-of-scope:_remediate.md 明确保留watched_paths_digest供操作员在mark-fresh运行时刷新,并禁止修复者编辑它。这保证了"文档内容由模型修、台账指纹由工具管"的职责分离。
F-007:core.meta.slang未纳入 watched_paths(deferred)
文档在"转换成本"一节对core.meta.slang中的构造器与bool初始化器做了详细行为断言(如vector<T,4>的(vector<T,2>, T)形态、__init<T : __EnumType>(T)),但该文件不在本页面的 watched_paths 中,因此这些声明的变更无法使页面过期,这违反了 _common.md 的家族范围规则。评审认为 Finding 正确、且在范围内,但正确的补救方式是watched_paths扩展——而 _remediate.md 禁止修复者修改清单,L72-L78 又把清单扩展列为"原型性延期"。因此动作是deferred:后续需把source/slang/core.meta.slang加入本页watched_paths,再链接被引用的vector<T,4>与__init<T : __EnumType>声明;直接删除这些断言反而会丢掉提示词明确要求的准确成本模型内容。
八、从修复记录反观完整算法:探测与定稿两阶段
把五个fixed的修正放回原位,即可拼出重载解析的完整图景(详见 overload-resolution.md)。
探测阶段:TryCheckOverloadCandidate
SemanticsVisitor::TryCheckOverloadCandidate(slang-check-overload.cpp)逐步推进candidate.status,任一步失败即返回。步骤顺序:TryCheckOverloadCandidateArity(L145,校验实参数在required与allowed之间,allowed == -1表示不限)→TryCheckOverloadCandidateFixity(L225,仅对PrefixExpr/PostfixExpr生效,阻止-x绑定到仅后缀的operator-)→TryCheckOverloadCandidateTypes(L807,含泛型推断;每实参调canCoerce并累加成本,disallowNestedConversions时要求精确类型相等)→TryCheckOverloadCandidateDirections(L1083,仅检查隐式this的可变性,[mutating]方法作用于不可变基表达式在此被拒)→TryCheckOverloadCandidateConstraints(L1157,对最外层泛型经求解器trySolveGenericArguments解默认值与 witness;solveCost刻意不并入conversionCostSum,以免偏移排名、破坏本应保持的歧义)→TryCheckOverloadCandidateVisibility(L265-L287)。TryCheckOverloadCandidateClassNewMatchUp(L107-L143)不在探测阶段运行,只在定稿阶段把关new与class的配对。
AddOverloadCandidateInner(slang-check-overload.cpp)维护运行中的胜者:新候选严格胜过已有条目则移除后者、被严格胜过则丢弃自己(调试断言保证"优于"关系传递)、平票则扩展为bestCandidates列表。无论候选是否 applicable 都会参与:无更优者时非适用候选也会被保留,这正是失败路径能报告"最不坏"候选而非裸 "no overload" 的原因。
定稿阶段:CompleteOverloadCandidate
探测阶段恰好得到一个bestCandidate后,CompleteOverloadCandidate(slang-check-overload.cpp)把context.mode翻转为ForReal,从TryCheckOverloadCandidateClassNewMatchUp开始按顺序重跑全管道(L1632-L1648),首次失败即跳转共享错误标签。ForReal重检产生用户可见的诊断;若最佳候选的Status是GenericArgumentInferenceFailed,顶部 switch(L1510-L1610)把记录的genericInferenceFailure映射为聚焦诊断(见下表),仅Kind::None落到笼统的GenericArgumentInferenceFailed(slang-diagnostics.lua)。
GenericArgumentInferenceFailure::Kind | 诊断 |
|---|---|
VariadicPackCountMismatch | VariadicPackCountDoesNotMatch—— 包实参元素数不匹配 |
GenericArityMismatch | GenericSpecializationArityMismatch—— 泛型调用实参个数错误 |
OrdinaryGenericParamNotInferred | GenericParameterCouldNotBeInferred—— 指名未能确定的参数 |
GenericConstraintNotSatisfied | GenericArgumentDoesNotSatisfyConstraint+ 见约束声明注释;覆盖相等(where T == X)与非空包(where nonempty(P)),不含类型强转约束 |
GenericParamUnificationConflict | GenericParameterUnificationConflict—— 报告两个冲突的推断 |
InterfaceConformanceNotSatisfied | TypeArgumentDoesNotConformToInterface |
None | 兜底GenericArgumentInferenceFailed—— "could not specialize generic for arguments of type ..." |
每个分支额外发一条GenericSignatureTried注释(经ASTPrinter::getDeclSignatureString渲染);格式化只在选定候选的路径上发生,被探测后丢弃的候选永不付费。
ForReal成功后构造最终 AST 节点:Flavor::Func/Generic先经ConstructLookupResultExpr(L1656)重放 breadcrumb 链重建 callee;Flavor::Generic在标志置位时包PartiallyAppliedGenericExpr,否则用createGenericDeclRef建完全特化 decl-ref;Flavor::Expr直接用candidate.exprVal作 callee(无LookupResultItem,跳过ConstructLookupResultExpr)。
平票比较器:CompareOverloadCandidates
slang-check-overload.cpp 的比较器,步骤 1 适用于任意对,步骤 2-8 仅在双方Status::Applicable时运行:
- Status 差异:更高的 Status 胜出;
- 转换成本总和:更低
conversionCostSum胜出(源码带TODO,将来应细化为逐实参测试); CompareLookupResultItems(L1915):按声明"所在"种类比较——具体成员胜过其满足的接口需求、非extern胜过extern、非扩展胜过扩展成员(普通扩展胜过自由形式扩展,后者指目标类型为泛型类型参数的extension<T : IFoo> T)、任意声明胜过模块声明、双方均为接口需求时更派生的接口胜;任一方是泛型可调用对象时直接返回相等。这一步正是折叠 lookup 刻意不去重的多个LookupResult条目的地方;- 隐式转换偏好:恰一方带
ImplicitConversionModifier时该方胜出; compareOverloadCandidateSpecificity(L2141):比较getSpecializedParamCount,更小的计数胜——非泛型候选胜过特化泛型候选;变参与默认参数计数不参与,函数上方的长注释描述了一种未实现的更一般规则,且其简化方向写反了;getExportRank(L2191):注释声称export优先,实际实现只在左候选带ExternModifier、右候选带HLSLExportModifier时返回 -1(读作"偏好左候选",即extern胜出),其余组合返回 0;- 作用域距离:非泛型 flavor 下
getScopeRank偏好更近的声明;双方任一为Generic/UnspecializedGeneric时跳过,因为第一遍泛型候选过滤按泛型参数形状匹配而非真实适用性,按作用域排名会在第二遍收窄前选错; getOverloadRank:由[OverloadRank(N)]属性携带(slang-check-overload.cpp),无属性者为 0,更高者胜。该属性在 core.meta.slang 中声明为@internal,作为打破核心模块重载歧义的权宜之计,全树仅core.meta.slang/hlsl.meta.slang使用。修复报告特别注明:这个@internal是文档注释约定而非强制门禁——slang-check-modifier.cpp 的核心模块专属检查只覆盖MagicTypeModifier、BuiltinTypeModifier、BuiltinRequirementModifier,不含OverloadRankAttribute,所以普通.slang文件里写[OverloadRank(N)]会被接受并真的打破歧义——应视为"被容忍而非受支持"。
与查找的关系
查找系统(lookup.md)在构建类型的继承图时按来源对 facet 列表去重,但在LookupResult层不去重:沿多条路径找到的同一名字会产生多个LookupResultItem,折叠它们是本页的职责。调用链为refineLookup→resolveOverloadedLookup→CompareLookupResultItems,前两者位于 slang-check-expr.cpp,后者即比较器步骤 3。一个诊断可见的后果:context.bestCandidates可多次持有同一声明,因此 "no applicable overload" 报告路径在打印注释前按渲染后的签名串去重(而非按Decl*,因为declRef.getDecl()会剥离替换,foo<float>与foo<int>会被错误合并)。
失败模式速查
- 两个同等
Applicable候选:比较器全零 → 追加到bestCandidates→AmbiguousOverloadForNameWithArgs(slang-diagnostics.lua),每个平票候选作为Candidate变参注释列出,上限 10 条、余者以MoreOverloadCandidates收尾; - 无
Applicable候选:单个最佳候选时经CompleteOverloadCandidate重跑以给出最具体诊断;多个平票时直接发NoApplicableOverloadForNameWithArgs(slang-check-overload.cpp); - 实参错在何处:每个候选打印
OverloadCandidate注释,类型检查步骤记录过不匹配的候选追加OverloadCandidateArgumentTypeMismatch("argument N does not match: expected 'X', got 'Y'",slang-diagnostics.lua); - 被可见性隐藏的候选:
JustTrying静默丢弃,ForReal发DeclIsNotVisible;无候选适用且有不可见候选时,以InvisibleOverloadCandidate点名(slang-check-overload.cpp); - 转换链:探测阶段无逐实参成本上限,
canCoerce报"无转换"才丢弃候选;堆叠两次用户定义转换由disallowNestedConversions单独阻止; [NoDiscard]不影响解析:由maybeDiagnoseDiscardedNoDiscardResult(slang-check-impl.h)在调用解析完成后检查,不改变候选可行性、不贡献成本。
结语:一份修复记录的价值
这篇修复记录的价值远超"改了几个文档句子":它以 5 个被源码证实并修正的事实、1 个被工作流规则拒绝的越权操作、1 个被诚实延期的范围问题,勾勒出 Slang 重载解析机制的精确行为边界——快速路径绕过哪些形态、哪些形态必须回落到通用路径、成本模型里每个档位的真实含义、部分泛型应用唯一合法的闭合方式、以及平票比较器各步骤的取舍。对想修改重载解析逻辑、新增候选 flavor 或过滤步骤、或排查 "ambiguous call" / "no applicable overload" 诊断的开发者而言,这份记录连同其背后的 目标设计文档、评审报告 与 工作流契约,构成了一份可以从"断言"追溯到"源码行号"的完整技术档案。
- 编译器
- 图形学
- 编程语言
【免费下载链接】slang
Making it easier to work with shaders
相关推荐
Slang 编译器 Code Emission 生成文档的审查与修复:AI 文档质量保障机制解析
Slang 编译器 Code Emission 生成文档的审查与修复:AI 文档质量保障机制解析 导读 Slang 编译器在 docs/generated/de
编译器图形学编程语言Slang 编译器 Targets、Capabilities 与 Profiles 设计文档审查报告深度解读:五处源头对齐问题的技术修正
Slang 编译器 Targets、Capabilities 与 Profiles 设计文档审查报告深度解读:五处源头对齐问题的技术修正 本篇技术指南围绕 Sl
编译器图形学编程语言Slang 编译器 AST-to-IR Lowering 全解析:文档缺口修复与源码级验证实录
Slang 编译器 AST to IR Lowering 全解析:文档缺口修复与源码级验证实录 AST to IR Lowering(AST 到中间表示的下译)
编译器图形学编程语言
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考