ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Carbon 语言非限定名称查找(Unqualified Name Lookup)设计解析:类、接口与命名空间的作用域规则

Carbon 语言非限定名称查找(Unqualified Name Lookup)设计解析:类、接口与命名空间的作用域规则 Carbon 语言非限定名称查找Unqualified Name Lookup设计解析类、接口与命名空间的作用域规则【免费下载链接】carbon-langCarbon Languages main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang非限定名称查找unqualified name lookup是 Carbon 语言作用域与名称解析体系的核心环节它决定了在类、接口、命名空间内部或外部定义out-of-line definition中直接书写Scale、B、a这类裸名称时编译器到哪个作用域中去寻找对应实体。本文以提案 proposals/p002287-allow-unqualified-name-lookup.md 为主体结合 docs/design/name_lookup.md、docs/design/classes.md、docs/design/expressions/member_access.md 以及编译器检查层源码 toolchain/check/name_lookup.h 与 toolchain/check/name_lookup.cpp完整还原该提案的问题背景、具体规则、代码示例与开放问题并给出实现层面的印证。读完本文你将掌握 Carbon 中限定符提名作用域scope nomination by qualifier的确切语义理解类成员、接口成员、命名空间成员在内外定义时为何能或不能被裸名称直接引用。提案背景成员访问文档未覆盖的场景Carbon 的名称解析体系由两份核心文档共同定义docs/design/expressions/member_access.md 定义了限定名称qualified name与成员访问表达式例如Widgets.Cog.Make、cog1.size、cog2.(Widget.Grow)等写法的解析步骤成员解析 →impl查找 → 实例绑定docs/design/name_lookup.md 定义了名称查找的整体规则。然而member_access 文档只回答了点号右侧的限定名称如何解析没有覆盖不带点号、直接出现在类或命名空间相关代码中的裸名称如何解析。提案 p002287 正是为了填补这一空白当在一个类class、接口interface或命名空间namespace的外部定义out-of-line definition中书写函数签名与函数体时其中的裸名称应当被解析到哪个作用域该提案同时受到两份上游文档的影响docs/design/expressions/member_access.md 定义了成员访问语义是限定名称解析的既有基础docs/project/principles/information_accumulation.md信息累积原则规定了名称只能引用此前已声明的实体以及类成员函数体延迟处理deferred等例外规则这直接约束了名称查找的可行范围。此外这一行为与 C 的非限定名称查找unqualified lookup语义相似便于从 C 迁移的开发者建立直观预期。提案核心允许非限定名称查找进入合适的作用域提案的 Abstract 用一句话概括目标在多种情形下允许非限定名称查找具体包括对于类与接口无论是处于类作用域内部还是处于类/接口的外部函数定义中都允许非限定名称查找对于命名空间当命名空间被用于某个声明如fn Foo.C(...)时也允许非限定名称查找。这里有一个重要边界隐式地将非限定名称绑定到me实例绑定不在本提案范围内它被留作开放问题见下文开放问题一节。现行设计文档中的完整规则提案被接受后其规则被沉淀进了 docs/design/name_lookup.md 的 Unqualified name lookup 一节现行为Unqualified name lookup in Carbon searches the current and enclosing scopes up to the top-level file scope.即非限定名称查找会依次搜索当前作用域及所有外层作用域直到文件顶层作用域。具体能搜到哪些名称当前或外层作用域中局部声明的名称——要求声明必须先于引用出现符合信息累积原则限定符提名的作用域——当某个外部声明/定义被作用域或嵌套作用域限定例如fn Foo.Bar(...)则限定符Foo、Foo.Bar所提名的作用域会作为该声明签名与函数体内部非限定名称查找的外层作用域。这一条对命名空间、类、接口三者统一适用当前包中通过 import 可见的名称例如import library ...包括从 API 文件到 impl 文件的隐式导入隐式的 prelude——标准库基础实体的隐式导入与别名如bool、i32等类型字面量实际是 prelude 中实体的别名。文档同时定义了**名称污染poisoning**机制在声明式作用域命名空间、类、接口中如果对某个名称做非限定查找但未找到该名称在此作用域中被视为被污染poisoned若此后该作用域又引入了同名声明则属于错误因为这会改变早前查找的含义。在一个库内污染会从 API 文件延续到实现文件。在顺序作用域如函数体中则不允许重复声明实体。这与实现层的接口一一对应toolchain/check/name_lookup.h 中暴露了LookupUnqualifiedName非限定查找、LookupQualifiedName限定查找、LookupNameInDecl声明中的查找以及DiagnosePoisonedName污染名称诊断等核心入口该文件顶部的注释还说明了LookupNameInExactScope对污染的处理细节非声明式查找未命中会污染名称声明式查找则不会污染但会返回已被查找过的信号。类作用域中的非限定查找对于类提案更新了 docs/design/classes.md。该类文档的 Name lookup in classes 一节给出了细化规则由于函数定义是延迟处理deferred的详见 docs/project/principles/information_accumulation.md 的例外一节类成员函数体被推迟到最外层包围类的定义结束之后再处理类似 C类内名称查找与函数是否内联无关类体构成一个名称查找作用域函数定义可以在此作用域内执行非限定名称查找但函数签名必须在不延迟的情况下完成查找——即签名中引用的成员必须已经声明。类文档给出了一个极具代表性的示例class Square { fn GetArea(self) - f32 { // ✅ OK: performs name lookup on self. return self.size * self.size; // ❌ Error: finds Square.size, but an instance is required. return size * size; // ❌ Error: an instance is required. return Square.size * Square.size; // ✅ OK: performs instance binding with self. return self.(Square.size) * self.(Square.size); // ✅ OK: uses unqualified name lookup to find Square.size, then performs // instance binding with self. return self.(size) * self.(size); } fn GetDoubled(self) - Square { // ✅ OK: performs name lookup on Square for Create. return Square.Make(self.size); // ✅ OK: performs unqualified name lookup within class scope for Create. return Make(self.size); // ✅ OK: performs name lookup on self for Create. return self.Make(self.size); } fn Make(size: f32) - Square; var size: f32; }这个例子精确划出了非限定查找与实例绑定之间的边界return size * size;中非限定查找能找到Square.size但它是一个实例成员直接裸用缺少实例因此报错return Make(self.size);中非限定查找能找到Square.Make静态成员函数直接裸用合法裸名称查找与self.(size)这类先非限定查找、再对self做实例绑定的写法是互补的——后者正是提案中在类外部定义中先对接口做非限定查找、再对me做实例绑定模式的类内版本。注意示例中成员访问self.size、Make(...)引用的Create原文如此即Make与size均定义在访问点之后这之所以合法正是因为类成员函数体的延迟处理而签名则不允许这样。对于类的外部定义out-of-line definition类文档同样给出明确示例——参数、返回类型与函数体都视为处于类作用域之内来求值class List { // ❌ Error: Iterator has not yet been defined. fn Iterate() - Iterator; class Iterator { ... } // ✅ OK: The definition of Iterator is now available. fn Iterate() - Iterator; } // ✅ OK: The return type performs unqualified name lookup into List for // Iterator. fn List.Iterate() - Iterator { ... }这里体现了两个关键点其一签名中的查找是即时非延迟的因此类内先声明fn Iterate() - Iterator;时Iterator尚未定义会报错需要在Iterator定义后重复声明其二外部定义fn List.Iterate() - Iterator的返回类型可以直接写Iterator因为限定符List提名的作用域已作为该定义的外层作用域参与非限定查找——这正是提案要确立的规则。接口外部定义Self与非限定成员调用提案对接口给出了完整的代码示例展示如何在接口的外部默认定义中直接使用接口自己的成员interface Vector { fn Scaleme: Self - Self; // Default definition of Invert calls Scale. default fn Invert[me: Self]() - Self; } // Self is valid here because its doing unqualified name lookup into // Vector. default fn Vector.Invert[me: Self]() - Self { // Scale is valid here because it does unqualified name lookup into // Vector, then an instance binding with me. return me.(Scale)(-1.0); }逐行拆解这个示例接口Vector声明了实例方法Scaleme: Self - Self并声明了default fn Invert[me: Self]() - Self默认定义放在类外即 out-of-line外部定义default fn Vector.Invert[me: Self]() - Self中出现了三次非限定名称参数与返回类型中的Self通过限定符Vector提名的作用域做非限定查找找到这是接口是名称查找作用域的直接体现函数体中的Scale先通过限定符Vector做非限定查找解析到Vector.Scale再通过复合成员访问me.(Scale)对me做实例绑定从而调用实例方法me.(Scale)(-1.0)之所以使用复合成员访问compound member access.(...)语法而非me.Scale(-1.0)是因为此处需要先完成对Scale的解析再显式绑定到me实例——这一机制的语义细节定义在 docs/design/expressions/member_access.md 的 Instance binding 一节复合成员访问x.(Y)中Y命名实例成员时总是执行实例绑定且Y不能已被绑定过实例。提案同时提醒隐式地将非限定名称绑定到me即 C 中this-Member()与Member()等价的行为并未在此提案中提出。因此示例必须显式写成me.(Scale)(-1.0)而不是直接Scale(-1.0)。这保持了与既有设计文档均假设不存在隐式me绑定的一致性。命名空间中的非限定查找提案强调这一规则不应只适用于类与接口而应更一般地适用于所有被用于声明的其他作用域其中命名空间namespace是典型代表。不过由于 docs/design/name_lookup.md 当时尚未细化提案本身对该文档的更新有限——规则最终沉淀为上文引述的限定符提名作用域条款。命名空间的示例namespace Foo; var Foo.a: i32 0; class Foo.B {} // B and a are valid here because unqualified name lookup occurs within // Foo. fn Foo.C(B b) - i32 { return a; }解析要点fn Foo.C(B b) - i32是一个被命名空间Foo限定的外部函数定义其参数类型B通过非限定查找进入Foo作用域找到Foo.B类其函数体中的a同样通过非限定查找进入Foo作用域找到var Foo.a: i32若无此规则开发者被迫写成fn Foo.C(Foo.B b) - i32 { return Foo.a; }——语义等价但冗长得多。命名空间的这种写法与类文档中的fn Point.Distance(self) - f32 { ... }外部定义风格一致见 docs/design/classes.md后者在函数体内同样可以裸用类作用域中可解析的名字。开放问题me的隐式实例绑定与 impl 的外部定义提案明确列出两个尚未定论的问题。开放问题一me的隐式实例绑定在 C 中非限定名称查找可以隐式地对this做实例绑定即方法定义内this-Member()与Member()行为等价。Carbon 的当前设计中me是否应具有类似行为尚未定型大多数既有设计文档假设不提供该行为该问题从未被任何提案正面讨论过隐式作用域函数参数implicit scoped function parameters 或许能提供一种语言自洽的实现方式。提案对此不持立场它无意改变此前提案确立的行为因此本提案通过后非限定查找仍不会隐式解析为me。这一点与 docs/design/classes.md 中return size * size;报错实例成员缺少实例的示例相互印证。开放问题二impl 的外部定义问题 #2377 似乎也没有为其他impl的外部定义给出语法。因此impl 场景下的非限定查找规则仍待后续设计。设计论证与备选方案设计理由Rationale提案从两个项目目标出发论证该设计代码易读、易理解、易编写docs/project/goals.md对类成员做非限定查找对读者而言并不意外且在命名空间内工作时允许写更简洁的代码如上文fn Foo.C(B b) - i32 { return a; }省去了大量Foo.前缀与既有 C 代码的互操作与迁移docs/project/goals.md该行为与 C 的工作方式相似降低 C 开发者迁移时的认知负担。备选方案不在作用域外部定义时进行非限定查找一个被否定的备选方案是当定义的对象处于文件顶层作用域之外即被限定符提名时不支持非限定查找。这一选择有微妙的连锁后果若命名空间示例中的Foo.C为此目的被视为处于Foo作用域之外则函数必须写成全限定形式fn Foo.C(Foo.B b) - i32 { return Foo.a; }对类而言它还会造成内联声明与外部定义不对称的怪象内联声明fn Foo() - ClassMember合法而外部定义fn Class.Foo() - ClassMember不合法必须写Class.ClassMember。该方案的优势是访问显式当package.a与package.Foo.a同时存在时全限定写法不会让读者混淆且package.Foo.a对非限定查找而言很可能是无歧义的会被唯一命中。其劣势则非常明显写法冗长对开发者不够符合人体工学un-ergonomic。这一显式性 vs 简洁性的权衡最终以支持非限定查找告终。实现印证编译器检查层的查找入口规则最终落地在 Carbon 工具链的类型检查check阶段。关键实现文件为 toolchain/check/name_lookup.h 与 toolchain/check/name_lookup.cpp核心接口包括LookupUnqualifiedName执行非限定名称查找返回被引用的InstId——对应本文讨论的非限定查找行为LookupNameInDecl在指定作用域中为声明中出现的名称执行查找若scope_id为None则退回词法作用域——对应限定符提名作用域作为外层作用域的机制LookupQualifiedName在指定作用域及其extend扩展作用域中执行限定查找LookupScope结构体记录执行查找的名称作用域、specific 与 self 类型——其注释明确指出self类型在成员访问式查找时使用与类/接口成员查找对应DiagnosePoisonedName/LookupNameInExactScope实现名称污染poisoning机制未命中的非限定名称会被污染后续同名声明将触发诊断。测试方面toolchain/check/testdata/basics/name_lookup.carbon 是名称查找的自动化文件测试file test用例其中fail_not_found.carbon验证了名称x未找到时产生NameNotFound诊断的行为可用以下命令单独运行验证bazel test //toolchain/testing:file_test --test_arg--file_teststoolchain/check/testdata/basics/name_lookup.carbon同类测试还包括 toolchain/check/testdata/impl/name_lookup_in_impl_definition.carbonimpl 定义中的名称查找与开放问题二相关与 toolchain/check/testdata/named_constraint/extend_name_lookup.carbon命名约束中extend后的名称查找。这些测试共同印证了提案规则在编译器中的真实落点。总结提案 p002287 为 Carbon 确立了一条简洁而一致的作用域规则当声明或定义被作用域限定如fn Foo.C(...)、default fn Vector.Invert(...)时限定符提名的作用域命名空间、类、接口会成为该声明签名与函数体进行非限定名称查找的外层作用域。它让类内成员裸用、接口外部默认定义中裸用Self与成员、命名空间外部函数中裸用包内名字成为可能同时明确排除了隐式绑定到me。该规则已固化在 docs/design/name_lookup.md 与 docs/design/classes.md 中并在 toolchain/check/name_lookup.cpp 与相应测试中落地是理解 Carbon 名称解析、编写简洁包级代码时不可绕过的一块基石。【免费下载链接】carbon-langCarbon Languages main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进