diff --git a/Sources/SwiftLexicalLookup/CMakeLists.txt b/Sources/SwiftLexicalLookup/CMakeLists.txt index b6cd9204fc3..048448f0c4f 100644 --- a/Sources/SwiftLexicalLookup/CMakeLists.txt +++ b/Sources/SwiftLexicalLookup/CMakeLists.txt @@ -15,12 +15,15 @@ add_swift_syntax_library(SwiftLexicalLookup Configurations/FileScopeHandlingConfig.swift Configurations/LookupConfig.swift + Scopes/CanInterleaveResultsLaterScopeSyntax.swift + Scopes/FunctionScopeSyntax.swift 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..9b46b9dea8d 100644 --- a/Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift +++ b/Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift @@ -13,14 +13,61 @@ @_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. + /// + /// ### 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. /// /// - `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..3da5d712d4d 100644 --- a/Sources/SwiftLexicalLookup/LookupName.swift +++ b/Sources/SwiftLexicalLookup/LookupName.swift @@ -16,7 +16,7 @@ import SwiftSyntax @_spi(Experimental) public enum ImplicitDecl { /// `self` keyword representing object instance. /// Could be associated with type declaration, extension, - /// or closure captures. + /// or closure captures. Introduced at function edge. case `self`(DeclSyntaxProtocol) /// `Self` keyword representing object type. /// Could be associated with type declaration or extension. @@ -135,6 +135,51 @@ 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, _): + return syntax.identifier.positionAfterSkippingLeadingTrivia + case .declaration(let syntax): + 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.positionAfterSkippingLeadingTrivia + case .initializerDecl(let initializerDecl): + return initializerDecl.initKeyword.positionAfterSkippingLeadingTrivia + case .subscriptDecl(let subscriptDecl): + 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 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.positionAfterSkippingLeadingTrivia + default: + return implicitName.syntax.positionAfterSkippingLeadingTrivia + } + } + } + /// 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? { @@ -166,7 +211,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 @@ -194,6 +239,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) @@ -210,12 +257,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. @@ -225,4 +267,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?.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..e53d1b980bc 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,48 @@ 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 { + 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..5c21d49c7ee --- /dev/null +++ b/Sources/SwiftLexicalLookup/Scopes/CanInterleaveResultsLaterScopeSyntax.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// 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 { + /// 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, + with config: LookupConfig, + resultsToInterleave: [LookupResult] + ) -> [LookupResult] +} 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/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..f509486881b --- /dev/null +++ b/Sources/SwiftLexicalLookup/Scopes/NominalTypeDeclSyntax.swift @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// 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 genericParameterClause: GenericParameterClauseSyntax? { get } + 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 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 5c59f8ba66c..27b29928806 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. /// @@ -103,6 +114,8 @@ import SwiftSyntax ) -> [LookupResult] { switch config.fileScopeHandling { case .memberBlock: + guard config.includeMembers else { return [] } + let names = introducedNames(using: .memberBlock) .filter { lookupName in checkIdentifier(identifier, refersTo: lookupName, at: lookUpPosition) @@ -137,7 +150,8 @@ import SwiftSyntax with: config ) - return (members.isEmpty ? [] : [.fromFileScope(self, withNames: members)]) + sequentialNames + return (members.isEmpty || !config.includeMembers ? [] : [.fromFileScope(self, withNames: members)]) + + sequentialNames } } } @@ -151,6 +165,10 @@ import SwiftSyntax } } + @_spi(Experimental) public var scopeDebugName: String { + "CodeBlockScope" + } + @_spi(Experimental) public func lookup( _ identifier: Identifier?, at lookUpPosition: AbsolutePosition, @@ -170,21 +188,30 @@ import SwiftSyntax @_spi(Experimental) public var introducedNames: [LookupName] { LookupName.getNames(from: pattern) } + + @_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: 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,15 +230,73 @@ 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: [LookupName] + + if let signature, signature.range.contains(lookUpPosition) { + filteredSignatureNames = [] + } else { + 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 { /// 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) } } + + @_spi(Experimental) public var scopeDebugName: String { + "WhileStmtScope" + } } @_spi(Experimental) extension IfExprSyntax: ScopeSyntax { @@ -253,11 +338,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 +380,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 +419,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 +428,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 +459,57 @@ 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) { + 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) + } + } +} @_spi(Experimental) extension AccessorDeclSyntax: ScopeSyntax { /// Implicit and/or explicit names introduced within the accessor. @@ -370,6 +527,45 @@ 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` + /// declaration scope grandparent. + @_spi(Experimental) public func lookup( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig + ) -> [LookupResult] { + guard let parentScope, + let canInterleaveLaterScope = Syntax(parentScope).asProtocol(SyntaxProtocol.self) + as? CanInterleaveResultsLaterScopeSyntax + 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 + ) + + canInterleaveLaterScope.lookupWithInterleavedResults( + identifier, + at: lookUpPosition, + with: config, + resultsToInterleave: implicitSelf.isEmpty ? [] : [.fromScope(self, withNames: implicitSelf)] + ) + } } @_spi(Experimental) extension CatchClauseSyntax: ScopeSyntax { @@ -377,21 +573,67 @@ 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 { +@_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)]) + + (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 { + "ProtocolDeclScope" } /// For the lookup initiated from inside primary @@ -429,7 +671,15 @@ import SwiftSyntax ) } - return results + defaultLookupImplementation(identifier, at: lookUpPosition, with: config) + let lookInMembers: [LookupResult] = memberBlock.range.contains(lookUpPosition) ? [.lookInMembers(self)] : [] + + return results + + defaultLookupImplementation( + identifier, + at: lookUpPosition, + with: config, + propagateToParent: false + ) + lookInMembers + lookupInParent(identifier, at: lookUpPosition, with: config) } } @@ -437,26 +687,161 @@ 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 { - /// Function parameters introduced by this function's signature. +@_spi(Experimental) extension FunctionDeclSyntax: FunctionScopeSyntax { + @_spi(Experimental) public var scopeDebugName: String { + "FunctionDeclScope" + } +} + +@_spi(Experimental) extension InitializerDeclSyntax: FunctionScopeSyntax { + @_spi(Experimental) public var scopeDebugName: String { + "InitializerDeclScope" + } +} + +@_spi(Experimental) +extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax, CanInterleaveResultsLaterScopeSyntax { + /// Parameters introduced by this subscript and possibly `self` keyword. @_spi(Experimental) public var introducedNames: [LookupName] { - signature.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] { + lookupWithInterleavedResults( + 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 lookupWithInterleavedResults( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig, + resultsToInterleave: [LookupResult] + ) -> [LookupResult] { + var thisScopeResults: [LookupResult] = [] + + if !parameterClause.range.contains(lookUpPosition) && !returnClause.range.contains(lookUpPosition) { + thisScopeResults = defaultLookupImplementation( + identifier, + at: position, + with: config, + propagateToParent: false + ) + } + + return thisScopeResults + resultsToInterleave + + lookupThroughGenericParameterScope( + identifier, + at: lookUpPosition, + with: config + ) } } -@_spi(Experimental) extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax { - /// Parameters introduced by this subscript. +@_spi(Experimental) extension AccessorBlockSyntax: SequentialScopeSyntax, CanInterleaveResultsLaterScopeSyntax { + /// Names from the accessors or + /// getters of this accessor block scope. @_spi(Experimental) public var introducedNames: [LookupName] { - parameterClause.parameters.flatMap { parameter in - LookupName.getNames(from: parameter) + 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) + } + } + + /// Used by children accessors to interleave + /// their results with parent `subscript` declaration scope. + func lookupWithInterleavedResults( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig, + resultsToInterleave: [LookupResult] + ) -> [LookupResult] { + guard let parentScope, + let canInterleaveLaterScope = Syntax(parentScope).asProtocol(SyntaxProtocol.self) + as? CanInterleaveResultsLaterScopeSyntax + else { + return lookupInParent(identifier, at: lookUpPosition, with: config) } + + return canInterleaveLaterScope.lookupWithInterleavedResults( + identifier, + at: lookUpPosition, + with: config, + resultsToInterleave: resultsToInterleave + ) } } @@ -465,4 +850,58 @@ import SwiftSyntax @_spi(Experimental) public var introducedNames: [LookupName] { [] } + + @_spi(Experimental) public var scopeDebugName: String { + "TypeAliasDeclScope" + } +} + +@_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. + @_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 bindings.first?.accessorBlock?.range.contains(lookUpPosition) ?? false { + let shouldIntroduceSelf = parentScope?.is(MemberBlockSyntax.self) ?? false + + return defaultLookupImplementation( + in: LookupName.getNames(from: self) + (shouldIntroduceSelf ? [.implicit(.self(self))] : []), + identifier, + at: lookUpPosition, + with: config + ) + } else { + return lookupInParent(identifier, at: lookUpPosition, with: config) + } + } + + /// 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, + with config: LookupConfig, + resultsToInterleave: [LookupResult] + ) -> [LookupResult] { + guard parentScope?.is(MemberBlockSyntax.self) ?? false else { + return lookup(identifier, at: lookUpPosition, with: config) + } + + return resultsToInterleave + lookupInParent(identifier, at: lookUpPosition, with: config) + } } diff --git a/Sources/SwiftLexicalLookup/Scopes/ScopeSyntax.swift b/Sources/SwiftLexicalLookup/Scopes/ScopeSyntax.swift index c1b6c43d59e..1d7eb36f95c 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( @@ -85,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) } @@ -117,4 +120,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..4a3d283f174 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,37 @@ final class testNameLookup: XCTestCase { func testImplicitSelf() { assertLexicalNameLookup( source: """ - 1️⃣extension a { - 2️⃣struct b { - func foo() { + 7️⃣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), + .fromScope(ExtensionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.Self("7️⃣"))]), + .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️⃣": [ + .fromScope(ExtensionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.Self("7️⃣"))]), + .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 +740,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 +759,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 +783,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 +796,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 +893,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 +944,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 +971,21 @@ 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️⃣"]) + ], + "3️⃣": [ + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["1️⃣"]) + ], + "5️⃣": [ + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["7️⃣"]) + ], + "6️⃣": [ + .fromScope(GenericParameterClauseSyntax.self, expectedNames: ["4️⃣"]) + ], ], expectedResultTypes: .all(GenericParameterSyntax.self) ) @@ -949,14 +1037,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 +1080,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" - } - } -}