菜单

名称查找与作用域

相关源文件

本文档描述了 Swift 的名称查找和作用域系统,该系统决定了标识符在代码中的解析方式。它涵盖了编译器如何在不同上下文中查找变量、函数和类型名称,并解释了决定名称可见性的作用域层次结构。有关名称查找后的类型检查等相关主题,请参阅 类型系统

名称查找过程概述

名称查找是解析 Swift 源代码中标识符到声明的过程。查找过程会遍历作用域的层次结构,每个作用域对应于程序中的一个语法结构,以查找与给定名称匹配的声明。

名称查找的主要入口点是 ASTScopeImpl::unqualifiedLookup(),它接收一个源文件、一个源位置和一个用于收集找到的声明的消费者。

来源: lib/AST/ASTScopeLookup.cpp41-47 lib/AST/UnqualifiedLookup.cpp238-273

ASTScope 层次结构

Swift 编译器通过一系列 ASTScopeImpl 对象的层次结构来表示词法作用域。每个作用域都对应于源代码中声明可能可见的一个区域。

作用域树是惰性构建的——编译器仅构建当前操作所需的树的部分。这由 expandAndBeCurrent() 方法管理,该方法按需构建子作用域。

来源: include/swift/AST/ASTScope.h109-132 lib/AST/ASTScopeCreation.cpp47-237

作用域树中的源范围管理

ASTScope 树中的每个作用域都有一个源范围,用于定义其在源代码中的范围。作用域的排列方式是:

  1. 子作用域的源范围是其父作用域范围的真子集
  2. 同级作用域不重叠
  3. 子作用域按其源位置排序

源范围管理由 checkSourceRangeBeforeAddingChild() 强制执行,该函数在向树中添加新作用域时会验证这些约束。

来源: lib/AST/ASTScopeSourceRange.cpp42-103

非限定名称查找

非限定名称查找发生在解析没有限定符的标识符时(例如,仅 identifier 而不是 some.identifier)。此过程包含几个阶段:

  1. 基于 ASTScope 的查找:从给定源位置的作用域开始,沿着作用域层次结构向上遍历,查找具有所需名称的声明。
  2. 模块级查找:如果在局部作用域中未找到结果,则在当前模块和导入的模块中查找顶级声明。
  3. 结果处理:根据访问控制、可用性和遮蔽规则过滤结果。

查找过程由 UnqualifiedLookupFlags 中定义的各种标志控制。

标志目的
TypeLookup仅查找类型
AllowProtocolMembers考虑协议成员
IgnoreAccessControl忽略访问级别限制
IncludeOuterResults包含来自外部作用域的结果
MacroLookup仅查找宏
ModuleLookup仅查找模块

来源: lib/AST/UnqualifiedLookup.cpp237-296 include/swift/AST/NameLookup.h231-260

查找结果与名称遮蔽

名称查找操作的结果由 LookupResultLookupResultEntry 类表示。

  • LookupResultEntry 包含一个找到的声明、其基础声明上下文以及基础声明(这对实例成员很重要)。
  • LookupResult 包含一个条目集合,分为“内部”和“外部”结果,用于处理作用域可见性。

名称遮蔽发生在内部作用域的声明与外部作用域的声明同名时。查找系统通过优先考虑内部作用域中的声明,以及将结果组织成“内部”和“外部”类别来处理这种情况。

来源: include/swift/AST/NameLookup.h53-229 lib/AST/NameLookup.cpp310-363

特殊查找情况

泛型参数

泛型参数在其声明的作用域及其子作用域中可见。ASTScope 系统确保泛型参数可以在以下位置正确查找:

  • 声明的泛型参数列表
  • Where 子句
  • 泛型函数或类型的体

例如,在泛型函数中,参数在函数体内是可见的。

来源: lib/AST/ASTScopeLookup.cpp185-201

闭包和捕获列表

闭包表达式会创建自己的作用域,并有捕获外部作用域变量的特殊规则。闭包参数创建自己的作用域,然后是闭包体。

闭包可以访问:

  1. 它们自己的参数和局部声明的变量
  2. 从外部作用域捕获的变量,无论是显式地在捕获列表中还是隐式地捕获

查找系统处理各种捕获场景,包括 weak self 和其他修改引用的捕获。

来源: lib/AST/ASTScopeCreation.cpp98-195 lib/AST/UnqualifiedLookup.cpp354-441

模式绑定与初始化器

模式绑定声明(用于 letvar 声明)会创建特殊的作用域规则。在模式中声明的变量在其自己的初始化器中不可见,以防止类似以下的代码:

这由 PatternEntryInitializerScope 处理,该作用域在执行查找时会跳过父模式绑定作用域。

来源: lib/AST/ASTScopeLookup.cpp318-357

调试作用域树

可以使用几种工具来可视化和调试 ASTScope 树:

  1. 编译器标志 -dump-scope-maps,它会打印整个作用域树。
  2. ASTScopeImpl::print() 方法,它可以打印一个作用域及其子作用域。
  3. dumpOneScopeMapLocation() 方法,它会查找并打印特定源位置的作用域。

这些工具对于理解编译器如何为给定的代码段构建作用域层次结构非常有价值。

来源: lib/AST/ASTScopePrinting.cpp41-68

实践示例:嵌套上下文中的名称解析

为了说明名称查找和作用域的实际工作原理,请考虑以下 Swift 代码示例:

nestedFunc 中查找 staticVar 时,编译器会:

  1. NestedFuncBody 作用域开始。
  2. 在此处找不到 staticVar,因此移动到 NestedFuncDecl 作用域。
  3. 在此处找不到,继续到 MethodBody
  4. 在此处找不到,继续到 MethodDecl
  5. 在此处找不到,继续到 StructInner
  6. StructInner 作用域中找到了 staticVar 并停止。

这演示了基于作用域的查找的层次结构性质以及它如何在嵌套上下文中解析标识符。

来源: test/expr/closure/closures.swift134-148

与类型检查集成

名称查找是类型检查的关键前置步骤。在名称解析为声明后,类型检查器可以确定:

  1. 声明的使用在当前上下文中是否有效
  2. 是否需要隐式转换
  3. 是否存在需要通过重载解析来解决的歧义

查找系统提供了关于声明的必要信息,以启用这些后续的类型检查操作。

来源: lib/Sema/TypeCheckNameLookup.cpp73-91

结论

Swift 的名称查找和作用域系统通过一个强大的框架为各种上下文中的标识符解析提供了支持。通过将源代码组织到作用域层次结构中并系统地搜索该层次结构,编译器可以提供准确且可预测的名称解析,这对于闭包、泛型和访问控制等语言特性的正确工作至关重要。