Unqualified lookup can be initiated from any source position, optionally targeting a specific identifier for name comparison. Unqualified lookup traverses the syntax tree from bottom-up, collecting names that match the lookup criteria. A name matches the lookup if the specified identifier (if present) refers to the introduced name and the position from which the unqualified lookup was triggered is preceded by the position where the name was introduced, provided the name enforces position based availability by itself (such exceptions are for example type declarations).
Names are introduced and their availabilities are set by special syntax nodes referred to as syntax scopes or simply scopes. By default, each scope for lookup produces a result object that associates the scope of origin with the matched names. Lookup is then invoked on the parent scope, i.e., the closest syntax scope ancestor of the original scope in the syntax tree.
There are three distinct kinds of names identified by unqualified lookup:
- Identifier: Represents
IdentifiableSyntaxsyntax nodes introducing names such asIdentifierPatternSyntax,ClosureParameterSyntax, orFunctionParameterSyntax. - Declaration: Represents
NamedDeclSyntaxsyntax nodes introducing names such asClassDeclSyntax,FunctionDeclSyntax, orProtocolDeclSyntax. - Implicit: Represents names introduced by some scopes implicitly, without an associated token representation. Such names include
self,newValue, orerror.
Some scopes share common behavior and can be further generalized as follows:
- Type Scope (
TypeScopeSyntax): Implicitly introducesselfandSelfnames. - Sequential Scope (
SequentialScopeSyntax): Interleaves its own results with results produced by Introducing to Sequential Parent Scopes when encountered during name extraction. Results are ordered from closest to furthest from the unqualified lookup position. - Introducing to Sequential Parent Scope (
IntroducingToSequentialParentScopeSyntax): Produces a result that is later interleaved with the results produced by its sequential parent.
Example: Sequential Scope behavior (code block):
func foo(x: Int?) { // Guard scope guard let a = x else { return } // In code block scope let a: Int? = a // Guard scope guard let b = x, let a else { return } // In code block scope let a = b a // <-- lookup here b // <-- lookup here }When looking up the reference
a, we get 4 results in total, going from the bottom to the top of the source code: theadeclaration associated with the function's code block body scope, theavariable associated with the bottom-mostguard, anotheradeclaration associated with the code block, and finally theaintroduced in the top-mostguard. When looking upb, on the other hand, we get just one result: thebdeclaration inside the bottom-mostguard.
- With Generic Parameters Scope (
WithGenericParametersScopeSyntax): Instead of invoking lookup on its parent scope, it first tries to pass it to the Generic Parameter Scope if available. - Generic Parameter Scope (
GenericParameterScopeSyntax): Instead of invoking lookup on its parent scope, it first tries to invoke it on the With Generic Parameters Scope's parent.
Example: Generic Parameter Scope Behavior:
class Foo<A, B: A> { // <-- lookup here func bar<A, C>() { X // <-- lookup here } }When starting the lookup from the location marked
Xwithout identifier matching, we get two results. The first is associated with the generic parameter scope of thebarfunction, containing the namesAandCin that exact order. The second is associated with the classFoo, containing the namesAandBin that exact order. When performing lookup at theAreference in theFooclass generic parameter clause, the generic parameterAintroduced inside the clause is returned as in the result.
- File Scope (
SourceFileSyntax): Conditional sequential scope syntax. Depending on user-defined configuration, the file scope can behave in two ways: either as a member block scope or as a sequential lookup for all declarations up to the first non-declaration statement. - Code Block Scope (
CodeBlockSyntax): Sequential scope. Introduces names from its statements with position-based availability. - For Statement Scope (
ForStmtSyntax): Introduces names from inside its pattern. - Closure Expression Scope (
ClosureExprSyntax): Introduces names from closure captures and parameters. - While Statement Scope (
WhileStmtSyntax): Introduces names from its conditions. - If Expression Scope (
IfExprSyntax): Introduces names from its conditions and specifies custom parent behavior. For nestedelse ifstatements, the parent scope is set to the enclosing scope for the entireifexpression. Additionally, lookup from inside theelseclause is routed directly to the parent scope. - Member Block Scope (
MemberBlockSyntax): Introduces names from its members. - Guard Statement Scope (
GuardStmtSyntax): Introduces names from its conditions to its sequential parent. For lookup from inside itselseclause, no results are introduced. - Actor Declaration Scope (
ActorDeclSyntax): Type scope with generic parameters. - Class Declaration Scope (
ClassDeclSyntax): Type scope with generic parameters. - Struct Declaration Scope (
StructDeclSyntax): Type scope with generic parameters. - Enum Declaration Scope (
EnumDeclSyntax): Type scope with generic parameters. - Extension Declaration Scope (
ExtensionDeclSyntax): Type scope. - Accessor Declaration Syntax (
AccessorDeclSyntax): Introduces names from its parameters. If not present, and if the accessor is ofsetorwillSetkind, it introduces the implicitnewValuename. If ofdidSetkind, it introduces the implicitoldValuename. - Catch Clause Scope (
CatchClauseSyntax): Introduces the impliciterrorname if catch items are not specified. - Switch Case Scope (
SwitchCaseSyntax): Introduces names from its case items. - Protocol Declaration Scope (
ProtocolDeclSyntax): Does not introduce any names. For lookup initiated in its primary associated type clause, it performs lookup on its member scope exclusively for associated type declarations. - Generic Parameter Clause Scope (
GenericParameterClauseSyntax): Generic parameter scope syntax. Introduces names from its generic parameters with position-based availability. - Function Declaration Scope (
FunctionDeclSyntax): With generic parameters scope. Introduces names from its parameters. - Subscript Declaration Syntax (
SubscriptDeclSyntax): With generic parameters scope. Introduces names from its parameters. - Type Alias Declaration Scope (
TypeAliasDeclSyntax): With generic parameters scope. Does not introduce any names. - Enum Case Declaration Scope (
EnumCaseDeclSyntax): Introduces names from each case element's associated-value parameters. A parameter name is only visible inside default-value expressions of subsequent parameters in the same case declaration (position-based availability). No names are introduced to any outer scope.