From d83af9c7aec7286de3eb34335f1ececa1beb4769 Mon Sep 17 00:00:00 2001 From: Jakub Florek Date: Tue, 17 Sep 2024 17:50:35 +0200 Subject: [PATCH 1/8] Add astscope related fixes and lookInMembers functionality. --- Sources/SwiftLexicalLookup/CMakeLists.txt | 3 +- .../Configurations/LookupConfig.swift | 14 +- Sources/SwiftLexicalLookup/LookupName.swift | 56 +++- Sources/SwiftLexicalLookup/LookupResult.swift | 58 +++- .../Scopes/GenericParameterScopeSyntax.swift | 4 +- ...x.swift => LookInMembersScopeSyntax.swift} | 13 +- .../Scopes/NominalTypeDeclSyntax.swift | 42 +++ .../Scopes/ScopeImplementations.swift | 229 ++++++++++++++-- .../Scopes/ScopeSyntax.swift | 7 + .../Scopes/SequentialScopeSyntax.swift | 20 +- .../WithGenericParametersScopeSyntax.swift | 20 +- .../NameLookupTests.swift | 253 +++++++++++++----- .../ResultExpectation.swift | 21 +- 13 files changed, 614 insertions(+), 126 deletions(-) rename Sources/SwiftLexicalLookup/Scopes/{TypeScopeSyntax.swift => LookInMembersScopeSyntax.swift} (60%) create mode 100644 Sources/SwiftLexicalLookup/Scopes/NominalTypeDeclSyntax.swift diff --git a/Sources/SwiftLexicalLookup/CMakeLists.txt b/Sources/SwiftLexicalLookup/CMakeLists.txt index b6cd9204fc3..c9a0a64e3fd 100644 --- a/Sources/SwiftLexicalLookup/CMakeLists.txt +++ b/Sources/SwiftLexicalLookup/CMakeLists.txt @@ -17,10 +17,11 @@ add_swift_syntax_library(SwiftLexicalLookup Scopes/GenericParameterScopeSyntax.swift Scopes/IntroducingToSequentialParentScopeSyntax.swift + Scopes/LookInMembersScopeSyntax.swift + Scopes/NominalTypeDeclSyntax.swift Scopes/ScopeImplementations.swift Scopes/ScopeSyntax.swift Scopes/SequentialScopeSyntax.swift - Scopes/TypeScopeSyntax.swift Scopes/WithGenericParametersScopeSyntax.swift ) diff --git a/Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift b/Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift index ec81ef2d8b4..98812e7cef0 100644 --- a/Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift +++ b/Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift @@ -13,14 +13,26 @@ @_spi(Experimental) public struct LookupConfig { /// Specifies behavior of file scope. @_spi(Experimental) public var fileScopeHandling: FileScopeHandlingConfig + /// Specifies whether lookup should finish in the closest sequential scope. + @_spi(Experimental) public var finishInSequentialScope: Bool + /// Specifies whether to include results generated in file and member block scopes. + @_spi(Experimental) public var includeMembers: Bool /// Creates a new lookup configuration. /// /// - `fileScopeHandling` - specifies behavior of file scope. /// `memberBlockUpToLastDecl` by default. + /// - `finishInSequentialScope` - specifies whether lookup should finish + /// in the closest sequential scope. `false` by default. + /// - `includeMembers` - specifies whether to include results generated + /// in file and member block scopes. `true` by default. @_spi(Experimental) public init( - fileScopeHandling: FileScopeHandlingConfig = .memberBlockUpToLastDecl + fileScopeHandling: FileScopeHandlingConfig = .memberBlockUpToLastDecl, + finishInSequentialScope: Bool = false, + includeMembers: Bool = true ) { self.fileScopeHandling = fileScopeHandling + self.finishInSequentialScope = finishInSequentialScope + self.includeMembers = includeMembers } } diff --git a/Sources/SwiftLexicalLookup/LookupName.swift b/Sources/SwiftLexicalLookup/LookupName.swift index 5bd15a93bf0..105a5ef655b 100644 --- a/Sources/SwiftLexicalLookup/LookupName.swift +++ b/Sources/SwiftLexicalLookup/LookupName.swift @@ -16,8 +16,8 @@ import SwiftSyntax @_spi(Experimental) public enum ImplicitDecl { /// `self` keyword representing object instance. /// Could be associated with type declaration, extension, - /// or closure captures. - case `self`(DeclSyntaxProtocol) + /// or closure captures. Introduced at function edge. + case `self`(FunctionDeclSyntax) /// `Self` keyword representing object type. /// Could be associated with type declaration or extension. case `Self`(DeclSyntaxProtocol) @@ -135,6 +135,35 @@ import SwiftSyntax } } + /// Position of this name. + /// + /// For some syntax nodes, their position doesn't reflect + /// the position at which a particular name was introduced at. + /// Such cases are function parameters (as they can + /// contain two identifiers) and function declarations (where name + /// is precided by access modifiers and `func` keyword). + @_spi(Experimental) public var position: AbsolutePosition { + switch self { + case .identifier(let syntax, _): + switch Syntax(syntax).as(SyntaxEnum.self) { + case .functionParameter(let functionParameter): + return functionParameter.secondName?.positionAfterSkippingLeadingTrivia + ?? functionParameter.firstName.positionAfterSkippingLeadingTrivia + default: + return syntax.position + } + case .declaration(let syntax): + return syntax.name.position + case .implicit(let implicitName): + switch implicitName { + case .self(let functionDecl): + return functionDecl.name.position + default: + return implicitName.syntax.position + } + } + } + /// Point, after which the name is available in scope. /// If set to `nil`, the name is available at any point in scope. var accessibleAfter: AbsolutePosition? { @@ -225,4 +254,27 @@ import SwiftSyntax ) -> [LookupName] { [.declaration(namedDecl)] } + + /// Debug description of this lookup name. + @_spi(Experimental) public var debugDescription: String { + let sourceLocationConverter = SourceLocationConverter(fileName: "", tree: syntax.root) + let location = sourceLocationConverter.location(for: position) + let strName = (identifier != nil ? identifier!.name : "NO-NAME") + " at: \(location.line):\(location.column)" + + switch self { + case .identifier: + let str = "identifier: \(strName)" + + if let accessibleAfter { + let location = sourceLocationConverter.location(for: accessibleAfter) + return str + " after: \(location.line):\(location.column)" + } else { + return str + } + case .declaration: + return "declaration: \(strName)" + case .implicit: + return "implicit: \(strName)" + } + } } diff --git a/Sources/SwiftLexicalLookup/LookupResult.swift b/Sources/SwiftLexicalLookup/LookupResult.swift index 3bcc1ac2a8b..7d38a7f58c3 100644 --- a/Sources/SwiftLexicalLookup/LookupResult.swift +++ b/Sources/SwiftLexicalLookup/LookupResult.swift @@ -18,14 +18,18 @@ import SwiftSyntax case fromScope(ScopeSyntax, withNames: [LookupName]) /// File scope and names that matched lookup. case fromFileScope(SourceFileSyntax, withNames: [LookupName]) + /// Indicates where to perform member lookup. + case lookInMembers(LookInMembersScopeSyntax) /// Associated scope. - @_spi(Experimental) public var scope: ScopeSyntax? { + @_spi(Experimental) public var scope: ScopeSyntax { switch self { case .fromScope(let scopeSyntax, _): return scopeSyntax case .fromFileScope(let fileScopeSyntax, _): return fileScopeSyntax + case .lookInMembers(let lookInMemb): + return lookInMemb } } @@ -34,6 +38,8 @@ import SwiftSyntax switch self { case .fromScope(_, let names), .fromFileScope(_, let names): return names + case .lookInMembers(_): + return [] } } @@ -46,4 +52,54 @@ import SwiftSyntax return .fromScope(scope, withNames: names) } } + + /// Debug description of this lookup name. + @_spi(Experimental) public var debugDescription: String { + var description = + resultKindDebugName + ": " + scope.scopeDebugDescription + + switch self { + case .lookInMembers: + break + default: + if !names.isEmpty { + description += "\n" + } + } + + for (index, name) in names.enumerated() { + if index + 1 == names.count { + description += "`-" + name.debugDescription + } else { + description += "|-" + name.debugDescription + "\n" + } + } + + return description + } + + /// Debug name of this result kind. + private var resultKindDebugName: String { + switch self { + case .fromScope: + return "fromScope" + case .fromFileScope: + return "fromFileScope" + case .lookInMembers: + return "lookInMembers" + } + } +} + +@_spi(Experimental) extension [LookupResult] { + /// Debug description this array of lookup results. + @_spi(Experimental) public var debugDescription: String { + var str: String = "" + + for (index, result) in self.enumerated() { + str += result.debugDescription + (index + 1 == self.count ? "" : "\n") + } + + return str + } } diff --git a/Sources/SwiftLexicalLookup/Scopes/GenericParameterScopeSyntax.swift b/Sources/SwiftLexicalLookup/Scopes/GenericParameterScopeSyntax.swift index eac1a67c824..dd6e3036129 100644 --- a/Sources/SwiftLexicalLookup/Scopes/GenericParameterScopeSyntax.swift +++ b/Sources/SwiftLexicalLookup/Scopes/GenericParameterScopeSyntax.swift @@ -16,7 +16,7 @@ import SwiftSyntax /// futher lookup to its `WithGenericParametersScopeSyntax` /// parent scope's parent scope (i.e. on return, bypasses names /// introduced by its parent). -@_spi(Experimental) public protocol GenericParameterScopeSyntax: ScopeSyntax {} +protocol GenericParameterScopeSyntax: ScopeSyntax {} @_spi(Experimental) extension GenericParameterScopeSyntax { /// Returns names matching lookup and bypasses @@ -83,7 +83,7 @@ import SwiftSyntax if let parentScope = Syntax(parentScope).asProtocol(SyntaxProtocol.self) as? WithGenericParametersScopeSyntax { - return parentScope.lookupInParent(identifier, at: lookUpPosition, with: config) + return parentScope.returningLookupFromGenericParameterScope(identifier, at: lookUpPosition, with: config) } else { return lookupInParent(identifier, at: lookUpPosition, with: config) } diff --git a/Sources/SwiftLexicalLookup/Scopes/TypeScopeSyntax.swift b/Sources/SwiftLexicalLookup/Scopes/LookInMembersScopeSyntax.swift similarity index 60% rename from Sources/SwiftLexicalLookup/Scopes/TypeScopeSyntax.swift rename to Sources/SwiftLexicalLookup/Scopes/LookInMembersScopeSyntax.swift index 5f344405792..c8767d2099b 100644 --- a/Sources/SwiftLexicalLookup/Scopes/TypeScopeSyntax.swift +++ b/Sources/SwiftLexicalLookup/Scopes/LookInMembersScopeSyntax.swift @@ -12,14 +12,7 @@ import SwiftSyntax -@_spi(Experimental) public protocol TypeScopeSyntax: ScopeSyntax, DeclSyntaxProtocol {} - -extension TypeScopeSyntax { - @_spi(Experimental) public var implicitInstanceAndTypeNames: [LookupName] { - [.implicit(.self(self)), .implicit(.Self(self))] - } - - @_spi(Experimental) public var introducedNames: [LookupName] { - implicitInstanceAndTypeNames - } +@_spi(Experimental) public protocol LookInMembersScopeSyntax: ScopeSyntax { + /// Position used for member lookup. + var lookupMembersPosition: AbsolutePosition { get } } diff --git a/Sources/SwiftLexicalLookup/Scopes/NominalTypeDeclSyntax.swift b/Sources/SwiftLexicalLookup/Scopes/NominalTypeDeclSyntax.swift new file mode 100644 index 00000000000..7a1ea5a85af --- /dev/null +++ b/Sources/SwiftLexicalLookup/Scopes/NominalTypeDeclSyntax.swift @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +protocol NominalTypeDeclSyntax: LookInMembersScopeSyntax, NamedDeclSyntax, WithGenericParametersScopeSyntax { + var inheritanceClause: InheritanceClauseSyntax? { get } +} + +extension NominalTypeDeclSyntax { + @_spi(Experimental) public var lookupMembersPosition: AbsolutePosition { + name.position + } + + /// Nominal type doesn't introduce any names by itself. + @_spi(Experimental) public var introducedNames: [LookupName] { + [] + } + + /// Function used by generic parameter clause + /// scope on return from it's lookup. + func returningLookupFromGenericParameterScope( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig + ) -> [LookupResult] { + if let inheritanceClause, inheritanceClause.range.contains(lookUpPosition) { + return lookupInParent(identifier, at: lookUpPosition, with: config) + } else { + return [.lookInMembers(self)] + lookupInParent(identifier, at: lookUpPosition, with: config) + } + } +} diff --git a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift index 5c59f8ba66c..1a0cb9521de 100644 --- a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift +++ b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift @@ -21,6 +21,13 @@ import SwiftSyntax return self.parent?.scope } } + + /// This node's line and column separated by `:`. + var debugLineWithColumnDescription: String { + let location = SourceLocationConverter(fileName: "", tree: root).location(for: position) + + return "\(location.line):\(location.column)" + } } @_spi(Experimental) extension SourceFileSyntax: SequentialScopeSyntax { @@ -30,6 +37,10 @@ import SwiftSyntax introducedNames(using: .memberBlockUpToLastDecl) } + @_spi(Experimental) public var scopeDebugName: String { + "FileScope" + } + /// All names introduced in the file scope /// using the provided configuration. /// @@ -101,6 +112,8 @@ import SwiftSyntax at lookUpPosition: AbsolutePosition, with config: LookupConfig ) -> [LookupResult] { + guard config.includeMembers else { return [] } + switch config.fileScopeHandling { case .memberBlock: let names = introducedNames(using: .memberBlock) @@ -151,6 +164,10 @@ import SwiftSyntax } } + @_spi(Experimental) public var scopeDebugName: String { + "CodeBlockScope" + } + @_spi(Experimental) public func lookup( _ identifier: Identifier?, at lookUpPosition: AbsolutePosition, @@ -170,21 +187,15 @@ import SwiftSyntax @_spi(Experimental) public var introducedNames: [LookupName] { LookupName.getNames(from: pattern) } + + @_spi(Experimental) public var scopeDebugName: String { + "ForStmtScope" + } } -@_spi(Experimental) extension ClosureExprSyntax: ScopeSyntax { - /// All names introduced by the closure signature. - /// Could be closure captures or (shorthand) parameters. - /// - /// ### Example - /// ```swift - /// let x = { [weak self, a] b, _ in - /// // <-- - /// } - /// ``` - /// During lookup, names available at the marked place are: - /// `self`, a, b. - @_spi(Experimental) public var introducedNames: [LookupName] { +@_spi(Experimental) extension ClosureExprSyntax: SequentialScopeSyntax { + /// Names introduced in this closure's signature. + var introducedNamesInSignature: [LookupName] { let captureNames = signature?.capture?.items.flatMap { element in LookupName.getNames(from: element) @@ -203,6 +214,54 @@ import SwiftSyntax return captureNames + parameterNames } + + /// Names introduced sequentially in body. + var introducedNamesInBody: [LookupName] { + statements.flatMap { codeBlockItem in + LookupName.getNames(from: codeBlockItem.item, accessibleAfter: codeBlockItem.endPosition) + } + } + + @_spi(Experimental) public var introducedNames: [LookupName] { + introducedNamesInSignature + introducedNamesInBody + } + + @_spi(Experimental) public var scopeDebugName: String { + "ClosureExprScope" + } + + /// All names introduced by the closure signature and its body. + /// Could be closure captures, (shorthand) parameters or + /// sequential results from the body. + /// + /// ### Example + /// ```swift + /// let x = { [weak self, a] b, _ in + /// let c = 42 + /// // <-- + /// let d = 42 + /// } + /// ``` + /// During lookup, names available at the marked place are: + /// `self`, `a`, `b` and `c`. + @_spi(Experimental) public func lookup( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig + ) -> [LookupResult] { + let filteredSignatureNames = introducedNamesInSignature.filter { name in + checkIdentifier(identifier, refersTo: name, at: lookUpPosition) + } + + return sequentialLookup( + in: statements, + identifier, + at: lookUpPosition, + with: config, + propagateToParent: false + ) + (filteredSignatureNames.isEmpty ? [] : [.fromScope(self, withNames: filteredSignatureNames)]) + + (config.finishInSequentialScope ? [] : lookupInParent(identifier, at: lookUpPosition, with: config)) + } } @_spi(Experimental) extension WhileStmtSyntax: ScopeSyntax { @@ -212,6 +271,10 @@ import SwiftSyntax LookupName.getNames(from: element.condition) } } + + @_spi(Experimental) public var scopeDebugName: String { + "WhileStmtScope" + } } @_spi(Experimental) extension IfExprSyntax: ScopeSyntax { @@ -253,11 +316,15 @@ import SwiftSyntax /// Names introduced by the `if` optional binding conditions. @_spi(Experimental) public var introducedNames: [LookupName] { - conditions.flatMap { element in + conditions.reversed().flatMap { element in LookupName.getNames(from: element.condition, accessibleAfter: element.endPosition) } } + @_spi(Experimental) public var scopeDebugName: String { + "IfExprScope" + } + /// Returns names matching lookup. /// Lookup triggered from inside of `else` /// clause is immediately forwarded to parent scope. @@ -291,6 +358,24 @@ import SwiftSyntax } } + @_spi(Experimental) public var scopeDebugName: String { + "MemberBlockScope" + } + + /// Lookup results from this member block scope. + /// Bypasses names from this scope if `includeMembers` set to `false`. + @_spi(Experimental) public func lookup( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig + ) -> [LookupResult] { + if config.includeMembers { + return defaultLookupImplementation(identifier, at: lookUpPosition, with: config) + } else { + return lookupInParent(identifier, at: lookUpPosition, with: config) + } + } + /// Creates a result from associated type declarations /// made by it's members. func lookupAssociatedTypeDeclarations( @@ -312,7 +397,7 @@ import SwiftSyntax @_spi(Experimental) extension GuardStmtSyntax: IntroducingToSequentialParentScopeSyntax { var namesIntroducedToSequentialParent: [LookupName] { - conditions.flatMap { element in + conditions.reversed().flatMap { element in LookupName.getNames(from: element.condition, accessibleAfter: element.endPosition) } } @@ -321,6 +406,10 @@ import SwiftSyntax [] } + @_spi(Experimental) public var scopeDebugName: String { + "GuardStmtScope" + } + /// Returns results matching lookup that should be /// interleaved with sequential parent's results. /// Lookup triggered from within of the `else` body @@ -348,11 +437,51 @@ import SwiftSyntax } } -@_spi(Experimental) extension ActorDeclSyntax: TypeScopeSyntax, WithGenericParametersScopeSyntax {} -@_spi(Experimental) extension ClassDeclSyntax: TypeScopeSyntax, WithGenericParametersScopeSyntax {} -@_spi(Experimental) extension StructDeclSyntax: TypeScopeSyntax, WithGenericParametersScopeSyntax {} -@_spi(Experimental) extension EnumDeclSyntax: TypeScopeSyntax, WithGenericParametersScopeSyntax {} -@_spi(Experimental) extension ExtensionDeclSyntax: TypeScopeSyntax {} +@_spi(Experimental) extension ActorDeclSyntax: NominalTypeDeclSyntax { + @_spi(Experimental) public var scopeDebugName: String { + "ActorDeclScope" + } +} +@_spi(Experimental) extension ClassDeclSyntax: NominalTypeDeclSyntax { + @_spi(Experimental) public var scopeDebugName: String { + "ClassDeclScope" + } +} +@_spi(Experimental) extension StructDeclSyntax: NominalTypeDeclSyntax { + @_spi(Experimental) public var scopeDebugName: String { + "StructDeclScope" + } +} +@_spi(Experimental) extension EnumDeclSyntax: NominalTypeDeclSyntax { + @_spi(Experimental) public var scopeDebugName: String { + "EnumDeclScope" + } +} +@_spi(Experimental) extension ExtensionDeclSyntax: LookInMembersScopeSyntax { + @_spi(Experimental) public var lookupMembersPosition: AbsolutePosition { + extendedType.position + } + + @_spi(Experimental) public var introducedNames: [LookupName] { + [] + } + + @_spi(Experimental) public var scopeDebugName: String { + "ExtensionDeclScope" + } + + @_spi(Experimental) public func lookup( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig + ) -> [LookupResult] { + if memberBlock.range.contains(lookUpPosition) { + return [.lookInMembers(self)] + defaultLookupImplementation(identifier, at: lookUpPosition, with: config) + } else { + return defaultLookupImplementation(identifier, at: lookUpPosition, with: config) + } + } +} @_spi(Experimental) extension AccessorDeclSyntax: ScopeSyntax { /// Implicit and/or explicit names introduced within the accessor. @@ -370,6 +499,10 @@ import SwiftSyntax } } } + + @_spi(Experimental) public var scopeDebugName: String { + "AccessorDeclScope" + } } @_spi(Experimental) extension CatchClauseSyntax: ScopeSyntax { @@ -377,6 +510,10 @@ import SwiftSyntax @_spi(Experimental) public var introducedNames: [LookupName] { return catchItems.isEmpty ? [.implicit(.error(self))] : [] } + + @_spi(Experimental) public var scopeDebugName: String { + "CatchClauseScope" + } } @_spi(Experimental) extension SwitchCaseSyntax: ScopeSyntax { @@ -386,6 +523,10 @@ import SwiftSyntax LookupName.getNames(from: child.pattern) } ?? [] } + + @_spi(Experimental) public var scopeDebugName: String { + "SwitchCaseScope" + } } @_spi(Experimental) extension ProtocolDeclSyntax: ScopeSyntax { @@ -394,6 +535,10 @@ import SwiftSyntax [] } + @_spi(Experimental) public var scopeDebugName: String { + "ProtocolDeclScope" + } + /// For the lookup initiated from inside primary /// associated type clause, this function also finds /// all associated type declarations made inside the @@ -437,9 +582,13 @@ import SwiftSyntax /// Generic parameter names introduced by this clause. @_spi(Experimental) public var introducedNames: [LookupName] { parameters.children(viewMode: .fixedUp).flatMap { child in - LookupName.getNames(from: child, accessibleAfter: child.endPosition) + LookupName.getNames(from: child) } } + + @_spi(Experimental) public var scopeDebugName: String { + "GenericParameterClauseScope" + } } @_spi(Experimental) extension FunctionDeclSyntax: WithGenericParametersScopeSyntax { @@ -447,7 +596,37 @@ import SwiftSyntax @_spi(Experimental) public var introducedNames: [LookupName] { signature.parameterClause.parameters.flatMap { parameter in LookupName.getNames(from: parameter) + } + (parentScope?.is(MemberBlockSyntax.self) ?? false ? [.implicit(.self(self))] : []) + } + + @_spi(Experimental) public var scopeDebugName: String { + "FunctionDeclScope" + } + + /// Lookup results from this function scope. + /// Routes to generic parameter clause scope if exists. + @_spi(Experimental) public func lookup( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig + ) -> [LookupResult] { + var thisScopeResults: [LookupResult] = [] + + if !signature.range.contains(lookUpPosition) { + thisScopeResults = defaultLookupImplementation( + identifier, + at: position, + with: config, + propagateToParent: false + ) } + + return thisScopeResults + + lookupThroughGenericParameterScope( + identifier, + at: lookUpPosition, + with: config + ) } } @@ -458,6 +637,10 @@ import SwiftSyntax LookupName.getNames(from: parameter) } } + + @_spi(Experimental) public var scopeDebugName: String { + "SubscriptDeclScope" + } } @_spi(Experimental) extension TypeAliasDeclSyntax: WithGenericParametersScopeSyntax { @@ -465,4 +648,8 @@ import SwiftSyntax @_spi(Experimental) public var introducedNames: [LookupName] { [] } + + @_spi(Experimental) public var scopeDebugName: String { + "TypeAliasDeclScope" + } } diff --git a/Sources/SwiftLexicalLookup/Scopes/ScopeSyntax.swift b/Sources/SwiftLexicalLookup/Scopes/ScopeSyntax.swift index c1b6c43d59e..e73f12a1aa0 100644 --- a/Sources/SwiftLexicalLookup/Scopes/ScopeSyntax.swift +++ b/Sources/SwiftLexicalLookup/Scopes/ScopeSyntax.swift @@ -56,6 +56,8 @@ extension SyntaxProtocol { var parentScope: ScopeSyntax? { get } /// Names found in this scope. Ordered from first to last introduced. var introducedNames: [LookupName] { get } + /// Debug description of this scope. + var scopeDebugName: String { get } /// Finds all declarations `identifier` refers to. `syntax` specifies the node lookup was triggered with. /// If `identifier` set to `nil`, returns all available names at the given node. func lookup( @@ -117,4 +119,9 @@ extension SyntaxProtocol { ) -> Bool { introducedName.isAccessible(at: lookUpPosition) && (identifier == nil || introducedName.identifier == identifier!) } + + /// Debug description of this scope. + @_spi(Experimental) public var scopeDebugDescription: String { + scopeDebugName + " " + debugLineWithColumnDescription + } } diff --git a/Sources/SwiftLexicalLookup/Scopes/SequentialScopeSyntax.swift b/Sources/SwiftLexicalLookup/Scopes/SequentialScopeSyntax.swift index da96821373e..a4fd0b0850e 100644 --- a/Sources/SwiftLexicalLookup/Scopes/SequentialScopeSyntax.swift +++ b/Sources/SwiftLexicalLookup/Scopes/SequentialScopeSyntax.swift @@ -42,7 +42,8 @@ extension SequentialScopeSyntax { in codeBlockItems: some Collection, _ identifier: Identifier?, at lookUpPosition: AbsolutePosition, - with config: LookupConfig + with config: LookupConfig, + propagateToParent: Bool = true ) -> [LookupResult] { // Sequential scope needs to ensure all type declarations are // available in the whole scope (first loop) and @@ -69,9 +70,12 @@ extension SequentialScopeSyntax { } } - for codeBlockItem in itemsWithoutNamedDecl { - guard codeBlockItem.position <= lookUpPosition else { break } + if !currentChunk.isEmpty { + results.append(LookupResult.getResult(for: self, withNames: currentChunk)) + currentChunk = [] + } + for codeBlockItem in itemsWithoutNamedDecl { if let introducingToParentScope = Syntax(codeBlockItem.item).asProtocol(SyntaxProtocol.self) as? IntroducingToSequentialParentScopeSyntax { @@ -87,7 +91,7 @@ extension SequentialScopeSyntax { // If there are some names collected, create a new result for this scope. if !currentChunk.isEmpty { - results.append(LookupResult.getResult(for: self, withNames: currentChunk)) + results.append(LookupResult.getResult(for: self, withNames: currentChunk.reversed())) currentChunk = [] } @@ -97,7 +101,7 @@ extension SequentialScopeSyntax { currentChunk += LookupName.getNames( from: codeBlockItem.item, accessibleAfter: codeBlockItem.endPosition - ).filter { introducedName in + ).reversed().filter { introducedName in checkIdentifier(identifier, refersTo: introducedName, at: lookUpPosition) } } @@ -105,9 +109,11 @@ extension SequentialScopeSyntax { // If there are some names collected, create a new result for this scope. if !currentChunk.isEmpty { - results.append(LookupResult.getResult(for: self, withNames: currentChunk)) + results.append(LookupResult.getResult(for: self, withNames: currentChunk.reversed())) } - return results.reversed() + lookupInParent(identifier, at: lookUpPosition, with: config) + return results.reversed() + + (config.finishInSequentialScope || !propagateToParent + ? [] : lookupInParent(identifier, at: lookUpPosition, with: config)) } } diff --git a/Sources/SwiftLexicalLookup/Scopes/WithGenericParametersScopeSyntax.swift b/Sources/SwiftLexicalLookup/Scopes/WithGenericParametersScopeSyntax.swift index 5ab700bfe4b..be6582c5005 100644 --- a/Sources/SwiftLexicalLookup/Scopes/WithGenericParametersScopeSyntax.swift +++ b/Sources/SwiftLexicalLookup/Scopes/WithGenericParametersScopeSyntax.swift @@ -12,8 +12,14 @@ import SwiftSyntax -@_spi(Experimental) public protocol WithGenericParametersScopeSyntax: ScopeSyntax { +protocol WithGenericParametersScopeSyntax: ScopeSyntax { var genericParameterClause: GenericParameterClauseSyntax? { get } + + func returningLookupFromGenericParameterScope( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig + ) -> [LookupResult] } @_spi(Experimental) extension WithGenericParametersScopeSyntax { @@ -67,7 +73,7 @@ import SwiftSyntax /// function declaration scope and then to generic parameter /// scope (`WithGenericParametersScopeSyntax`) /// with this method (instead of using standard `lookupInParent`). - private func lookupThroughGenericParameterScope( + func lookupThroughGenericParameterScope( _ identifier: Identifier?, at lookUpPosition: AbsolutePosition, with config: LookupConfig @@ -75,7 +81,15 @@ import SwiftSyntax if let genericParameterClause { return genericParameterClause.lookup(identifier, at: lookUpPosition, with: config) } else { - return lookupInParent(identifier, at: lookUpPosition, with: config) + return returningLookupFromGenericParameterScope(identifier, at: lookUpPosition, with: config) } } + + func returningLookupFromGenericParameterScope( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig + ) -> [LookupResult] { + lookupInParent(identifier, at: lookUpPosition, with: config) + } } diff --git a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift index ff96b8230d9..3def8550845 100644 --- a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift +++ b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift @@ -140,7 +140,7 @@ final class testNameLookup: XCTestCase { assertLexicalNameLookup( source: """ 7️⃣class a { - func foo() { + 9️⃣func foo() { let 1️⃣a = 1 let x = { [2️⃣weak self, 3️⃣a, 4️⃣unowned b] in print(5️⃣self, 6️⃣a, 8️⃣b) @@ -152,20 +152,26 @@ final class testNameLookup: XCTestCase { references: [ "5️⃣": [ .fromScope(ClosureExprSyntax.self, expectedNames: [NameExpectation.identifier("2️⃣")]), - .fromScope(ClassDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("7️⃣"))]), + .fromScope(FunctionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("9️⃣"))]), + .lookInMembers(ClassDeclSyntax.self), ], "6️⃣": [ .fromScope(ClosureExprSyntax.self, expectedNames: ["3️⃣"]), .fromScope(CodeBlockSyntax.self, expectedNames: ["1️⃣"]), + .lookInMembers(ClassDeclSyntax.self), .fromFileScope(expectedNames: ["7️⃣"]), ], - "8️⃣": [.fromScope(ClosureExprSyntax.self, expectedNames: ["4️⃣"])], + "8️⃣": [ + .fromScope(ClosureExprSyntax.self, expectedNames: ["4️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], ], expectedResultTypes: .all( ClosureCaptureSyntax.self, except: [ "1️⃣": IdentifierPatternSyntax.self, "7️⃣": ClassDeclSyntax.self, + "9️⃣": FunctionDeclSyntax.self, ] ) ) @@ -309,10 +315,22 @@ final class testNameLookup: XCTestCase { } """, references: [ - "5️⃣": [.fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "4️⃣"])], - "6️⃣": [.fromScope(MemberBlockSyntax.self, expectedNames: ["2️⃣", "3️⃣"])], - "7️⃣": [.fromScope(MemberBlockSyntax.self, expectedNames: ["9️⃣"])], - "8️⃣": [.fromScope(MemberBlockSyntax.self, expectedNames: ["0️⃣"])], + "5️⃣": [ + .fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "4️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "6️⃣": [ + .fromScope(MemberBlockSyntax.self, expectedNames: ["2️⃣", "3️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "7️⃣": [ + .fromScope(MemberBlockSyntax.self, expectedNames: ["9️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "8️⃣": [ + .fromScope(MemberBlockSyntax.self, expectedNames: ["0️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], ], expectedResultTypes: .distinct([ "1️⃣": IdentifierPatternSyntax.self, @@ -343,17 +361,28 @@ final class testNameLookup: XCTestCase { } """, references: [ - "2️⃣": [.fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "9️⃣"])], - "0️⃣": [.fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "9️⃣"])], - "4️⃣": [.fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "9️⃣"])], + "2️⃣": [ + .fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "9️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "0️⃣": [ + .fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "9️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "4️⃣": [ + .fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "9️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], "6️⃣": [ .fromScope(CodeBlockSyntax.self, expectedNames: ["3️⃣"]), .fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "9️⃣"]), + .lookInMembers(ClassDeclSyntax.self), ], "8️⃣": [ .fromScope(IfExprSyntax.self, expectedNames: ["5️⃣"]), .fromScope(CodeBlockSyntax.self, expectedNames: ["3️⃣"]), .fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "9️⃣"]), + .lookInMembers(ClassDeclSyntax.self), ], ], expectedResultTypes: .all( @@ -408,23 +437,25 @@ final class testNameLookup: XCTestCase { """, references: [ "7️⃣": [ - .fromScope(CodeBlockSyntax.self, expectedNames: ["4️⃣", "5️⃣"]), - .fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "2️⃣", "3️⃣"]), + .fromScope(CodeBlockSyntax.self, expectedNames: ["5️⃣", "4️⃣"]), .fromScope( - ClassDeclSyntax.self, - expectedNames: [NameExpectation.implicit(.self("🔟")), NameExpectation.implicit(.Self("🔟"))] + FunctionDeclSyntax.self, + expectedNames: [NameExpectation.implicit(.self("3️⃣"))] ), + .fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "2️⃣", "3️⃣"]), + .lookInMembers(ClassDeclSyntax.self), .fromFileScope(expectedNames: ["🔟"]), ], "0️⃣": [ .fromScope(CodeBlockSyntax.self, expectedNames: ["8️⃣", "9️⃣"]), .fromScope(IfExprSyntax.self, expectedNames: ["6️⃣"]), - .fromScope(CodeBlockSyntax.self, expectedNames: ["4️⃣", "5️⃣"]), - .fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "2️⃣", "3️⃣"]), + .fromScope(CodeBlockSyntax.self, expectedNames: ["5️⃣", "4️⃣"]), .fromScope( - ClassDeclSyntax.self, - expectedNames: [NameExpectation.implicit(.self("🔟")), NameExpectation.implicit(.Self("🔟"))] + FunctionDeclSyntax.self, + expectedNames: [NameExpectation.implicit(.self("3️⃣"))] ), + .fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "2️⃣", "3️⃣"]), + .lookInMembers(ClassDeclSyntax.self), .fromFileScope(expectedNames: ["🔟"]), ], ], @@ -483,7 +514,7 @@ final class testNameLookup: XCTestCase { .fromScope(CodeBlockSyntax.self, expectedNames: ["1️⃣"]), ], "6️⃣": [ - .fromScope(GuardStmtSyntax.self, expectedNames: ["2️⃣", "4️⃣"]), + .fromScope(GuardStmtSyntax.self, expectedNames: ["4️⃣", "2️⃣"]), .fromScope(CodeBlockSyntax.self, expectedNames: ["1️⃣"]), ], ], @@ -513,10 +544,22 @@ final class testNameLookup: XCTestCase { let 🔟a = 0️⃣d """, references: [ - "3️⃣": [.fromFileScope(expectedNames: ["1️⃣", "8️⃣"])], - "4️⃣": [.fromFileScope(expectedNames: ["2️⃣"])], - "5️⃣": [.fromFileScope(expectedNames: ["7️⃣"])], - "6️⃣": [.fromFileScope(expectedNames: ["9️⃣"])], + "3️⃣": [ + .lookInMembers(ClassDeclSyntax.self), + .fromFileScope(expectedNames: ["1️⃣", "8️⃣"]), + ], + "4️⃣": [ + .lookInMembers(ClassDeclSyntax.self), + .fromFileScope(expectedNames: ["2️⃣"]), + ], + "5️⃣": [ + .lookInMembers(ClassDeclSyntax.self), + .fromFileScope(expectedNames: ["7️⃣"]), + ], + "6️⃣": [ + .lookInMembers(ClassDeclSyntax.self), + .fromFileScope(expectedNames: ["9️⃣"]), + ], "0️⃣": [.fromFileScope(expectedNames: ["9️⃣"])], ], expectedResultTypes: .all(ClassDeclSyntax.self, except: ["8️⃣": IdentifierPatternSyntax.self]) @@ -543,10 +586,22 @@ final class testNameLookup: XCTestCase { let 🔟a = 0️⃣d """, references: [ - "3️⃣": [.fromFileScope(expectedNames: ["1️⃣", "8️⃣", "🔟"])], - "4️⃣": [.fromFileScope(expectedNames: ["2️⃣"])], - "5️⃣": [.fromFileScope(expectedNames: ["7️⃣"])], - "6️⃣": [.fromFileScope(expectedNames: ["9️⃣"])], + "3️⃣": [ + .lookInMembers(ClassDeclSyntax.self), + .fromFileScope(expectedNames: ["1️⃣", "8️⃣", "🔟"]), + ], + "4️⃣": [ + .lookInMembers(ClassDeclSyntax.self), + .fromFileScope(expectedNames: ["2️⃣"]), + ], + "5️⃣": [ + .lookInMembers(ClassDeclSyntax.self), + .fromFileScope(expectedNames: ["7️⃣"]), + ], + "6️⃣": [ + .lookInMembers(ClassDeclSyntax.self), + .fromFileScope(expectedNames: ["9️⃣"]), + ], "0️⃣": [.fromFileScope(expectedNames: ["9️⃣"])], ], expectedResultTypes: .all( @@ -605,29 +660,35 @@ final class testNameLookup: XCTestCase { func testImplicitSelf() { assertLexicalNameLookup( source: """ - 1️⃣extension a { - 2️⃣struct b { - func foo() { + extension a { + struct b { + 2️⃣func foo() { let x: 3️⃣Self = 4️⃣self } } - func bar() { + 1️⃣func bar() { let x: 5️⃣Self = 6️⃣self } } """, references: [ "3️⃣": [ - .fromScope(StructDeclSyntax.self, expectedNames: [NameExpectation.implicit(.Self("2️⃣"))]), - .fromScope(ExtensionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.Self("1️⃣"))]), + .lookInMembers(StructDeclSyntax.self), + .lookInMembers(ExtensionDeclSyntax.self), ], "4️⃣": [ - .fromScope(StructDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("2️⃣"))]), - .fromScope(ExtensionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("1️⃣"))]), + .fromScope(FunctionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("2️⃣"))]), + .lookInMembers(StructDeclSyntax.self), + .lookInMembers(ExtensionDeclSyntax.self), + ], + "5️⃣": [ + .lookInMembers(ExtensionDeclSyntax.self) + ], + "6️⃣": [ + .fromScope(FunctionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("1️⃣"))]), + .lookInMembers(ExtensionDeclSyntax.self), ], - "5️⃣": [.fromScope(ExtensionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.Self("1️⃣"))])], - "6️⃣": [.fromScope(ExtensionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("1️⃣"))])], ] ) } @@ -677,16 +738,16 @@ final class testNameLookup: XCTestCase { func testBacktickCompatibility() { assertLexicalNameLookup( source: """ - 1️⃣struct Foo { - func test() { + struct Foo { + 1️⃣func test() { let 2️⃣`self` = 1 print(3️⃣self) print(4️⃣`self`) } } - 5️⃣struct Bar { - func test() { + struct Bar { + 5️⃣func test() { print(6️⃣self) let 7️⃣`self` = 1 print(8️⃣`self`) @@ -696,18 +757,22 @@ final class testNameLookup: XCTestCase { references: [ "3️⃣": [ .fromScope(CodeBlockSyntax.self, expectedNames: [NameExpectation.identifier("2️⃣")]), - .fromScope(StructDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("1️⃣"))]), + .fromScope(FunctionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("1️⃣"))]), + .lookInMembers(StructDeclSyntax.self), ], "4️⃣": [ .fromScope(CodeBlockSyntax.self, expectedNames: [NameExpectation.identifier("2️⃣")]), - .fromScope(StructDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("1️⃣"))]), + .fromScope(FunctionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("1️⃣"))]), + .lookInMembers(StructDeclSyntax.self), ], "6️⃣": [ - .fromScope(StructDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("5️⃣"))]) + .fromScope(FunctionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("5️⃣"))]), + .lookInMembers(StructDeclSyntax.self), ], "8️⃣": [ .fromScope(CodeBlockSyntax.self, expectedNames: [NameExpectation.identifier("7️⃣")]), - .fromScope(StructDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("5️⃣"))]), + .fromScope(FunctionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("5️⃣"))]), + .lookInMembers(StructDeclSyntax.self), ], ] ) @@ -716,8 +781,8 @@ final class testNameLookup: XCTestCase { func testImplicitSelfOverride() { assertLexicalNameLookup( source: """ - 1️⃣class Foo { - func test() { + class Foo { + 1️⃣func test() { let 2️⃣`Self` = "abc" print(3️⃣Self.self) @@ -729,11 +794,12 @@ final class testNameLookup: XCTestCase { references: [ "3️⃣": [ .fromScope(CodeBlockSyntax.self, expectedNames: [NameExpectation.identifier("2️⃣")]), - .fromScope(ClassDeclSyntax.self, expectedNames: [NameExpectation.implicit(.Self("1️⃣"))]), + .lookInMembers(ClassDeclSyntax.self), ], "5️⃣": [ .fromScope(CodeBlockSyntax.self, expectedNames: [NameExpectation.identifier("4️⃣")]), - .fromScope(ClassDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("1️⃣"))]), + .fromScope(FunctionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("1️⃣"))]), + .lookInMembers(ClassDeclSyntax.self), ], ] ) @@ -825,7 +891,7 @@ final class testNameLookup: XCTestCase { references: [ "7️⃣": [ .fromScope(GuardStmtSyntax.self, expectedNames: ["6️⃣"]), - .fromScope(CodeBlockSyntax.self, expectedNames: ["1️⃣", "4️⃣"]), + .fromScope(CodeBlockSyntax.self, expectedNames: ["4️⃣", "1️⃣"]), ], "8️⃣": [ .fromScope(CodeBlockSyntax.self, expectedNames: ["5️⃣"]), @@ -876,13 +942,25 @@ final class testNameLookup: XCTestCase { } """, references: [ - "3️⃣": [.fromScope(GenericParameterClauseSyntax.self, expectedNames: ["1️⃣"])], - "4️⃣": [.fromScope(GenericParameterClauseSyntax.self, expectedNames: ["2️⃣"])], + "3️⃣": [ + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["1️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "4️⃣": [ + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["2️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], "6️⃣": [ .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["5️⃣"]), + .lookInMembers(ClassDeclSyntax.self), .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["1️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "8️⃣": [ + .lookInMembers(ClassDeclSyntax.self), + .fromScope(MemberBlockSyntax.self, expectedNames: ["7️⃣"]), + .lookInMembers(ClassDeclSyntax.self), ], - "8️⃣": [.fromScope(MemberBlockSyntax.self, expectedNames: ["7️⃣"])], ], expectedResultTypes: .all(GenericParameterSyntax.self, except: ["7️⃣": IdentifierPatternSyntax.self]) ) @@ -891,13 +969,25 @@ final class testNameLookup: XCTestCase { func testGenericParameterOrdering() { assertLexicalNameLookup( source: """ - class Foo<1️⃣A: 2️⃣A, B: 3️⃣A, 4️⃣C: 5️⃣D, D: 6️⃣C> {} + class Foo<1️⃣A: 2️⃣A, B: 3️⃣A, 4️⃣C: 5️⃣D, 7️⃣D: 6️⃣C> {} """, references: [ - "2️⃣": [], - "3️⃣": [.fromScope(GenericParameterClauseSyntax.self, expectedNames: ["1️⃣"])], - "5️⃣": [], - "6️⃣": [.fromScope(GenericParameterClauseSyntax.self, expectedNames: ["4️⃣"])], + "2️⃣": [ + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["1️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "3️⃣": [ + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["1️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "5️⃣": [ + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["7️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "6️⃣": [ + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["4️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], ], expectedResultTypes: .all(GenericParameterSyntax.self) ) @@ -949,14 +1039,25 @@ final class testNameLookup: XCTestCase { "6️⃣": [ .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["3️⃣"]), .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["1️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "8️⃣": [ + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["4️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "9️⃣": [ + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["4️⃣"]), + .lookInMembers(ClassDeclSyntax.self), ], - "8️⃣": [.fromScope(GenericParameterClauseSyntax.self, expectedNames: ["4️⃣"])], - "9️⃣": [.fromScope(GenericParameterClauseSyntax.self, expectedNames: ["4️⃣"])], "0️⃣": [ .fromScope(FunctionDeclSyntax.self, expectedNames: ["5️⃣"]), .fromScope(MemberBlockSyntax.self, expectedNames: ["2️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "🔟": [ + .fromScope(FunctionDeclSyntax.self, expectedNames: ["7️⃣"]), + .lookInMembers(ClassDeclSyntax.self), ], - "🔟": [.fromScope(FunctionDeclSyntax.self, expectedNames: ["7️⃣"])], ], expectedResultTypes: .all( GenericParameterSyntax.self, @@ -981,12 +1082,30 @@ final class testNameLookup: XCTestCase { } """, references: [ - "4️⃣": [.fromScope(GenericParameterClauseSyntax.self, expectedNames: ["1️⃣"])], - "6️⃣": [.fromScope(GenericParameterClauseSyntax.self, expectedNames: ["2️⃣"])], - "7️⃣": [.fromScope(GenericParameterClauseSyntax.self, expectedNames: ["2️⃣"])], - "8️⃣": [.fromScope(SubscriptDeclSyntax.self, expectedNames: ["3️⃣"])], - "9️⃣": [.fromScope(SubscriptDeclSyntax.self, expectedNames: ["5️⃣"])], - "🔟": [.fromScope(MemberBlockSyntax.self, expectedNames: ["0️⃣"])], + "4️⃣": [ + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["1️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "6️⃣": [ + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["2️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "7️⃣": [ + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["2️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "8️⃣": [ + .fromScope(SubscriptDeclSyntax.self, expectedNames: ["3️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "9️⃣": [ + .fromScope(SubscriptDeclSyntax.self, expectedNames: ["5️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], + "🔟": [ + .fromScope(MemberBlockSyntax.self, expectedNames: ["0️⃣"]), + .lookInMembers(ClassDeclSyntax.self), + ], ], expectedResultTypes: .all( GenericParameterSyntax.self, diff --git a/Tests/SwiftLexicalLookupTest/ResultExpectation.swift b/Tests/SwiftLexicalLookupTest/ResultExpectation.swift index b4a8c62f6db..89f30f1ef3a 100644 --- a/Tests/SwiftLexicalLookupTest/ResultExpectation.swift +++ b/Tests/SwiftLexicalLookupTest/ResultExpectation.swift @@ -18,6 +18,7 @@ import XCTest enum ResultExpectation { case fromScope(ScopeSyntax.Type, expectedNames: [ExpectedName]) case fromFileScope(expectedNames: [ExpectedName]) + case lookInMembers(LookInMembersScopeSyntax.Type) var expectedNames: [ExpectedName] { switch self { @@ -25,6 +26,8 @@ enum ResultExpectation { return expectedNames case .fromFileScope(expectedNames: let expectedNames): return expectedNames + case .lookInMembers: + return [] } } @@ -34,6 +37,8 @@ enum ResultExpectation { return "fromScope" case .fromFileScope: return "fromFileScope" + case .lookInMembers: + return "lookInMembers" } } @@ -57,6 +62,11 @@ enum ResultExpectation { NameExpectation.assertNames(marker: marker, acutalNames: actualNames, expectedNames: expectedNames) case (.fromFileScope(_, let actualNames), .fromFileScope(let expectedNames)): NameExpectation.assertNames(marker: marker, acutalNames: actualNames, expectedNames: expectedNames) + case (.lookInMembers(let scope), .lookInMembers(let expectedType)): + XCTAssert( + scope.syntaxNodeType == expectedType, + "For marker \(marker), scope result type of \(scope.syntaxNodeType) doesn't match expected \(expectedType)" + ) default: XCTFail( "For marker \(marker), actual result kind \(actual.debugDescription) doesn't match expected \(expected.debugDescription)" @@ -65,14 +75,3 @@ enum ResultExpectation { } } } - -extension LookupResult { - var debugDescription: String { - switch self { - case .fromScope: - return "fromScope" - case .fromFileScope: - return "fromFileScope" - } - } -} From 7521e4e0a33aeb7aa05b109628701682250c67e0 Mon Sep 17 00:00:00 2001 From: Jakub Florek Date: Wed, 18 Sep 2024 14:52:00 +0200 Subject: [PATCH 2/8] Add sequential handling of `subscript` and switch `case` scopes. Remove wild card filtering. --- Sources/SwiftLexicalLookup/LookupName.swift | 36 ++--- .../Scopes/NominalTypeDeclSyntax.swift | 5 + .../Scopes/ScopeImplementations.swift | 150 ++++++++++++++++-- .../NameLookupTests.swift | 18 +-- 4 files changed, 167 insertions(+), 42 deletions(-) diff --git a/Sources/SwiftLexicalLookup/LookupName.swift b/Sources/SwiftLexicalLookup/LookupName.swift index 105a5ef655b..1a7bc9291d0 100644 --- a/Sources/SwiftLexicalLookup/LookupName.swift +++ b/Sources/SwiftLexicalLookup/LookupName.swift @@ -17,7 +17,7 @@ import SwiftSyntax /// `self` keyword representing object instance. /// Could be associated with type declaration, extension, /// or closure captures. Introduced at function edge. - case `self`(FunctionDeclSyntax) + case `self`(DeclSyntaxProtocol) /// `Self` keyword representing object type. /// Could be associated with type declaration or extension. case `Self`(DeclSyntaxProtocol) @@ -136,7 +136,7 @@ import SwiftSyntax } /// Position of this name. - /// + /// /// For some syntax nodes, their position doesn't reflect /// the position at which a particular name was introduced at. /// Such cases are function parameters (as they can @@ -145,21 +145,24 @@ import SwiftSyntax @_spi(Experimental) public var position: AbsolutePosition { switch self { case .identifier(let syntax, _): - switch Syntax(syntax).as(SyntaxEnum.self) { - case .functionParameter(let functionParameter): - return functionParameter.secondName?.positionAfterSkippingLeadingTrivia - ?? functionParameter.firstName.positionAfterSkippingLeadingTrivia - default: - return syntax.position - } + return syntax.identifier.positionAfterSkippingLeadingTrivia case .declaration(let syntax): return syntax.name.position case .implicit(let implicitName): switch implicitName { - case .self(let functionDecl): - return functionDecl.name.position + case .self(let declSyntax): + switch Syntax(declSyntax).as(SyntaxEnum.self) { + case .functionDecl(let functionDecl): + return functionDecl.name.position + case .subscriptDecl(let subscriptDecl): + return subscriptDecl.accessorBlock?.position ?? subscriptDecl.endPosition + default: + return declSyntax.positionAfterSkippingLeadingTrivia + } + case .error(let catchClause): + return catchClause.body.position default: - return implicitName.syntax.position + return implicitName.syntax.positionAfterSkippingLeadingTrivia } } } @@ -195,7 +198,7 @@ import SwiftSyntax ) -> [LookupName] { switch Syntax(syntax).as(SyntaxEnum.self) { case .variableDecl(let variableDecl): - return variableDecl.bindings.flatMap { binding in + return variableDecl.bindings.reversed().flatMap { binding in getNames( from: binding.pattern, accessibleAfter: accessibleAfter != nil ? binding.endPositionBeforeTrailingTrivia : nil @@ -239,12 +242,7 @@ import SwiftSyntax identifiable: IdentifiableSyntax, accessibleAfter: AbsolutePosition? = nil ) -> [LookupName] { - switch identifiable.identifier.tokenKind { - case .wildcard: - return [] - default: - return [.identifier(identifiable, accessibleAfter: accessibleAfter)] - } + [.identifier(identifiable, accessibleAfter: accessibleAfter)] } /// Extracts name introduced by `NamedDeclSyntax` node. diff --git a/Sources/SwiftLexicalLookup/Scopes/NominalTypeDeclSyntax.swift b/Sources/SwiftLexicalLookup/Scopes/NominalTypeDeclSyntax.swift index 7a1ea5a85af..f509486881b 100644 --- a/Sources/SwiftLexicalLookup/Scopes/NominalTypeDeclSyntax.swift +++ b/Sources/SwiftLexicalLookup/Scopes/NominalTypeDeclSyntax.swift @@ -13,6 +13,7 @@ import SwiftSyntax protocol NominalTypeDeclSyntax: LookInMembersScopeSyntax, NamedDeclSyntax, WithGenericParametersScopeSyntax { + var genericParameterClause: GenericParameterClauseSyntax? { get } var inheritanceClause: InheritanceClauseSyntax? { get } } @@ -35,6 +36,10 @@ extension NominalTypeDeclSyntax { ) -> [LookupResult] { if let inheritanceClause, inheritanceClause.range.contains(lookUpPosition) { return lookupInParent(identifier, at: lookUpPosition, with: config) + } else if let genericParameterClause, genericParameterClause.range.contains(lookUpPosition) { + return lookupInParent(identifier, at: lookUpPosition, with: config) + } else if name.range.contains(lookUpPosition) { + return lookupInParent(identifier, at: lookUpPosition, with: config) } else { return [.lookInMembers(self)] + lookupInParent(identifier, at: lookUpPosition, with: config) } diff --git a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift index 1a0cb9521de..9f2c63dc69d 100644 --- a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift +++ b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift @@ -191,6 +191,21 @@ import SwiftSyntax @_spi(Experimental) public var scopeDebugName: String { "ForStmtScope" } + + /// Returns results with names matching lookup. + /// Doesn't include names introduced at this scope + /// if lookup started inside it's `pattern` or `sequence`. + @_spi(Experimental) public func lookup( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig + ) -> [LookupResult] { + if pattern.range.contains(lookUpPosition) || sequence.range.contains(lookUpPosition) { + return lookupInParent(identifier, at: lookUpPosition, with: config) + } else { + return defaultLookupImplementation(identifier, at: lookUpPosition, with: config) + } + } } @_spi(Experimental) extension ClosureExprSyntax: SequentialScopeSyntax { @@ -249,8 +264,14 @@ import SwiftSyntax at lookUpPosition: AbsolutePosition, with config: LookupConfig ) -> [LookupResult] { - let filteredSignatureNames = introducedNamesInSignature.filter { name in - checkIdentifier(identifier, refersTo: name, at: lookUpPosition) + let filteredSignatureNames: [LookupName] + + if let signature, signature.range.contains(lookUpPosition) { + filteredSignatureNames = [] + } else { + filteredSignatureNames = introducedNamesInSignature.filter { name in + checkIdentifier(identifier, refersTo: name, at: lookUpPosition) + } } return sequentialLookup( @@ -267,8 +288,8 @@ import SwiftSyntax @_spi(Experimental) extension WhileStmtSyntax: ScopeSyntax { /// Names introduced by the `while` loop by its conditions. @_spi(Experimental) public var introducedNames: [LookupName] { - conditions.flatMap { element in - LookupName.getNames(from: element.condition) + conditions.reversed().flatMap { element in + LookupName.getNames(from: element.condition, accessibleAfter: element.endPositionBeforeTrailingTrivia) } } @@ -486,18 +507,26 @@ import SwiftSyntax @_spi(Experimental) extension AccessorDeclSyntax: ScopeSyntax { /// Implicit and/or explicit names introduced within the accessor. @_spi(Experimental) public var introducedNames: [LookupName] { + let names: [LookupName] + if let parameters { - return LookupName.getNames(from: parameters) + names = LookupName.getNames(from: parameters) } else { switch accessorSpecifier.tokenKind { case .keyword(.set), .keyword(.willSet): - return [.implicit(.newValue(self))] + names = [.implicit(.newValue(self))] case .keyword(.didSet): - return [.implicit(.oldValue(self))] + names = [.implicit(.oldValue(self))] default: - return [] + names = [] } } + + if let parentScope, parentScope.is(AccessorBlockSyntax.self) { + return names + [.implicit(.self(self))] + } else { + return names + } } @_spi(Experimental) public var scopeDebugName: String { @@ -516,17 +545,47 @@ import SwiftSyntax } } -@_spi(Experimental) extension SwitchCaseSyntax: ScopeSyntax { +@_spi(Experimental) extension SwitchCaseSyntax: SequentialScopeSyntax { /// Names introduced within `case` items. - @_spi(Experimental) public var introducedNames: [LookupName] { + var namesFromLabel: [LookupName] { label.as(SwitchCaseLabelSyntax.self)?.caseItems.flatMap { child in LookupName.getNames(from: child.pattern) } ?? [] } + /// Names introduced within `case` items + /// as well as sequential names from inside this case. + @_spi(Experimental) public var introducedNames: [LookupName] { + statements.flatMap { codeBlockItem in + LookupName.getNames(from: codeBlockItem.item, accessibleAfter: codeBlockItem.endPosition) + } + namesFromLabel + } + @_spi(Experimental) public var scopeDebugName: String { "SwitchCaseScope" } + + /// Returns results with names matching lookup. + /// Includes names introduced in it's label and sequentially + /// introduced names from inside this case. + @_spi(Experimental) public func lookup( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig + ) -> [LookupResult] { + let filteredNamesFromLabel = namesFromLabel.filter { name in + checkIdentifier(identifier, refersTo: name, at: lookUpPosition) + } + + return sequentialLookup( + in: statements, + identifier, + at: lookUpPosition, + with: config, + propagateToParent: false + ) + (filteredNamesFromLabel.isEmpty ? [] : [.fromScope(self, withNames: filteredNamesFromLabel)]) + + lookupInParent(identifier, at: lookUpPosition, with: config) + } } @_spi(Experimental) extension ProtocolDeclSyntax: ScopeSyntax { @@ -631,16 +690,83 @@ import SwiftSyntax } @_spi(Experimental) extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax { - /// Parameters introduced by this subscript. + /// Parameters introduced by this subscript and possibly `self` keyword. @_spi(Experimental) public var introducedNames: [LookupName] { - parameterClause.parameters.flatMap { parameter in + let parameters = parameterClause.parameters.flatMap { parameter in LookupName.getNames(from: parameter) } + + if let accessorBlock, case .getter = accessorBlock.accessors { + return parameters + [.implicit(.self(self))] + } else { + return parameters + } } @_spi(Experimental) public var scopeDebugName: String { "SubscriptDeclScope" } + + /// Lookup results from this subscript scope. + /// Routes to generic parameter clause scope if exists. + @_spi(Experimental) public func lookup( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig + ) -> [LookupResult] { + var thisScopeResults: [LookupResult] = [] + + if !parameterClause.range.contains(lookUpPosition) && !returnClause.range.contains(lookUpPosition) { + thisScopeResults = defaultLookupImplementation( + identifier, + at: position, + with: config, + propagateToParent: false + ) + } + + return thisScopeResults + + lookupThroughGenericParameterScope( + identifier, + at: lookUpPosition, + with: config + ) + } +} + +@_spi(Experimental) extension AccessorBlockSyntax: SequentialScopeSyntax { + /// Names from the accessors or + /// getters of this accessor block scope. + @_spi(Experimental) public var introducedNames: [LookupName] { + switch accessors { + case .getter(let codeBlockItems): + return codeBlockItems.flatMap { codeBlockItem in + LookupName.getNames(from: codeBlockItem.item) + } + case .accessors: + return [] + } + } + + @_spi(Experimental) public var scopeDebugName: String { + "AccessorBlockScope" + } + + /// Names introduced in this accessir block scope. + /// If `accessor` is of `.getter` kind, introduced + /// it's items sequentially. Otherwise, propagate to parent. + @_spi(Experimental) public func lookup( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig + ) -> [LookupResult] { + switch accessors { + case .getter(let codeBlockItems): + return sequentialLookup(in: codeBlockItems, identifier, at: lookUpPosition, with: config) + case .accessors: + return lookupInParent(identifier, at: lookUpPosition, with: config) + } + } } @_spi(Experimental) extension TypeAliasDeclSyntax: WithGenericParametersScopeSyntax { diff --git a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift index 3def8550845..2dbaf4dbe16 100644 --- a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift +++ b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift @@ -633,7 +633,7 @@ final class testNameLookup: XCTestCase { ) } - func testGuardOnFileScope() { + func testGuardOnFileScope() { // TODO: Fix this according to ASTScope (ommiting class a) assertLexicalNameLookup( source: """ let 1️⃣a = 0 @@ -902,7 +902,7 @@ final class testNameLookup: XCTestCase { ) } - func testSwitchExpression() { + func testSwitchExpression() { // TODO: For some reason ASTScope doesn't introduce any results besides first function call expr. assertLexicalNameLookup( source: """ switch { @@ -973,20 +973,16 @@ final class testNameLookup: XCTestCase { """, references: [ "2️⃣": [ - .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["1️⃣"]), - .lookInMembers(ClassDeclSyntax.self), + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["1️⃣"]) ], "3️⃣": [ - .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["1️⃣"]), - .lookInMembers(ClassDeclSyntax.self), + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["1️⃣"]) ], "5️⃣": [ - .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["7️⃣"]), - .lookInMembers(ClassDeclSyntax.self), + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["7️⃣"]) ], "6️⃣": [ - .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["4️⃣"]), - .lookInMembers(ClassDeclSyntax.self), + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["4️⃣"]) ], ], expectedResultTypes: .all(GenericParameterSyntax.self) @@ -1070,7 +1066,7 @@ final class testNameLookup: XCTestCase { ) } - func testSubscript() { + func testSubscript() { // TODO: Fix behavior of self keyword in subscript with accessors. assertLexicalNameLookup( source: """ class X { From fae4d0e9df7ea8a30f79f0b3336c5dad915757e2 Mon Sep 17 00:00:00 2001 From: Jakub Florek Date: Wed, 18 Sep 2024 19:40:31 +0200 Subject: [PATCH 3/8] Fix ordering of implicit `self` introduction by subscript accessors. --- .../Scopes/ScopeImplementations.swift | 95 +++++++++++++++++-- 1 file changed, 85 insertions(+), 10 deletions(-) diff --git a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift index 9f2c63dc69d..ea7cc1636d6 100644 --- a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift +++ b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift @@ -112,10 +112,10 @@ import SwiftSyntax at lookUpPosition: AbsolutePosition, with config: LookupConfig ) -> [LookupResult] { - guard config.includeMembers else { return [] } - switch config.fileScopeHandling { case .memberBlock: + guard config.includeMembers else { return [] } + let names = introducedNames(using: .memberBlock) .filter { lookupName in checkIdentifier(identifier, refersTo: lookupName, at: lookUpPosition) @@ -150,7 +150,7 @@ import SwiftSyntax with: config ) - return (members.isEmpty ? [] : [.fromFileScope(self, withNames: members)]) + sequentialNames + return (members.isEmpty || !config.includeMembers ? [] : [.fromFileScope(self, withNames: members)]) + sequentialNames } } } @@ -522,16 +522,38 @@ import SwiftSyntax } } - if let parentScope, parentScope.is(AccessorBlockSyntax.self) { - return names + [.implicit(.self(self))] - } else { - return names - } + return names } @_spi(Experimental) public var scopeDebugName: String { "AccessorDeclScope" } + + /// Returns result with matching names from + /// this scope and passes result with implicit `self` + /// to be introduced after the `subscript` + /// declaration scope grandparent. + @_spi(Experimental) public func lookup( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig + ) -> [LookupResult] { + guard let parentAccessorBlockScope = parentScope?.as(AccessorBlockSyntax.self) else { + return defaultLookupImplementation(identifier, at: lookUpPosition, with: config) + } + + return defaultLookupImplementation( + identifier, + at: lookUpPosition, + with: config, + propagateToParent: false + ) + parentAccessorBlockScope.interleaveAccessorResultsAfterSubscriptLookup( + identifier, + at: lookUpPosition, + with: config, + resultsToInterleave: [.fromScope(self, withNames: [.implicit(.self(self))])] + ) + } } @_spi(Experimental) extension CatchClauseSyntax: ScopeSyntax { @@ -584,7 +606,7 @@ import SwiftSyntax with: config, propagateToParent: false ) + (filteredNamesFromLabel.isEmpty ? [] : [.fromScope(self, withNames: filteredNamesFromLabel)]) - + lookupInParent(identifier, at: lookUpPosition, with: config) + + (config.finishInSequentialScope ? [] : lookupInParent(identifier, at: lookUpPosition, with: config)) } } @@ -713,6 +735,39 @@ import SwiftSyntax _ identifier: Identifier?, at lookUpPosition: AbsolutePosition, with config: LookupConfig + ) -> [LookupResult] { + interleaveResultsAfterThisSubscriptLookup( + identifier, + at: lookUpPosition, + with: config, + resultsToInterleave: [] + ) + } + + /// Lookup names in this scope and add `resultsToInterleave` + /// after results from this scope. + /// + /// It's used to handle implicit `self` introduction + /// at the boundaries of accessors in this subscript. + /// ```swift + /// class X { + /// subscript(self: Int) -> Int { + /// get { + /// self // <-- lookup here + /// } + /// } + /// } + /// ``` + /// In this case, the `self` reference references the `self` + /// function parameter which shadows implicit `self` + /// introduced at the boundary of the getter. That's why + /// this function needs to ensure the implicit `self` passed + /// from inside the accessor block is added after `subscript` parameters. + func interleaveResultsAfterThisSubscriptLookup( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig, + resultsToInterleave: [LookupResult] ) -> [LookupResult] { var thisScopeResults: [LookupResult] = [] @@ -725,7 +780,7 @@ import SwiftSyntax ) } - return thisScopeResults + return thisScopeResults + resultsToInterleave + lookupThroughGenericParameterScope( identifier, at: lookUpPosition, @@ -767,6 +822,26 @@ import SwiftSyntax return lookupInParent(identifier, at: lookUpPosition, with: config) } } + + /// Used by children accessors to interleave + /// their results with parent `subscript` declaration scope. + func interleaveAccessorResultsAfterSubscriptLookup( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig, + resultsToInterleave: [LookupResult] + ) -> [LookupResult] { + guard let parentSubscriptScope = parentScope?.as(SubscriptDeclSyntax.self) else { + return lookupInParent(identifier, at: lookUpPosition, with: config) + } + + return parentSubscriptScope.interleaveResultsAfterThisSubscriptLookup( + identifier, + at: lookUpPosition, + with: config, + resultsToInterleave: resultsToInterleave + ) + } } @_spi(Experimental) extension TypeAliasDeclSyntax: WithGenericParametersScopeSyntax { From 3de7208886c10c39daf915595255f0fc2e7f6b85 Mon Sep 17 00:00:00 2001 From: Jakub Florek Date: Thu, 19 Sep 2024 14:39:02 +0200 Subject: [PATCH 4/8] Add initializer declaration scope. Generalize function decl and initializer decl scopes to function scope. Introduce `self` keyword on the edge of computed properties. --- Sources/SwiftLexicalLookup/CMakeLists.txt | 1 + Sources/SwiftLexicalLookup/LookupName.swift | 14 ++ .../Scopes/FunctionScopeSyntax.swift | 52 ++++++++ .../Scopes/ScopeImplementations.swift | 122 +++++++++++------- .../Scopes/ScopeSyntax.swift | 3 +- .../NameLookupTests.swift | 4 +- 6 files changed, 146 insertions(+), 50 deletions(-) create mode 100644 Sources/SwiftLexicalLookup/Scopes/FunctionScopeSyntax.swift diff --git a/Sources/SwiftLexicalLookup/CMakeLists.txt b/Sources/SwiftLexicalLookup/CMakeLists.txt index c9a0a64e3fd..67f72b9acad 100644 --- a/Sources/SwiftLexicalLookup/CMakeLists.txt +++ b/Sources/SwiftLexicalLookup/CMakeLists.txt @@ -15,6 +15,7 @@ add_swift_syntax_library(SwiftLexicalLookup Configurations/FileScopeHandlingConfig.swift Configurations/LookupConfig.swift + Scopes/FunctionScopeSyntax.swift Scopes/GenericParameterScopeSyntax.swift Scopes/IntroducingToSequentialParentScopeSyntax.swift Scopes/LookInMembersScopeSyntax.swift diff --git a/Sources/SwiftLexicalLookup/LookupName.swift b/Sources/SwiftLexicalLookup/LookupName.swift index 1a7bc9291d0..95497a06dbb 100644 --- a/Sources/SwiftLexicalLookup/LookupName.swift +++ b/Sources/SwiftLexicalLookup/LookupName.swift @@ -154,8 +154,20 @@ import SwiftSyntax switch Syntax(declSyntax).as(SyntaxEnum.self) { case .functionDecl(let functionDecl): return functionDecl.name.position + case .initializerDecl(let initializerDecl): + return initializerDecl.initKeyword.positionAfterSkippingLeadingTrivia case .subscriptDecl(let subscriptDecl): return subscriptDecl.accessorBlock?.position ?? subscriptDecl.endPosition + case .variableDecl(let variableDecl): + return variableDecl.bindings.first?.accessorBlock?.positionAfterSkippingLeadingTrivia + ?? variableDecl.endPosition + default: + return declSyntax.positionAfterSkippingLeadingTrivia + } + case .Self(let declSyntax): + switch Syntax(declSyntax).as(SyntaxEnum.self) { + case .protocolDecl(let protocolDecl): + return protocolDecl.name.positionAfterSkippingLeadingTrivia default: return declSyntax.positionAfterSkippingLeadingTrivia } @@ -226,6 +238,8 @@ import SwiftSyntax return functionCallExpr.arguments.flatMap { argument in getNames(from: argument.expression, accessibleAfter: accessibleAfter) } + case .optionalChainingExpr(let optionalChainingExpr): + return getNames(from: optionalChainingExpr.expression, accessibleAfter: accessibleAfter) default: if let namedDecl = Syntax(syntax).asProtocol(SyntaxProtocol.self) as? NamedDeclSyntax { return handle(namedDecl: namedDecl, accessibleAfter: accessibleAfter) diff --git a/Sources/SwiftLexicalLookup/Scopes/FunctionScopeSyntax.swift b/Sources/SwiftLexicalLookup/Scopes/FunctionScopeSyntax.swift new file mode 100644 index 00000000000..ba49c47e996 --- /dev/null +++ b/Sources/SwiftLexicalLookup/Scopes/FunctionScopeSyntax.swift @@ -0,0 +1,52 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +protocol FunctionScopeSyntax: DeclSyntaxProtocol, WithGenericParametersScopeSyntax { + var signature: FunctionSignatureSyntax { get } +} + +extension FunctionScopeSyntax { + /// Function parameters introduced by this function's signature. + @_spi(Experimental) public var introducedNames: [LookupName] { + signature.parameterClause.parameters.flatMap { parameter in + LookupName.getNames(from: parameter) + } + (parentScope?.is(MemberBlockSyntax.self) ?? false ? [.implicit(.self(self))] : []) + } + + /// Lookup results from this function scope. + /// Routes to generic parameter clause scope if exists. + @_spi(Experimental) public func lookup( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig + ) -> [LookupResult] { + var thisScopeResults: [LookupResult] = [] + + if !signature.range.contains(lookUpPosition) { + thisScopeResults = defaultLookupImplementation( + identifier, + at: position, + with: config, + propagateToParent: false + ) + } + + return thisScopeResults + + lookupThroughGenericParameterScope( + identifier, + at: lookUpPosition, + with: config + ) + } +} diff --git a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift index ea7cc1636d6..0f23d6d7b8b 100644 --- a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift +++ b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift @@ -115,7 +115,7 @@ import SwiftSyntax switch config.fileScopeHandling { case .memberBlock: guard config.includeMembers else { return [] } - + let names = introducedNames(using: .memberBlock) .filter { lookupName in checkIdentifier(identifier, refersTo: lookupName, at: lookUpPosition) @@ -150,7 +150,8 @@ import SwiftSyntax with: config ) - return (members.isEmpty || !config.includeMembers ? [] : [.fromFileScope(self, withNames: members)]) + sequentialNames + return (members.isEmpty || !config.includeMembers ? [] : [.fromFileScope(self, withNames: members)]) + + sequentialNames } } } @@ -528,7 +529,7 @@ import SwiftSyntax @_spi(Experimental) public var scopeDebugName: String { "AccessorDeclScope" } - + /// Returns result with matching names from /// this scope and passes result with implicit `self` /// to be introduced after the `subscript` @@ -541,18 +542,24 @@ import SwiftSyntax guard let parentAccessorBlockScope = parentScope?.as(AccessorBlockSyntax.self) else { return defaultLookupImplementation(identifier, at: lookUpPosition, with: config) } - + + let implicitSelf: [LookupName] = [.implicit(.self(self))] + .filter { name in + checkIdentifier(identifier, refersTo: name, at: lookUpPosition) + } + return defaultLookupImplementation( identifier, at: lookUpPosition, with: config, propagateToParent: false - ) + parentAccessorBlockScope.interleaveAccessorResultsAfterSubscriptLookup( - identifier, - at: lookUpPosition, - with: config, - resultsToInterleave: [.fromScope(self, withNames: [.implicit(.self(self))])] ) + + parentAccessorBlockScope.interleaveAccessorResultsAfterSubscriptLookup( + identifier, + at: lookUpPosition, + with: config, + resultsToInterleave: implicitSelf.isEmpty ? [] : [.fromScope(self, withNames: implicitSelf)] + ) } } @@ -606,14 +613,18 @@ import SwiftSyntax with: config, propagateToParent: false ) + (filteredNamesFromLabel.isEmpty ? [] : [.fromScope(self, withNames: filteredNamesFromLabel)]) - + (config.finishInSequentialScope ? [] : lookupInParent(identifier, at: lookUpPosition, with: config)) + + (config.finishInSequentialScope ? [] : lookupInParent(identifier, at: lookUpPosition, with: config)) } } -@_spi(Experimental) extension ProtocolDeclSyntax: ScopeSyntax { +@_spi(Experimental) extension ProtocolDeclSyntax: ScopeSyntax, LookInMembersScopeSyntax { /// Protocol declarations don't introduce names by themselves. @_spi(Experimental) public var introducedNames: [LookupName] { - [] + [.implicit(.Self(self))] + } + + @_spi(Experimental) public var lookupMembersPosition: AbsolutePosition { + name.positionAfterSkippingLeadingTrivia } @_spi(Experimental) public var scopeDebugName: String { @@ -655,7 +666,13 @@ import SwiftSyntax ) } - return results + defaultLookupImplementation(identifier, at: lookUpPosition, with: config) + return results + + defaultLookupImplementation( + identifier, + at: lookUpPosition, + with: config, + propagateToParent: false + ) + [.lookInMembers(self)] + lookupInParent(identifier, at: lookUpPosition, with: config) } } @@ -672,42 +689,15 @@ import SwiftSyntax } } -@_spi(Experimental) extension FunctionDeclSyntax: WithGenericParametersScopeSyntax { - /// Function parameters introduced by this function's signature. - @_spi(Experimental) public var introducedNames: [LookupName] { - signature.parameterClause.parameters.flatMap { parameter in - LookupName.getNames(from: parameter) - } + (parentScope?.is(MemberBlockSyntax.self) ?? false ? [.implicit(.self(self))] : []) - } - +@_spi(Experimental) extension FunctionDeclSyntax: FunctionScopeSyntax { @_spi(Experimental) public var scopeDebugName: String { "FunctionDeclScope" } +} - /// Lookup results from this function scope. - /// Routes to generic parameter clause scope if exists. - @_spi(Experimental) public func lookup( - _ identifier: Identifier?, - at lookUpPosition: AbsolutePosition, - with config: LookupConfig - ) -> [LookupResult] { - var thisScopeResults: [LookupResult] = [] - - if !signature.range.contains(lookUpPosition) { - thisScopeResults = defaultLookupImplementation( - identifier, - at: position, - with: config, - propagateToParent: false - ) - } - - return thisScopeResults - + lookupThroughGenericParameterScope( - identifier, - at: lookUpPosition, - with: config - ) +@_spi(Experimental) extension InitializerDeclSyntax: FunctionScopeSyntax { + @_spi(Experimental) public var scopeDebugName: String { + "InitializerDeclScope" } } @@ -743,7 +733,7 @@ import SwiftSyntax resultsToInterleave: [] ) } - + /// Lookup names in this scope and add `resultsToInterleave` /// after results from this scope. /// @@ -822,7 +812,7 @@ import SwiftSyntax return lookupInParent(identifier, at: lookUpPosition, with: config) } } - + /// Used by children accessors to interleave /// their results with parent `subscript` declaration scope. func interleaveAccessorResultsAfterSubscriptLookup( @@ -834,7 +824,7 @@ import SwiftSyntax guard let parentSubscriptScope = parentScope?.as(SubscriptDeclSyntax.self) else { return lookupInParent(identifier, at: lookUpPosition, with: config) } - + return parentSubscriptScope.interleaveResultsAfterThisSubscriptLookup( identifier, at: lookUpPosition, @@ -854,3 +844,39 @@ import SwiftSyntax "TypeAliasDeclScope" } } + +@_spi(Experimental) extension VariableDeclSyntax: ScopeSyntax { + /// Variable decl scope doesn't introduce any + /// names unless it is a member and is looked + /// up from inside it's accessor block. + @_spi(Experimental) public var introducedNames: [LookupName] { + [] + } + + @_spi(Experimental) public var scopeDebugName: String { + "VariableDeclScope" + } + + /// If a member and looked up from inside + /// it's accessor block, introduce implicit + /// `self` and propagate the lookup further. + @_spi(Experimental) public func lookup( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig + ) -> [LookupResult] { + if let parentScope, + parentScope.is(MemberBlockSyntax.self), + bindings.first?.accessorBlock?.range.contains(lookUpPosition) ?? false + { + return defaultLookupImplementation( + in: [.implicit(.self(self))], + identifier, + at: lookUpPosition, + with: config + ) + } else { + return lookupInParent(identifier, at: lookUpPosition, with: config) + } + } +} diff --git a/Sources/SwiftLexicalLookup/Scopes/ScopeSyntax.swift b/Sources/SwiftLexicalLookup/Scopes/ScopeSyntax.swift index e73f12a1aa0..1d7eb36f95c 100644 --- a/Sources/SwiftLexicalLookup/Scopes/ScopeSyntax.swift +++ b/Sources/SwiftLexicalLookup/Scopes/ScopeSyntax.swift @@ -87,13 +87,14 @@ extension SyntaxProtocol { /// refers to and is accessible at given syntax node then passes lookup to the parent. /// If `identifier` set to `nil`, returns all available names at the given node. func defaultLookupImplementation( + in names: [LookupName]? = nil, _ identifier: Identifier?, at lookUpPosition: AbsolutePosition, with config: LookupConfig, propagateToParent: Bool = true ) -> [LookupResult] { let filteredNames = - introducedNames + (names ?? introducedNames) .filter { introducedName in checkIdentifier(identifier, refersTo: introducedName, at: lookUpPosition) } diff --git a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift index 2dbaf4dbe16..9a90b86d4c7 100644 --- a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift +++ b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift @@ -1005,10 +1005,12 @@ final class testNameLookup: XCTestCase { references: [ "1️⃣": [ .fromScope(MemberBlockSyntax.self, expectedNames: ["3️⃣"]), + .lookInMembers(ProtocolDeclSyntax.self), .fromFileScope(expectedNames: ["0️⃣"]), ], "2️⃣": [ - .fromScope(MemberBlockSyntax.self, expectedNames: ["4️⃣"]) + .fromScope(MemberBlockSyntax.self, expectedNames: ["4️⃣"]), + .lookInMembers(ProtocolDeclSyntax.self), ], ], expectedResultTypes: .all( From 433f14bbd85810efd88954e0cb62c3b655ca7037 Mon Sep 17 00:00:00 2001 From: Jakub Florek Date: Mon, 23 Sep 2024 21:35:29 +0200 Subject: [PATCH 5/8] Add suggested changes. Add new specialized scope: CanInterleaveResultsLaterScopeSyntax. --- Sources/SwiftLexicalLookup/CMakeLists.txt | 1 + .../Configurations/LookupConfig.swift | 35 +++++++++++++ Sources/SwiftLexicalLookup/LookupName.swift | 13 ++--- Sources/SwiftLexicalLookup/LookupResult.swift | 8 +-- ...CanInterleaveResultsLaterScopeSyntax.swift | 22 ++++++++ .../Scopes/ScopeImplementations.swift | 52 ++++++++++++------- 6 files changed, 97 insertions(+), 34 deletions(-) create mode 100644 Sources/SwiftLexicalLookup/Scopes/CanInterleaveResultsLaterScopeSyntax.swift diff --git a/Sources/SwiftLexicalLookup/CMakeLists.txt b/Sources/SwiftLexicalLookup/CMakeLists.txt index 67f72b9acad..048448f0c4f 100644 --- a/Sources/SwiftLexicalLookup/CMakeLists.txt +++ b/Sources/SwiftLexicalLookup/CMakeLists.txt @@ -15,6 +15,7 @@ add_swift_syntax_library(SwiftLexicalLookup Configurations/FileScopeHandlingConfig.swift Configurations/LookupConfig.swift + Scopes/CanInterleaveResultsLaterScopeSyntax.swift Scopes/FunctionScopeSyntax.swift Scopes/GenericParameterScopeSyntax.swift Scopes/IntroducingToSequentialParentScopeSyntax.swift diff --git a/Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift b/Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift index 98812e7cef0..9b46b9dea8d 100644 --- a/Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift +++ b/Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift @@ -14,8 +14,43 @@ /// Specifies behavior of file scope. @_spi(Experimental) public var fileScopeHandling: FileScopeHandlingConfig /// Specifies whether lookup should finish in the closest sequential scope. + /// + /// ### Example + /// ```swift + /// class X { + /// let a = 42 + /// + /// func (a: Int) { + /// let a = 123 + /// + /// a // <-- lookup here + /// } + /// } + /// ``` + /// When looking up at the specified position with `finishInSequentialScope` + /// set to `false`, lookup will return declaration from inside function body, + /// function parameter and the `a` declaration from `class X` member block. + /// If `finishInSequentialScope` would be set to `false`, the only name + /// returned by lookup would be the `a` declaration from inside function body. @_spi(Experimental) public var finishInSequentialScope: Bool /// Specifies whether to include results generated in file and member block scopes. + /// + /// ### Example + /// ```swift + /// class X { + /// let a = 42 + /// + /// func (a: Int) { + /// let a = 123 + /// + /// a // <-- lookup here + /// } + /// } + /// ``` + /// When looking up at the specified position with `includeMembers` + /// set to `true`, lookup will return declaration from inside function body, + /// function parameter and the `a` declaration from `class X` member block. + /// If `includeMembers` would be set to `false`, the latter name would be omitted. @_spi(Experimental) public var includeMembers: Bool /// Creates a new lookup configuration. diff --git a/Sources/SwiftLexicalLookup/LookupName.swift b/Sources/SwiftLexicalLookup/LookupName.swift index 95497a06dbb..20994913fb5 100644 --- a/Sources/SwiftLexicalLookup/LookupName.swift +++ b/Sources/SwiftLexicalLookup/LookupName.swift @@ -20,7 +20,7 @@ import SwiftSyntax case `self`(DeclSyntaxProtocol) /// `Self` keyword representing object type. /// Could be associated with type declaration or extension. - case `Self`(DeclSyntaxProtocol) + case `Self`(ProtocolDeclSyntax) /// `error` value caught by a `catch` /// block that does not specify a catch pattern. case error(CatchClauseSyntax) @@ -164,13 +164,8 @@ import SwiftSyntax default: return declSyntax.positionAfterSkippingLeadingTrivia } - case .Self(let declSyntax): - switch Syntax(declSyntax).as(SyntaxEnum.self) { - case .protocolDecl(let protocolDecl): - return protocolDecl.name.positionAfterSkippingLeadingTrivia - default: - return declSyntax.positionAfterSkippingLeadingTrivia - } + case .Self(let protocolDecl): + return protocolDecl.name.positionAfterSkippingLeadingTrivia case .error(let catchClause): return catchClause.body.position default: @@ -271,7 +266,7 @@ import SwiftSyntax @_spi(Experimental) public var debugDescription: String { let sourceLocationConverter = SourceLocationConverter(fileName: "", tree: syntax.root) let location = sourceLocationConverter.location(for: position) - let strName = (identifier != nil ? identifier!.name : "NO-NAME") + " at: \(location.line):\(location.column)" + let strName = (identifier?.name ?? "NO-NAME") + " at: \(location.line):\(location.column)" switch self { case .identifier: diff --git a/Sources/SwiftLexicalLookup/LookupResult.swift b/Sources/SwiftLexicalLookup/LookupResult.swift index 7d38a7f58c3..e53d1b980bc 100644 --- a/Sources/SwiftLexicalLookup/LookupResult.swift +++ b/Sources/SwiftLexicalLookup/LookupResult.swift @@ -94,12 +94,6 @@ import SwiftSyntax @_spi(Experimental) extension [LookupResult] { /// Debug description this array of lookup results. @_spi(Experimental) public var debugDescription: String { - var str: String = "" - - for (index, result) in self.enumerated() { - str += result.debugDescription + (index + 1 == self.count ? "" : "\n") - } - - return str + return self.map(\.debugDescription).joined(separator: "\n") } } diff --git a/Sources/SwiftLexicalLookup/Scopes/CanInterleaveResultsLaterScopeSyntax.swift b/Sources/SwiftLexicalLookup/Scopes/CanInterleaveResultsLaterScopeSyntax.swift new file mode 100644 index 00000000000..97f32a75a46 --- /dev/null +++ b/Sources/SwiftLexicalLookup/Scopes/CanInterleaveResultsLaterScopeSyntax.swift @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +protocol CanInterleaveResultsLaterScopeSyntax: ScopeSyntax { + func lookupWithInterleavedResults( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig, + resultsToInterleave: [LookupResult] + ) -> [LookupResult] +} diff --git a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift index 0f23d6d7b8b..9ef26e4eb4f 100644 --- a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift +++ b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift @@ -508,22 +508,18 @@ import SwiftSyntax @_spi(Experimental) extension AccessorDeclSyntax: ScopeSyntax { /// Implicit and/or explicit names introduced within the accessor. @_spi(Experimental) public var introducedNames: [LookupName] { - let names: [LookupName] - if let parameters { - names = LookupName.getNames(from: parameters) + return LookupName.getNames(from: parameters) } else { switch accessorSpecifier.tokenKind { case .keyword(.set), .keyword(.willSet): - names = [.implicit(.newValue(self))] + return [.implicit(.newValue(self))] case .keyword(.didSet): - names = [.implicit(.oldValue(self))] + return [.implicit(.oldValue(self))] default: - names = [] + return [] } } - - return names } @_spi(Experimental) public var scopeDebugName: String { @@ -539,7 +535,10 @@ import SwiftSyntax at lookUpPosition: AbsolutePosition, with config: LookupConfig ) -> [LookupResult] { - guard let parentAccessorBlockScope = parentScope?.as(AccessorBlockSyntax.self) else { + guard let parentScope, + let canInterleaveLaterScope = Syntax(parentScope).asProtocol(SyntaxProtocol.self) + as? CanInterleaveResultsLaterScopeSyntax + else { return defaultLookupImplementation(identifier, at: lookUpPosition, with: config) } @@ -554,7 +553,7 @@ import SwiftSyntax with: config, propagateToParent: false ) - + parentAccessorBlockScope.interleaveAccessorResultsAfterSubscriptLookup( + + canInterleaveLaterScope.lookupWithInterleavedResults( identifier, at: lookUpPosition, with: config, @@ -701,7 +700,8 @@ import SwiftSyntax } } -@_spi(Experimental) extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax { +@_spi(Experimental) +extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax, CanInterleaveResultsLaterScopeSyntax { /// Parameters introduced by this subscript and possibly `self` keyword. @_spi(Experimental) public var introducedNames: [LookupName] { let parameters = parameterClause.parameters.flatMap { parameter in @@ -726,7 +726,7 @@ import SwiftSyntax at lookUpPosition: AbsolutePosition, with config: LookupConfig ) -> [LookupResult] { - interleaveResultsAfterThisSubscriptLookup( + lookupWithInterleavedResults( identifier, at: lookUpPosition, with: config, @@ -753,7 +753,7 @@ import SwiftSyntax /// introduced at the boundary of the getter. That's why /// this function needs to ensure the implicit `self` passed /// from inside the accessor block is added after `subscript` parameters. - func interleaveResultsAfterThisSubscriptLookup( + func lookupWithInterleavedResults( _ identifier: Identifier?, at lookUpPosition: AbsolutePosition, with config: LookupConfig, @@ -779,7 +779,7 @@ import SwiftSyntax } } -@_spi(Experimental) extension AccessorBlockSyntax: SequentialScopeSyntax { +@_spi(Experimental) extension AccessorBlockSyntax: SequentialScopeSyntax, CanInterleaveResultsLaterScopeSyntax { /// Names from the accessors or /// getters of this accessor block scope. @_spi(Experimental) public var introducedNames: [LookupName] { @@ -815,17 +815,20 @@ import SwiftSyntax /// Used by children accessors to interleave /// their results with parent `subscript` declaration scope. - func interleaveAccessorResultsAfterSubscriptLookup( + func lookupWithInterleavedResults( _ identifier: Identifier?, at lookUpPosition: AbsolutePosition, with config: LookupConfig, resultsToInterleave: [LookupResult] ) -> [LookupResult] { - guard let parentSubscriptScope = parentScope?.as(SubscriptDeclSyntax.self) else { + guard let parentScope, + let canInterleaveLaterScope = Syntax(parentScope).asProtocol(SyntaxProtocol.self) + as? CanInterleaveResultsLaterScopeSyntax + else { return lookupInParent(identifier, at: lookUpPosition, with: config) } - return parentSubscriptScope.interleaveResultsAfterThisSubscriptLookup( + return canInterleaveLaterScope.lookupWithInterleavedResults( identifier, at: lookUpPosition, with: config, @@ -845,7 +848,7 @@ import SwiftSyntax } } -@_spi(Experimental) extension VariableDeclSyntax: ScopeSyntax { +@_spi(Experimental) extension VariableDeclSyntax: CanInterleaveResultsLaterScopeSyntax { /// Variable decl scope doesn't introduce any /// names unless it is a member and is looked /// up from inside it's accessor block. @@ -879,4 +882,17 @@ import SwiftSyntax return lookupInParent(identifier, at: lookUpPosition, with: config) } } + + func lookupWithInterleavedResults( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig, + resultsToInterleave: [LookupResult] + ) -> [LookupResult] { + guard parentScope?.is(MemberBlockSyntax.self) ?? false else { + return lookupInParent(identifier, at: lookUpPosition, with: config) + } + + return resultsToInterleave + lookupInParent(identifier, at: lookUpPosition, with: config) + } } From 0f9b2bc2922de0d387c64de2a569205d244cafbb Mon Sep 17 00:00:00 2001 From: Jakub Florek Date: Wed, 25 Sep 2024 13:17:26 +0200 Subject: [PATCH 6/8] Add implicit `Self` to extension declaration scope. Fix name introduction of a variable with accessors in global scope. Add suggested changes and documentation. --- Sources/SwiftLexicalLookup/LookupName.swift | 20 +++++++++----- ...CanInterleaveResultsLaterScopeSyntax.swift | 3 +++ .../Scopes/ScopeImplementations.swift | 26 +++++++++++++------ .../NameLookupTests.swift | 6 +++-- 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/Sources/SwiftLexicalLookup/LookupName.swift b/Sources/SwiftLexicalLookup/LookupName.swift index 20994913fb5..3da5d712d4d 100644 --- a/Sources/SwiftLexicalLookup/LookupName.swift +++ b/Sources/SwiftLexicalLookup/LookupName.swift @@ -20,7 +20,7 @@ import SwiftSyntax case `self`(DeclSyntaxProtocol) /// `Self` keyword representing object type. /// Could be associated with type declaration or extension. - case `Self`(ProtocolDeclSyntax) + case `Self`(DeclSyntaxProtocol) /// `error` value caught by a `catch` /// block that does not specify a catch pattern. case error(CatchClauseSyntax) @@ -147,27 +147,33 @@ import SwiftSyntax case .identifier(let syntax, _): return syntax.identifier.positionAfterSkippingLeadingTrivia case .declaration(let syntax): - return syntax.name.position + return syntax.name.positionAfterSkippingLeadingTrivia case .implicit(let implicitName): switch implicitName { case .self(let declSyntax): switch Syntax(declSyntax).as(SyntaxEnum.self) { case .functionDecl(let functionDecl): - return functionDecl.name.position + return functionDecl.name.positionAfterSkippingLeadingTrivia case .initializerDecl(let initializerDecl): return initializerDecl.initKeyword.positionAfterSkippingLeadingTrivia case .subscriptDecl(let subscriptDecl): - return subscriptDecl.accessorBlock?.position ?? subscriptDecl.endPosition + return subscriptDecl.accessorBlock?.positionAfterSkippingLeadingTrivia + ?? subscriptDecl.endPositionBeforeTrailingTrivia case .variableDecl(let variableDecl): return variableDecl.bindings.first?.accessorBlock?.positionAfterSkippingLeadingTrivia ?? variableDecl.endPosition default: return declSyntax.positionAfterSkippingLeadingTrivia } - case .Self(let protocolDecl): - return protocolDecl.name.positionAfterSkippingLeadingTrivia + case .Self(let declSyntax): + switch Syntax(declSyntax).as(SyntaxEnum.self) { + case .protocolDecl(let protocolDecl): + return protocolDecl.name.positionAfterSkippingLeadingTrivia + default: + return declSyntax.positionAfterSkippingLeadingTrivia + } case .error(let catchClause): - return catchClause.body.position + return catchClause.body.positionAfterSkippingLeadingTrivia default: return implicitName.syntax.positionAfterSkippingLeadingTrivia } diff --git a/Sources/SwiftLexicalLookup/Scopes/CanInterleaveResultsLaterScopeSyntax.swift b/Sources/SwiftLexicalLookup/Scopes/CanInterleaveResultsLaterScopeSyntax.swift index 97f32a75a46..5c21d49c7ee 100644 --- a/Sources/SwiftLexicalLookup/Scopes/CanInterleaveResultsLaterScopeSyntax.swift +++ b/Sources/SwiftLexicalLookup/Scopes/CanInterleaveResultsLaterScopeSyntax.swift @@ -13,6 +13,9 @@ import SwiftSyntax protocol CanInterleaveResultsLaterScopeSyntax: ScopeSyntax { + /// Perform lookup in this scope and later introduce results + /// passed as `resultsToInterleave`. + /// The exact behavior depends on a specific scope. func lookupWithInterleavedResults( _ identifier: Identifier?, at lookUpPosition: AbsolutePosition, diff --git a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift index 9ef26e4eb4f..ea96bfe3c2c 100644 --- a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift +++ b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift @@ -498,7 +498,13 @@ import SwiftSyntax with config: LookupConfig ) -> [LookupResult] { if memberBlock.range.contains(lookUpPosition) { - return [.lookInMembers(self)] + defaultLookupImplementation(identifier, at: lookUpPosition, with: config) + let implicitSelf: [LookupName] = [.implicit(.Self(self))] + .filter { name in + checkIdentifier(identifier, refersTo: name, at: lookUpPosition) + } + + return (implicitSelf.isEmpty ? [] : [.fromScope(self, withNames: implicitSelf)]) + [.lookInMembers(self)] + + defaultLookupImplementation(identifier, at: lookUpPosition, with: config) } else { return defaultLookupImplementation(identifier, at: lookUpPosition, with: config) } @@ -665,13 +671,16 @@ import SwiftSyntax ) } + let lookInMembers: [LookupResult] = + inheritanceClause?.range.contains(lookUpPosition) ?? false ? [] : [.lookInMembers(self)] + return results + defaultLookupImplementation( identifier, at: lookUpPosition, with: config, propagateToParent: false - ) + [.lookInMembers(self)] + lookupInParent(identifier, at: lookUpPosition, with: config) + ) + lookInMembers + lookupInParent(identifier, at: lookUpPosition, with: config) } } @@ -868,12 +877,11 @@ extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax, CanInterleaveRe at lookUpPosition: AbsolutePosition, with config: LookupConfig ) -> [LookupResult] { - if let parentScope, - parentScope.is(MemberBlockSyntax.self), - bindings.first?.accessorBlock?.range.contains(lookUpPosition) ?? false - { + if bindings.first?.accessorBlock?.range.contains(lookUpPosition) ?? false { + let shouldIntroduceSelf = parentScope?.is(MemberBlockSyntax.self) ?? false + return defaultLookupImplementation( - in: [.implicit(.self(self))], + in: LookupName.getNames(from: self) + (shouldIntroduceSelf ? [.implicit(.self(self))] : []), identifier, at: lookUpPosition, with: config @@ -883,6 +891,8 @@ extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax, CanInterleaveRe } } + /// If a member, introduce results passed in `resultsToInterleave` + /// and then pass lookup to the parent. Otherwise, perform `lookup`. func lookupWithInterleavedResults( _ identifier: Identifier?, at lookUpPosition: AbsolutePosition, @@ -890,7 +900,7 @@ extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax, CanInterleaveRe resultsToInterleave: [LookupResult] ) -> [LookupResult] { guard parentScope?.is(MemberBlockSyntax.self) ?? false else { - return lookupInParent(identifier, at: lookUpPosition, with: config) + return lookup(identifier, at: lookUpPosition, with: config) } return resultsToInterleave + lookupInParent(identifier, at: lookUpPosition, with: config) diff --git a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift index 9a90b86d4c7..470cb8b2a2d 100644 --- a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift +++ b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift @@ -660,7 +660,7 @@ final class testNameLookup: XCTestCase { func testImplicitSelf() { assertLexicalNameLookup( source: """ - extension a { + 7️⃣extension a { struct b { 2️⃣func foo() { let x: 3️⃣Self = 4️⃣self @@ -675,6 +675,7 @@ final class testNameLookup: XCTestCase { references: [ "3️⃣": [ .lookInMembers(StructDeclSyntax.self), + .fromScope(ExtensionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.Self("7️⃣"))]), .lookInMembers(ExtensionDeclSyntax.self), ], "4️⃣": [ @@ -683,7 +684,8 @@ final class testNameLookup: XCTestCase { .lookInMembers(ExtensionDeclSyntax.self), ], "5️⃣": [ - .lookInMembers(ExtensionDeclSyntax.self) + .fromScope(ExtensionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.Self("7️⃣"))]), + .lookInMembers(ExtensionDeclSyntax.self), ], "6️⃣": [ .fromScope(FunctionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("1️⃣"))]), From baf6a2f4d10d94757b42d2ad501aa98460815056 Mon Sep 17 00:00:00 2001 From: Jakub Florek Date: Thu, 26 Sep 2024 10:41:40 +0200 Subject: [PATCH 7/8] Fix formatting issue caused by too long `todo` comments. --- Tests/SwiftLexicalLookupTest/NameLookupTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift index 470cb8b2a2d..2b5c83a5334 100644 --- a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift +++ b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift @@ -633,7 +633,7 @@ final class testNameLookup: XCTestCase { ) } - func testGuardOnFileScope() { // TODO: Fix this according to ASTScope (ommiting class a) + func testGuardOnFileScope() { assertLexicalNameLookup( source: """ let 1️⃣a = 0 @@ -904,7 +904,7 @@ final class testNameLookup: XCTestCase { ) } - func testSwitchExpression() { // TODO: For some reason ASTScope doesn't introduce any results besides first function call expr. + func testSwitchExpression() { assertLexicalNameLookup( source: """ switch { @@ -1070,7 +1070,7 @@ final class testNameLookup: XCTestCase { ) } - func testSubscript() { // TODO: Fix behavior of self keyword in subscript with accessors. + func testSubscript() { assertLexicalNameLookup( source: """ class X { From fc1ab883ad83af2f9b0e9cbdbab92a330dd02fad Mon Sep 17 00:00:00 2001 From: Jakub Florek Date: Thu, 26 Sep 2024 11:39:27 +0200 Subject: [PATCH 8/8] In protocol decl scope, whitelist member block instead of filtering lookup position from inheritance clause. --- Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift | 3 +-- Tests/SwiftLexicalLookupTest/NameLookupTests.swift | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift index ea96bfe3c2c..27b29928806 100644 --- a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift +++ b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift @@ -671,8 +671,7 @@ import SwiftSyntax ) } - let lookInMembers: [LookupResult] = - inheritanceClause?.range.contains(lookUpPosition) ?? false ? [] : [.lookInMembers(self)] + let lookInMembers: [LookupResult] = memberBlock.range.contains(lookUpPosition) ? [.lookInMembers(self)] : [] return results + defaultLookupImplementation( diff --git a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift index 2b5c83a5334..4a3d283f174 100644 --- a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift +++ b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift @@ -1007,12 +1007,10 @@ final class testNameLookup: XCTestCase { references: [ "1️⃣": [ .fromScope(MemberBlockSyntax.self, expectedNames: ["3️⃣"]), - .lookInMembers(ProtocolDeclSyntax.self), .fromFileScope(expectedNames: ["0️⃣"]), ], "2️⃣": [ - .fromScope(MemberBlockSyntax.self, expectedNames: ["4️⃣"]), - .lookInMembers(ProtocolDeclSyntax.self), + .fromScope(MemberBlockSyntax.self, expectedNames: ["4️⃣"]) ], ], expectedResultTypes: .all(