尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Slang 名称解析边界辨析:从修复报告到 `Scope` 链、可见性与重载解析的实现细节

Slang 名称解析边界辨析:从修复报告到 `Scope` 链、可见性与重载解析的实现细节 Slang 名称解析边界辨析从修复报告到Scope链、可见性与重载解析的实现细节【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang导读本文以 Slang 编译器仓库中docs/generated/design/name-resolution/index.md的修复报告remediation report为切入点围绕该报告确认并修复的三处文档事实偏差系统梳理 Slang 名称解析name resolution四页设计文档的核心脉络Scope链的构造与兄弟作用域接线、LookupResult的生成与去重边界、public/internal/private可见性过滤、以及重载解析对候选集的收敛。读者完成后将能理解为什么foo解析到这个 decl 而不是那个并掌握decl-is-not-visible、ambiguous reference、no applicable overload等诊断背后的实现机制与对应源码位置。修复报告概述三处事实性偏差的确认与修复docs/generated/design/_meta/remediations/name-resolution/index.md.remediation.md是一份由 AI 修复模型claude-opus-5在提交53b76e6d3009b8e6434d41573524c7ce5c499d23上生成的审查修复报告。报告对docs/generated/design/name-resolution/index.md的三项发现F-001、F-002、F-003逐条核实并全部修复结论为 3 fixed、0 rejected、0 deferred、0 escalatedFinding IDAction主题修复要点F-001fixed兄弟作用域sibling-scope归属导入模块的作用域拼接并非由ScopesWired阶段的SemanticsDeclScopeWiringVisitor完成而是由SemanticsDeclHeaderVisitor::visitImportDecl在SignatureChecked阶段完成F-002fixed去重deduplication表述CompareLookupResultItems只丢弃严格更差的路径、保留比较相等的项原文档narrowing 链移除所有重复项的表述被改写F-003fixed导读读者定位按照docs/generated/design/_meta/prompts/_common.md的要求首段需同时声明覆盖范围与目标读者这三项修复集中指向index.md中标题为Where the boundaries blur边界模糊之处的核心章节。下文以此章节为主线逐条展开其背后的机制并补充四个子页面scopes.md、lookup.md、visibility.md、overload-resolution.md与源码层的细节。边界一可见性被检查两次index.md明确指出可见性过滤并非一次性行为而是在解析流程的两个独立位置各执行一次。查找结果边界lookup boundary大多数 checker 中的查找调用点会把结果交给filterLookupResultByVisibilityAndDiagnoseslang-check-expr.cpp 中约 1291-1301 行其内部委托filterLookupResultByVisibility1278-1289 行。若查找返回了候选但全部被过滤掉则发出decl-is-not-visible诊断Diagnostics::DeclIsNotVisibleslang-diagnostics.lua30600并报告第一个被移除的 decl语言服务器language-server模式下则有意返回未过滤的结果保证补全功能继续工作。重载解析边界TryCheckOverloadCandidateVisibilityslang-check-overload.cpp 约 265-287 行由重载过滤流水线对每个通过 arity/type 检查的候选调用。在JustTrying模式下静默丢弃候选在ForReal模式下发出同样的DeclIsNotVisible诊断。两处最终都委托给同一个谓词SemanticsVisitor::isDeclVisibleFromScope(declRef, scope)slang-check-expr.cpp 约 1148-1264 行其分发逻辑为Public总是可见Internal当且仅当getModuleDecl(decl)与getModuleDecl(scope)相等即请求方与声明位于同一模块Private当且仅当请求方作用域的某个父级是拥有该 decl 的聚合类型aggregate或请求方位于一个目标类型匹配的ExtensionDecl内其他值理论上不可能的Default不可见。私有访问检查会沿请求方作用域的 parent 链查找父聚合约 1164 行当候选 decl 位于ExtensionDecl中时谓词还会解析扩展的目标类型并与请求方作用域的包围聚合做类型相等比较1191-1261 行通过局部ContainerTargetTypeResolver。这就是extension S { private foo() {...} }可以从S内部或S上的另一个扩展甚至一个特化为S的泛型扩展中调用的原因。需要注意的是该特化对齐步骤以候选自身的容器是ExtensionDecl为条件1259 行因此反方向不成立泛型类型GT主体内声明的private成员不会被对齐extension Gint无法访问它。边界二去重是相互独立的两层index.md修正后的表述指出去重并非单一机制而是分布在两个互不相同的层面。第一层facet 列表按来源去重。成员查找依赖的继承信息是线性化的Facet/FacetList/InheritanceInfo声明于 slang-check-impl.h 约 526-763 行。每个 facet 是(kind, directness, origin, subtypeWitness, declRefForMemberLookup)记录列表在构造时通过originsMatch/FacetList::containsMatchForslang-check-inheritance.cpp 约 1890-1976 行按 origin 去重——因此经由多条继承路径到达的同一个基类只贡献一个 facet其成员只出现一次。第二层LookupResult层面不去重。AddToLookupResultslang-lookup.cpp 约 95-113 行只是把每个传入的LookupResultItem追加到结果中从不与既有项比较。于是同一个 decl 沿两条路径被找到时会得到两个 breadcrumb 链不同的项。真正修剪它们的是refineLookup→resolveOverloadedLookup→CompareLookupResultItems这一链式流程且该流程横跨 lookup 与 overload-resolution 两页。F-002 修复所确认的正是CompareLookupResultItems的精确语义代码slang-check-expr.cpp 约 1436-1454 行只在比较结果为负时移除既有项、只在结果为正时跳过新项因此比较相等的项全部保留留给重载解析去排序或在无法排序时触发Diagnostics::AmbiguousReference由SemanticsVisitor::diagnoseAmbiguousReference在 slang-check-expr.cpp 约 1509 行发出。CompareLookupResultItems本身位于 slang-check-overload.cpp 约 1915 行它是重载比较器CompareOverloadCandidates的第三步约 2331 行调用偏好具体成员胜过其满足的接口需求、非externdecl 胜过extern、非扩展成员胜过扩展成员等。保留所有 breadcrumb 路径是后续阶段能给出精确歧义诊断的前提——这是index.md刻意强调的设计取舍。边界三内建运算符快速路径完全绕过重载解析检查一个InvokeExpr时SemanticsExprVisitor::visitInvokeExpr在收集任何候选之前先尝试两次重写先convertToLogicOperatorExpr处理短路/||再convertToBuiltinArithmeticOp。后者成功时表达式被替换为一个完全检查过的BuiltinOperatorExpr从不收集候选集。BuiltinOperatorExprslang-ast-expr.h 约 289 行只携带两样数据BuiltinOperationKind op和 1-2 个操作数。运算符名字到 kind 的映射在创建时恰好执行一次由getBuiltinOperationKindFromString(opText, arity)slang-ast-support-types.h 约 1957 行完成arity参数用来区分前缀-Neg与二元-Sub。后续所有消费者——常量折叠、IR 下降、for 循环趟数推断——都直接读 kind不再解析名字。快速路径会有意拒绝一部分内建操作数形态让它们回到通用路径短路/||已由convertToLogicOperatorExpr处理用户自定义操作数类型以及不满足广播兼容的混合形态混合类型移位操作数a b保留a的类型并独立转换移位量普通类型规则无法建模这种不对称性仅在 GLSL 运算符作用域内矩阵操作数与向量相等比较/!。isGLSLOperatorScope在设置-allow-glsl或glsl模块在作用域内时为真因为glsl模块的operator*重载使mat * mat成为代数矩阵乘法。这些被拒绝的形态连同用户自定义运算符一起走operator OP通用路径详见下节。两种操作数形态会被快速路径直接诊断而非交回对浮点操作数使用位运算/移位以及对浮点操作数使用一元~都产生Diagnostics::BitwiseOperatorRequiresIntegerOperandsslang-diagnostics.lua 约 3974 行——放任不管会得到令人困惑的 no overload foroperator~。注意该快速路径是替代了历史上存在过的运算符重载解析缓存。TypeCheckingCacheslang-check-impl.h 约 481 行现在只保留conversionCostCacheResolvedOperatorOverload结构体466-479 行虽仍在头文件中声明但整个source/已无任何引用其注释描述的还是已删除的缓存不应作为当前行为的文档来读。边界四兄弟作用域在独立的检查阶段接线index.md修正后的表述F-001 修复的落点澄清并非所有兄弟作用域都由解析器构建。命名空间与using声明在 decl 被驱动到DeclCheckState::ScopesWired时获得其nextSibling链接——该状态位于修饰符检查与签名检查之间见 slang-ast-support-types.h 约 493-506 行的状态枚举。接线工作由专门的SemanticsDeclScopeWiringVisitor完成visitNamespaceDeclslang-check-decl.cpp 约 17400 行在存在多个同名NamespaceDecl时调用addSiblingScopeForContainerDecl17427 行visitUsingDecl约 17336 行检查using参数对每个命名空间类NamespaceDeclBase目标调用addSiblingScopeForContainerDecl17366 行。而导入模块的拼接发生在更晚的阶段SemanticsDeclHeaderVisitor::visitImportDeclslang-check-decl.cpp 约 17806 行起在DeclCheckState::SignatureChecked调用importModuleIntoScope约 17839 行函数定义自 17752 行。这正是 F-001 修复所确认的事实旧文档把导入模块拼接错误地归给了ScopesWired而实际上ScopesWired映射到SemanticsDeclScopeWiringVisitor、SignatureChecked映射到SemanticsDeclHeaderVisitor。addSiblingScopeForContainerDecl是自由函数定义于 slang-check-expr.cpp 约 325-331 行分配新Scope、指向次要ContainerDecl、把它拼接到目标作用域的既有nextSibling链中。importModuleIntoScope的循环被isOwnModuleOrIncludedFileScope约 334 行调用点 slang-check-decl.cpp 约 17077 行过滤只放行模块自身的作用域与parentDecl是该模块的FileDecl即__include拉入的文件。若没有该过滤A 主文件中写的using namespace Foo;或 A 仅import的模块 C 会出现在 A 的nextSibling链上并泄漏进 B——使普通import静默变成传递性的。__exported import C;则是让importModuleIntoScope递归进入携带ExportedModifier的嵌套 import约 17096-17103 行从而显式再导出。接线在模块级以ensureAllDeclsRec按状态依次推进slang-check-decl.cpp 约 5244-5321 行因此任何签名被检查之前接线已全部完成。这是用户可见的有意排序namespace NS { struct Widget { int v; }; } Widget makeW(int n) { Widget w; w.v n; return w; } // 可解析 using namespace NS; // using 声明可以出现在依赖它的声明之后边界五解析器发起的查找弱于检查器发起的查找解析是两阶段的函数体被捕获为 token 区间并在稍后重新解析。当request.semantics为 null尚无SemanticsVisitor时查找行为在两处发生变化_lookUpMembersInSuperTypeDeclImplslang-lookup.cpp 约 513-576 行在语义上下文缺失时只对AggTypeDeclBase做直接成员查找并返回——不咨询基类也不咨询扩展约 532-549 行pack 类型各分支EachType、FirstPackElementType等约 625-686 行在request.semantics为 null 时直接提前返回因为计算InheritanceInfo需要共享语义上下文。这就是index.md所谓lookup from the parser is weaker than lookup from the checker的准确含义。UnparsedStmt创建于 slang-parser.cpp 约 2254 行在声明阶段捕获currentScope与outerScope两个指针slang-ast-stmt.h 约 53-61 行parseUnparsedStmt约 9976 行随后精确恢复这两个指针并以ParsingStage::Body重新进入保证延迟解析的函数体针对的是其声明处的存活作用域链。支撑机制一Scope链与解析器如何构造它Scope是驱动一切的三字段记录slang-ast-base.h 约 113-128 行ContainerDecl* containerDecl121 行——其成员构成此作用域内容的 declScope* parent124 行——在此作用域找不到名字时接下来咨询的作用域Scope* nextSibling127 行——在落到parent之前于同一层级咨询的下一个作用域。头文件注释特意注明containerDecl是未拥有的指针因此Scope不会让 AST 节点存活。ContainerDecl携带ownedScope字段其containerDecl指回拥有者——这是 AST 节点拥有作用域的规范方式。解析器携带当前作用域指针作为成员字段slang-parser.cpp 约 121-170 行currentScope新 decl 定义插入处、currentLookupScope解析期表达式查找起点经resetLookupScope与currentScope保持同步、outerScope解析起点初始作用域。三个辅助方法PushScope、pushScopeAndSetParent、PopScope都以此结尾调用resetLookupScope()。pushScopeAndSetParent额外把containerDecl-parentDecl currentScope-containerDecl再压栈是大多数解析代码调用的版本。查找按固定顺序走链见 lookup.md 与 scopes.md先访问currentScope自身的直接成员再沿nextSibling走到 null然后上移到parent重复直到 parent 链到nullptr。_lookUpInScopesslang-lookup.cpp 约 786-1069 行还会跳过containerDecl为 null 的哨兵作用域与重复出现的同一FileDecl819-829 行。值得注意的作用域边界细节来自 scopes.md无作用域语句体的 decl 会泄漏while (c) int x 1;被接受parseVarDeclrStatementslang-parser.cpp 约 7255 行把声明交给currentScope-containerDecl7260 行即未压栈时的外层容器x在循环后仍可见只有{ ... }体能隔离它。两种 switch 形式不同普通switch整个 body 是一个有作用域的BlockStmtParseSwitchStmt6588 行一个case下声明的局部变量在文本上靠后的case中仍可见__target_switch/__stage_switch则围绕每个 case 组压栈/弹栈ScopeDeclparseTargetSwitchStmtImpl6636-6637 与 6702 行一个 case 中声明的局部变量对其他 case 不可见。HLSL 下for用UnscopedForStmtParseForStatement7392 行并跳过pushScopeAndSetParent初始化变量泄漏进外层作用域符合 HLSL 语义。lambda 参数作用域结束于引入它的表达式parseLambdaExpr8441 行在参数表前压入paramScopeDecl8471 行、body 后弹出8492 行。支撑机制二LookupMask、LookupOptions与查找请求查找的四个入口点在 slang-lookup.h 声明lookUp17-25 行非限定查找接受两个bool而非完整LookupOptions、lookUpMember28-35 行对Type*的限定查找接受完整LookupOptions、lookUpDirectAndTransparentMembers38-45 行单容器直接查找永远以LookupOptions::None运行、refineLookup13 行对既有LookupResult按不同LookupMask重新过滤。AddToLookupResult60-61 行以单条和整结果合并两种重载导出供slang-lookup.cpp之外的调用者可见性与 optional 约束过滤使用。LookupMaskslang-ast-support-types.h 约 1308-1316 行是uint8_t位集位值含义type0x1AggTypeDecl/SimpleTypeDeclFunction0x2FunctionDeclBase子类Value0x4非类型、非函数、非属性、非语法、非语义的一切变量、参数、字段……Attribute0x8AttributeDecl即attribute_syntax [name(...)]引入的声明SyntaxDecl0x10关键字引入的SyntaxDeclSemantic0x20SemanticDecl即semantic sv_position { ... }Default0x13type \| Function \| Value \| SyntaxDecl解析器与多数 checker 入口使用Attribute与Semantic是Default之外的两个位只有显式指名它们的调用者才能到达那些 decl——一个拼写得像属性的普通标识符既不隐藏该属性、也不被它隐藏。分类在DeclPassesLookupMaskslang-lookup.cpp 约 41-93 行中完成且该函数的首要工作不是位测试extern相关拒绝43-54 行在任何位被咨询前运行FileDecl被硬编码为永不过79-83 行。LookupOptions约 1320-1334 行除None 0外有六位IgnoreBaseInterfaces跳过继承接口成员、Completion返回容器内所有适用 decl 而非仅同名者语言服务器使用、NoDeref不自动解引用指针类类型、ConsiderAllLocalNamesInScope绕过hiddenFromLookup/ 检查状态遮蔽测试解析期作用域仍在构建时可查找成功、IgnoreInheritance只返回struct直接成员加同类型extension、IgnoreTransparentMembers跳过透明成员注入。LookupRequest约 1563-1582 行是把参数捆成一束的结构semantics、scope、endScope、declToExclude、mask、options。两点值得注意endScope在当前提交下没有任何调用者赋值因此作用域遍历总是跑到 parent 链为 nulldeclToExclude来自SemanticsContext::getDeclToExcludeFromLookupslang-check-impl.h 约 1562-1574 行用于让正在检查的声明找不到自身——头文件中的例子是typedef Foo Foo;。支撑机制三可见性分类与按语言版本的默认值可见性修饰符类层次在 slang-ast-modifier.h 约 49-67 行抽象的VisibilityModifier派生三个空标记类PublicModifier55 行、PrivateModifier61 行、InternalModifier67 行。checker 推理用的DeclVisibility枚举在 slang-ast-support-types.h 约 1897-1902 行数值顺序Private Internal PublicDefault Internal。三个关键字的语义visibility.mdpublic对任何已 import 声明模块的模块可见internal仅声明模块内部可见含其所有文件private仅声明所在的聚合类型struct/class/interface/……内部可见。命名空间不是private的合法容器——命名空间成员按isGlobalDecl仍是全局 declslang-check-decl.cpp 约 1571-1575 行声明处报invalid-use-of-private-visibility30603。分类函数getDeclVisibilityslang-check-decl.cpp 约 21457-21521 行实现三个结构性的 fall-throughAccessorDecl/EnumCaseDecl继承包围 decl 的可见性GenericDecl取innerdecl 的可见性泛型参数取所在GenericDecl的 inner decl 可见性。无显式修饰符时的默认值按模块语言版本决定defaultVis parentModule-languageVersion SLANG_LANGUAGE_VERSION_LEGACY ? DeclVisibility::Public : parentModule-defaultVisibility;Legacy2018每个未标注 decl 默认public——该语言版本早于可见性系统既有代码依赖此行为现代版本默认internal除非模块顶部标public module M;——checkModule在ModuleDecl上发现PublicModifier时翻转模块级默认约 5147-5150 行Slang 2026 及以后未修饰的聚合成员继承聚合的有效可见性21501-21505 行先有接口成员规则2026 聚合规则仿照它写成NamespaceDecl无条件Public21514-21519 行。容器级上限由SemanticsVisitor::checkVisibilityslang-check-modifier.cpp 约 2344-2390 行执行decl 不能引用比自己更不可见的类型use-of-less-visible-type30604也不能比最近包围的AggTypeDeclBase更可见decl-cannot-have-higher-visibility30601。getTypeVisibilityslang-check-expr.cpp 约 1139-1146 行取底层 decl 可见性与 decl-ref 泛型参数可见性的最小值——这就是HashMapString, InternalKey在HashMap为public时仍有效为internal的原因递归走_getTypeVisibility1112-1128 行用DictionaryType*, DeclVisibility记忆化因为PairT, T中两个参数指向同一 canonical 类型 DAG第二条边应复用第一条已收集的可见性。支撑机制四重载解析的两阶段流水线与转换成本重载解析运行两个阶段探针阶段以JustTrying模式给每个候选打分定案阶段对胜出候选以ForReal模式重跑流水线并构造调用 AST 节点。TryCheckOverloadCandidateslang-check-overload.cpp 约 1426 行逐步推进candidate.status任一步失败即返回TryCheckOverloadCandidateArity145 行参数个数在required与allowed之间allowed -1表示无界→TryCheckOverloadCandidateFixity225 行仅对PrefixExpr/PostfixExpr生效要求候选携带匹配的前缀/后缀修饰符→TryCheckOverloadCandidateTypes807 行含泛型推断matchArgumentsToParams让f({1, 2})能匹配结构体参数→TryCheckOverloadCandidateDirections1083 行只检查隐式this的可变性[mutating]方法作用于不可变基表达式在此被拒→TryCheckOverloadCandidateConstraints1157 行验证where子句最外层泛型经trySolveGenericArguments求解器闭包求解→TryCheckOverloadCandidateVisibility265-287 行→Status Applicable。TryCheckOverloadCandidateClassNewMatchUp107-143 行只在定案阶段运行new表达式必须构造ClassDecl普通构造函数调用构造 class 则报错保证 AST 形态与表面语法一致。转换成本ConversionCostslang-ast-support-types.ts 约 89-192 行是关键排序依据。探针阶段经canCoerceslang-check-conversion.cpp 约 3092 行累计每参数成本。重要级别级别数值含义kConversionCost_None0恒等kConversionCost_MatrixLayout5同元素/行/列但布局不同kConversionCost_ImplicitDereference10解引用指针类值kConversionCost_CastToInterface50具体类型 → 满足的接口kConversionCost_BoolToInt120刻意便宜以打破平局kConversionCost_RankPromotion150同转换类别内的无损升秩kConversionCost_GeneralConversion900隐式转换上限被canConvertImplicitly拒绝kConversionCost_Explicit90000仅显式转换kConversionCost_LValueCast800源是左值时的附加成本使取参数自身类型的重载胜过需转换的重载kConversionCost_Impossible0xFFFFFFFF不存在转换CompareOverloadCandidates约 2307 行的比较步骤状态差 → 转换成本总和 →CompareLookupResultItems候选如何被发现→ 隐式转换偏好 →compareOverloadCandidateSpecificity2141 行偏好更少特化参数数→getExportRank→ 作用域距离非泛型→getOverloadRank[OverloadRank(N)]属性internalcore.meta.slang与hlsl.meta.slang是树内仅有的使用者。全部为零则候选同等优秀调用者最终发出AmbiguousOverloadForNameWithArgsslang-diagnostics.lua3959 行或AmbiguousOverloadWithArgs4020 行诊断每个并列候选作为Candidate变参 note 列出、上限 10 条。诊断对照表修复报告及四页设计文档共同给出了常见名称解析诊断与其触发点诊断代码触发点decl-is-not-visible30600filterLookupResultByVisibilityAndDiagnose在查找结果非空但全被过滤时发出use-of-less-visible-type30604checkVisibility检测到签名引用更不可见的类型decl-cannot-have-higher-visibility30601decl 可见性超过包围聚合invalid-use-of-private-visibility30603private用于非类型成员模块/文件作用域、命名空间成员、接口需求ambiguous-reference39999diagnoseAmbiguousReferencenarrowing 后仍多个候选且上下文需要恰好一个ambiguous call / ambiguous overload3959/4020重载比较器对并列候选返回零no applicable overload—无候选达到Applicable时对每个候选附OverloadCandidateArgumentTypeMismatch4002BitwiseOperatorRequiresIntegerOperands3974内建快速路径诊断浮点位运算/~相关文档导航名称解析算法规则目录docs/generated/design/name-resolution/index.md作用域链构造与兄弟作用域docs/generated/design/name-resolution/scopes.md查找算法与去重边界docs/generated/design/name-resolution/lookup.md可见性分类与过滤位置docs/generated/design/name-resolution/visibility.md重载解析流水线docs/generated/design/name-resolution/overload-resolution.md编译流水线定位解析阶段 docs/generated/design/pipeline/02-parse-ast.md、语义检查阶段 docs/generated/design/pipeline/03-semantic-check.md、AST 到 IR docs/generated/design/pipeline/04-ast-to-ir.md数据结构参考Scope/Decl/DeclRef/LookupResult见 docs/generated/design/ast-reference/base.md术语表docs/generated/design/glossary.md关键实现文件Scope与Decl在 source/slang/slang-ast-base.h / source/slang/slang-ast-decl.h查找算法在 source/slang/slang-lookup.h / source/slang/slang-lookup.cpp作用域接线、导入模块拼接与可见性分类在 source/slang/slang-check-decl.cpp查找后处理与isDeclVisibleFromScope在 source/slang/slang-check-expr.cpp重载解析与候选比较器在 source/slang/slang-check-overload.cpp转换成本在 source/slang/slang-check-conversion.cpp。【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表