diff --git a/Sources/SwiftLexicalLookup/CMakeLists.txt b/Sources/SwiftLexicalLookup/CMakeLists.txt index 048448f0c4f..5c136ce22d0 100644 --- a/Sources/SwiftLexicalLookup/CMakeLists.txt +++ b/Sources/SwiftLexicalLookup/CMakeLists.txt @@ -11,9 +11,7 @@ add_swift_syntax_library(SwiftLexicalLookup LookupName.swift LookupResult.swift SimpleLookupQueries.swift - - Configurations/FileScopeHandlingConfig.swift - Configurations/LookupConfig.swift + LookupConfig.swift Scopes/CanInterleaveResultsLaterScopeSyntax.swift Scopes/FunctionScopeSyntax.swift diff --git a/Sources/SwiftLexicalLookup/Configurations/FileScopeHandlingConfig.swift b/Sources/SwiftLexicalLookup/Configurations/FileScopeHandlingConfig.swift deleted file mode 100644 index 34c0861a91e..00000000000 --- a/Sources/SwiftLexicalLookup/Configurations/FileScopeHandlingConfig.swift +++ /dev/null @@ -1,23 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 - -/// Specifies how names should be introduced at the file scope. -@_spi(Experimental) public enum FileScopeHandlingConfig { - /// This is the behavior that is being used - /// for Swift files with top-level code. - case memberBlockUpToLastDecl - /// This is the behavior that is being used - /// for Swift files that don’t allow top-level code. - case memberBlock -} diff --git a/Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift b/Sources/SwiftLexicalLookup/LookupConfig.swift similarity index 56% rename from Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift rename to Sources/SwiftLexicalLookup/LookupConfig.swift index 9b46b9dea8d..627d8edf2ea 100644 --- a/Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift +++ b/Sources/SwiftLexicalLookup/LookupConfig.swift @@ -11,8 +11,6 @@ //===----------------------------------------------------------------------===// @_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 @@ -33,41 +31,14 @@ /// 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, - finishInSequentialScope: Bool = false, - includeMembers: Bool = true + finishInSequentialScope: Bool = false ) { - self.fileScopeHandling = fileScopeHandling self.finishInSequentialScope = finishInSequentialScope - self.includeMembers = includeMembers } } diff --git a/Sources/SwiftLexicalLookup/LookupName.swift b/Sources/SwiftLexicalLookup/LookupName.swift index 3da5d712d4d..4530473d3bb 100644 --- a/Sources/SwiftLexicalLookup/LookupName.swift +++ b/Sources/SwiftLexicalLookup/LookupName.swift @@ -46,7 +46,7 @@ import SwiftSyntax } /// The name of the implicit declaration. - private var name: String { + private var name: StaticString { switch self { case .self: return "self" @@ -86,17 +86,38 @@ import SwiftSyntax /// `self` and `Self` identifers override implicit `self` and `Self` introduced by /// the `Foo` class declaration. var identifier: Identifier { + Identifier(name) + } + + /// Position of this implicit name. + @_spi(Experimental) public var position: AbsolutePosition { switch self { - case .self: - return Identifier("self") - case .Self: - return Identifier("Self") - case .error: - return Identifier("error") - case .newValue: - return Identifier("newValue") - case .oldValue: - return Identifier("oldValue") + 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.catchItems.positionAfterSkippingLeadingTrivia + default: + return syntax.positionAfterSkippingLeadingTrivia } } } @@ -110,6 +131,8 @@ import SwiftSyntax case declaration(NamedDeclSyntax) /// Name introduced implicitly by certain syntax nodes. case implicit(ImplicitDecl) + /// Dollar identifier introduced by a closure without parameters. + case dollarIdentifier(ClosureExprSyntax, strRepresentation: String) /// Syntax associated with this name. @_spi(Experimental) public var syntax: SyntaxProtocol { @@ -120,6 +143,8 @@ import SwiftSyntax return syntax case .implicit(let implicitName): return implicitName.syntax + case .dollarIdentifier(let closureExpr, _): + return closureExpr } } @@ -132,6 +157,8 @@ import SwiftSyntax return Identifier(syntax.name) case .implicit(let kind): return kind.identifier + case .dollarIdentifier(_, strRepresentation: _): + return nil } } @@ -149,34 +176,9 @@ import SwiftSyntax 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 - } + return implicitName.position + case .dollarIdentifier(let closureExpr, _): + return closureExpr.positionAfterSkippingLeadingTrivia } } @@ -197,6 +199,17 @@ import SwiftSyntax return accessibleAfter <= lookUpPosition } + func refersTo(_ otherIdentifier: Identifier?) -> Bool { + guard let otherIdentifier else { return true } + + switch self { + case .dollarIdentifier(_, let strRepresentation): + return strRepresentation == otherIdentifier.name + default: + return identifier == otherIdentifier + } + } + /// Extracts names introduced by the given `syntax` structure. /// /// When e.g. looking up a variable declaration like `let a = a`, @@ -221,6 +234,12 @@ import SwiftSyntax return tuplePattern.elements.flatMap { tupleElement in getNames(from: tupleElement.pattern, accessibleAfter: accessibleAfter) } + case .tupleExpr(let tupleExpr): + return tupleExpr.elements.flatMap { tupleElement in + getNames(from: tupleElement, accessibleAfter: accessibleAfter) + } + case .labeledExpr(let labeledExpr): + return getNames(from: labeledExpr.expression, accessibleAfter: accessibleAfter) case .valueBindingPattern(let valueBindingPattern): return getNames(from: valueBindingPattern.pattern, accessibleAfter: accessibleAfter) case .expressionPattern(let expressionPattern): @@ -288,6 +307,8 @@ import SwiftSyntax return "declaration: \(strName)" case .implicit: return "implicit: \(strName)" + case .dollarIdentifier(_, strRepresentation: let str): + return "dollarIdentifier: \(str)" } } } diff --git a/Sources/SwiftLexicalLookup/LookupResult.swift b/Sources/SwiftLexicalLookup/LookupResult.swift index e53d1b980bc..2bfe1fb3e7e 100644 --- a/Sources/SwiftLexicalLookup/LookupResult.swift +++ b/Sources/SwiftLexicalLookup/LookupResult.swift @@ -20,6 +20,36 @@ import SwiftSyntax case fromFileScope(SourceFileSyntax, withNames: [LookupName]) /// Indicates where to perform member lookup. case lookInMembers(LookInMembersScopeSyntax) + /// Indicates to lookup generic parameters of extended type. + /// + /// ### Example + /// ```swift + /// extension Foo { + /// func bar() { + /// let a = A() // <-- lookup here + /// } + /// } + /// ``` + /// For a lookup started at the marked position, `lookInGenericParametersOfExtendedType` + /// will be included as one of the results prompting the client + /// to lookup the generic parameters of of the extended `Foo` type. + case lookInGenericParametersOfExtendedType(ExtensionDeclSyntax) + /// Indicates this closure expression could introduce dollar identifiers. + /// + /// ### Example + /// ```swift + /// func foo() { + /// let a = { + /// $0 // <-- lookup here + /// } + /// } + /// ``` + /// When looking up for any identifier at the indicated position, + /// the result will include `mightIntroduceDollarIdentifiers` + /// result kind. If it's performed for a dollar identifier, `LookupName.dollarIdentifier` + /// with the appropriate identifier will be used in the + /// result associated with the closure expression inside `a`. + case mightIntroduceDollarIdentifiers(ClosureExprSyntax) /// Associated scope. @_spi(Experimental) public var scope: ScopeSyntax { @@ -30,6 +60,10 @@ import SwiftSyntax return fileScopeSyntax case .lookInMembers(let lookInMemb): return lookInMemb + case .lookInGenericParametersOfExtendedType(let extensionDecl): + return extensionDecl + case .mightIntroduceDollarIdentifiers(let closureExpr): + return closureExpr } } @@ -38,7 +72,9 @@ import SwiftSyntax switch self { case .fromScope(_, let names), .fromFileScope(_, let names): return names - case .lookInMembers(_): + case .lookInMembers(_), + .lookInGenericParametersOfExtendedType(_), + .mightIntroduceDollarIdentifiers(_): return [] } } @@ -53,6 +89,14 @@ import SwiftSyntax } } + /// Returns result specific for the particular `scope` kind with provided `names` + /// as an array with one element. If names are empty, returns an empty array. + static func getResultArray(for scope: ScopeSyntax, withNames names: [LookupName]) -> [LookupResult] { + guard !names.isEmpty else { return [] } + + return [getResult(for: scope, withNames: names)] + } + /// Debug description of this lookup name. @_spi(Experimental) public var debugDescription: String { var description = @@ -87,6 +131,10 @@ import SwiftSyntax return "fromFileScope" case .lookInMembers: return "lookInMembers" + case .lookInGenericParametersOfExtendedType(_): + return "lookInGenericParametersOfExtendedType" + case .mightIntroduceDollarIdentifiers(_): + return "mightIntroduceDollarIdentifiers" } } } diff --git a/Sources/SwiftLexicalLookup/Scopes/FunctionScopeSyntax.swift b/Sources/SwiftLexicalLookup/Scopes/FunctionScopeSyntax.swift index ba49c47e996..246a8df9cec 100644 --- a/Sources/SwiftLexicalLookup/Scopes/FunctionScopeSyntax.swift +++ b/Sources/SwiftLexicalLookup/Scopes/FunctionScopeSyntax.swift @@ -12,13 +12,14 @@ import SwiftSyntax -protocol FunctionScopeSyntax: DeclSyntaxProtocol, WithGenericParametersScopeSyntax { +@_spi(Experimental) public protocol FunctionScopeSyntax: DeclSyntaxProtocol, WithGenericParametersScopeSyntax { var signature: FunctionSignatureSyntax { get } + var body: CodeBlockSyntax? { get } } extension FunctionScopeSyntax { /// Function parameters introduced by this function's signature. - @_spi(Experimental) public var introducedNames: [LookupName] { + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { signature.parameterClause.parameters.flatMap { parameter in LookupName.getNames(from: parameter) } + (parentScope?.is(MemberBlockSyntax.self) ?? false ? [.implicit(.self(self))] : []) @@ -33,7 +34,7 @@ extension FunctionScopeSyntax { ) -> [LookupResult] { var thisScopeResults: [LookupResult] = [] - if !signature.range.contains(lookUpPosition) { + if body?.range.contains(lookUpPosition) ?? false { thisScopeResults = defaultLookupImplementation( identifier, at: position, diff --git a/Sources/SwiftLexicalLookup/Scopes/NominalTypeDeclSyntax.swift b/Sources/SwiftLexicalLookup/Scopes/NominalTypeDeclSyntax.swift index f509486881b..6b5b3d261b4 100644 --- a/Sources/SwiftLexicalLookup/Scopes/NominalTypeDeclSyntax.swift +++ b/Sources/SwiftLexicalLookup/Scopes/NominalTypeDeclSyntax.swift @@ -12,24 +12,26 @@ import SwiftSyntax -protocol NominalTypeDeclSyntax: LookInMembersScopeSyntax, NamedDeclSyntax, WithGenericParametersScopeSyntax { +@_spi(Experimental) +public protocol NominalTypeDeclSyntax: LookInMembersScopeSyntax, NamedDeclSyntax, WithGenericParametersScopeSyntax { var genericParameterClause: GenericParameterClauseSyntax? { get } + var genericWhereClause: GenericWhereClauseSyntax? { get } var inheritanceClause: InheritanceClauseSyntax? { get } } extension NominalTypeDeclSyntax { @_spi(Experimental) public var lookupMembersPosition: AbsolutePosition { - name.position + name.positionAfterSkippingLeadingTrivia } /// Nominal type doesn't introduce any names by itself. - @_spi(Experimental) public var introducedNames: [LookupName] { + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { [] } /// Function used by generic parameter clause /// scope on return from it's lookup. - func returningLookupFromGenericParameterScope( + @_spi(Experimental) public func returningLookupFromGenericParameterScope( _ identifier: Identifier?, at lookUpPosition: AbsolutePosition, with config: LookupConfig @@ -38,7 +40,7 @@ extension NominalTypeDeclSyntax { 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) { + } else if name.range.contains(lookUpPosition) || genericWhereClause?.range.contains(lookUpPosition) ?? false { 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 27b29928806..81e8e649929 100644 --- a/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift +++ b/Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift @@ -31,127 +31,31 @@ import SwiftSyntax } @_spi(Experimental) extension SourceFileSyntax: SequentialScopeSyntax { - /// All names introduced in the file scope - /// according to the default strategy: `memberBlockUpToLastDecl`. - @_spi(Experimental) public var introducedNames: [LookupName] { - introducedNames(using: .memberBlockUpToLastDecl) + /// File Scope doesn't introduce any names. + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { + [] } @_spi(Experimental) public var scopeDebugName: String { "FileScope" } - /// All names introduced in the file scope - /// using the provided configuration. - /// - /// Example usage: - /// ```swift - /// class a {} - /// class b { - /// // <-- - /// } - /// let c = 0 - /// class d {} - /// if true {} - /// class e {} - /// let f = 0 - /// ``` - /// During lookup, according to different configurations, - /// names available at the marked place are: - /// - for `memberBlockUpToLastDecl` - a, b, c, d - /// - for `memberBlock` - a, b, c, d, e, f - /// - for `codeBlock` - a - @_spi(Experimental) public func introducedNames(using fileScopeHandling: FileScopeHandlingConfig) -> [LookupName] { - switch fileScopeHandling { - case .memberBlockUpToLastDecl: - var encounteredNonDeclaration = false - - return statements.flatMap { codeBlockItem in - let item = codeBlockItem.item - - if encounteredNonDeclaration { - return LookupName.getNames(from: item, accessibleAfter: codeBlockItem.endPosition) - } else { - if item.is(DeclSyntax.self) { - return LookupName.getNames(from: item) - } else { - encounteredNonDeclaration = true - return LookupName.getNames(from: item, accessibleAfter: codeBlockItem.endPosition) - } - } - } - case .memberBlock: - return statements.flatMap { codeBlockItem in - LookupName.getNames(from: codeBlockItem.item) - } - } - } - - /// Returns names matching lookup using provided file - /// scope handling configuration. - /// - /// Example usage: - /// ```swift - /// class a {} - /// class b { - /// // <-- - /// } - /// let c = 0 - /// class d {} - /// if true {} - /// class e {} - /// let f = 0 - /// ``` - /// According to different configurations, - /// names available at the marked place are: - /// - for `memberBlockUpToLastDecl` - a, b, c, d - /// - for `memberBlock` - a, b, c, d, e, f - /// - for `codeBlock` - a + /// In file scope, introduce only from `guard`. @_spi(Experimental) public func lookup( _ identifier: Identifier?, at lookUpPosition: AbsolutePosition, with config: LookupConfig ) -> [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) - } - - return names.isEmpty ? [] : [.fromFileScope(self, withNames: names)] - case .memberBlockUpToLastDecl: - var members: [LookupName] = [] - var sequentialItems: [CodeBlockItemSyntax] = [] - var encounteredNonDeclaration = false - - for codeBlockItem in statements { - let item = codeBlockItem.item - - if encounteredNonDeclaration { - sequentialItems.append(codeBlockItem) - } else if item.is(DeclSyntax.self) { - let foundNames = LookupName.getNames(from: item) - members.append( - contentsOf: foundNames.filter { checkIdentifier(identifier, refersTo: $0, at: lookUpPosition) } - ) - } else { - encounteredNonDeclaration = true - sequentialItems.append(codeBlockItem) - } + return statements.flatMap { codeBlockItem in + if let guardStmt = codeBlockItem.item.as(GuardStmtSyntax.self) { + return guardStmt.lookupFromSequentialParent( + identifier, + at: lookUpPosition, + with: config + ) + } else { + return [] } - - let sequentialNames = sequentialLookup( - in: sequentialItems, - identifier, - at: lookUpPosition, - with: config - ) - - return (members.isEmpty || !config.includeMembers ? [] : [.fromFileScope(self, withNames: members)]) - + sequentialNames } } } @@ -159,7 +63,7 @@ import SwiftSyntax @_spi(Experimental) extension CodeBlockSyntax: SequentialScopeSyntax { /// Names introduced in the code block scope /// accessible after their declaration. - @_spi(Experimental) public var introducedNames: [LookupName] { + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { statements.flatMap { codeBlockItem in LookupName.getNames(from: codeBlockItem.item, accessibleAfter: codeBlockItem.endPosition) } @@ -185,7 +89,7 @@ import SwiftSyntax @_spi(Experimental) extension ForStmtSyntax: ScopeSyntax { /// Names introduced in the `for` body. - @_spi(Experimental) public var introducedNames: [LookupName] { + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { LookupName.getNames(from: pattern) } @@ -210,25 +114,24 @@ import SwiftSyntax } @_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) - } ?? [] - - let parameterNames = - signature?.parameterClause?.children(viewMode: .sourceAccurate).flatMap { parameter in - if let parameterList = parameter.as(ClosureParameterListSyntax.self) { - return parameterList.children(viewMode: .sourceAccurate).flatMap { parameter in - LookupName.getNames(from: parameter) - } - } else { - return LookupName.getNames(from: parameter) - } - } ?? [] + /// Closure capture names introduced in this closure expression. + var captureNames: [LookupName] { + signature?.capture?.items.flatMap { element in + LookupName.getNames(from: element) + } ?? [] + } - return captureNames + parameterNames + /// Parameter names introduced in this closure expression. + var parameterNames: [LookupName] { + signature?.parameterClause?.children(viewMode: .sourceAccurate).flatMap { parameter in + if let parameterList = parameter.as(ClosureParameterListSyntax.self) { + return parameterList.children(viewMode: .sourceAccurate).flatMap { parameter in + LookupName.getNames(from: parameter) + } + } else { + return LookupName.getNames(from: parameter) + } + } ?? [] } /// Names introduced sequentially in body. @@ -238,8 +141,9 @@ import SwiftSyntax } } - @_spi(Experimental) public var introducedNames: [LookupName] { - introducedNamesInSignature + introducedNamesInBody + /// Capture, parameter and body names introduced in this scope. + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { + captureNames + parameterNames + introducedNamesInBody } @_spi(Experimental) public var scopeDebugName: String { @@ -265,30 +169,51 @@ import SwiftSyntax 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( + let sequentialResults = sequentialLookup( in: statements, identifier, at: lookUpPosition, with: config, propagateToParent: false - ) + (filteredSignatureNames.isEmpty ? [] : [.fromScope(self, withNames: filteredSignatureNames)]) - + (config.finishInSequentialScope ? [] : lookupInParent(identifier, at: lookUpPosition, with: config)) + ) + + guard !config.finishInSequentialScope else { return sequentialResults } + + let signatureResults: [LookupResult] + + if signature?.range.contains(lookUpPosition) ?? false { + signatureResults = [] + } else if parameterNames.isEmpty { + let filteredCaptureNames = captureNames.filter { name in + checkIdentifier(identifier, refersTo: name, at: lookUpPosition) + } + + if let dollarIdentifierStr = identifier?.dollarIdentifierStr { + signatureResults = LookupResult.getResultArray( + for: self, + withNames: filteredCaptureNames + [LookupName.dollarIdentifier(self, strRepresentation: dollarIdentifierStr)] + ) + } else { + signatureResults = + LookupResult.getResultArray(for: self, withNames: filteredCaptureNames) + + [.mightIntroduceDollarIdentifiers(self)] + } + } else { + signatureResults = LookupResult.getResultArray( + for: self, + withNames: (captureNames + parameterNames).filter { name in + checkIdentifier(identifier, refersTo: name, at: lookUpPosition) + } + ) + } + + return sequentialResults + signatureResults + 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] { + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { conditions.reversed().flatMap { element in LookupName.getNames(from: element.condition, accessibleAfter: element.endPositionBeforeTrailingTrivia) } @@ -337,7 +262,7 @@ import SwiftSyntax } /// Names introduced by the `if` optional binding conditions. - @_spi(Experimental) public var introducedNames: [LookupName] { + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { conditions.reversed().flatMap { element in LookupName.getNames(from: element.condition, accessibleAfter: element.endPosition) } @@ -373,31 +298,15 @@ import SwiftSyntax } @_spi(Experimental) extension MemberBlockSyntax: ScopeSyntax { - /// All names introduced by members of this member scope. - @_spi(Experimental) public var introducedNames: [LookupName] { - members.flatMap { member in - LookupName.getNames(from: member.decl) - } + /// Member Block Scope doesn't introduce any results. + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { + [] } @_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( @@ -413,18 +322,19 @@ import SwiftSyntax checkIdentifier(identifier, refersTo: name, at: lookUpPosition) } - return filteredNames.isEmpty ? [] : [.fromScope(self, withNames: filteredNames)] + return LookupResult.getResultArray(for: self, withNames: filteredNames) } } @_spi(Experimental) extension GuardStmtSyntax: IntroducingToSequentialParentScopeSyntax { + /// Names introduced in `guard` conditions to the sequential parent. var namesIntroducedToSequentialParent: [LookupName] { conditions.reversed().flatMap { element in LookupName.getNames(from: element.condition, accessibleAfter: element.endPosition) } } - @_spi(Experimental) public var introducedNames: [LookupName] { + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { [] } @@ -451,11 +361,11 @@ import SwiftSyntax ) -> [LookupResult] { guard !body.range.contains(lookUpPosition) else { return [] } - let names = namesIntroducedToSequentialParent.filter { introducedName in + let filteredNames = namesIntroducedToSequentialParent.filter { introducedName in checkIdentifier(identifier, refersTo: introducedName, at: lookUpPosition) } - return names.isEmpty ? [] : [.fromScope(self, withNames: names)] + return LookupResult.getResultArray(for: self, withNames: filteredNames) } } @@ -484,7 +394,7 @@ import SwiftSyntax extendedType.position } - @_spi(Experimental) public var introducedNames: [LookupName] { + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { [] } @@ -492,6 +402,8 @@ import SwiftSyntax "ExtensionDeclScope" } + /// Returns results matching lookup, including implicit `Self`, + /// `lookInGenericParametersOfExtendedType` and `lookInMembers` depending on `lookupPosition`. @_spi(Experimental) public func lookup( _ identifier: Identifier?, at lookUpPosition: AbsolutePosition, @@ -503,17 +415,46 @@ import SwiftSyntax checkIdentifier(identifier, refersTo: name, at: lookUpPosition) } - return (implicitSelf.isEmpty ? [] : [.fromScope(self, withNames: implicitSelf)]) + [.lookInMembers(self)] + return LookupResult.getResultArray(for: self, withNames: implicitSelf) + + [.lookInGenericParametersOfExtendedType(self)] + + defaultLookupImplementation(identifier, at: lookUpPosition, with: config, propagateToParent: false) + + [.lookInMembers(self)] + + lookupInParent(identifier, at: lookUpPosition, with: config) + } else if !extendedType.range.contains(lookUpPosition) && genericWhereClause != nil { + if inRightTypeOrSameTypeRequirement(lookUpPosition) { + return [.lookInGenericParametersOfExtendedType(self)] + [.lookInMembers(self)] + + defaultLookupImplementation(identifier, at: lookUpPosition, with: config) + } + + return [.lookInGenericParametersOfExtendedType(self)] + defaultLookupImplementation(identifier, at: lookUpPosition, with: config) - } else { - return defaultLookupImplementation(identifier, at: lookUpPosition, with: config) } + + return [.lookInGenericParametersOfExtendedType(self)] + + lookupInParent(identifier, at: lookUpPosition, with: config) + } + + /// Returns `true` if `checkedPosition` is a right type of a + /// conformance requirement or inside a same type requirement. + private func inRightTypeOrSameTypeRequirement( + _ checkedPosition: AbsolutePosition + ) -> Bool { + genericWhereClause?.requirements.contains { elem in + switch Syntax(elem.requirement).as(SyntaxEnum.self) { + case .conformanceRequirement(let conformanceRequirement): + return conformanceRequirement.rightType.range.contains(checkedPosition) + case .sameTypeRequirement(let sameTypeRequirement): + return sameTypeRequirement.range.contains(checkedPosition) + default: + return false + } + } ?? false } } @_spi(Experimental) extension AccessorDeclSyntax: ScopeSyntax { /// Implicit and/or explicit names introduced within the accessor. - @_spi(Experimental) public var introducedNames: [LookupName] { + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { if let parameters { return LookupName.getNames(from: parameters) } else { @@ -570,26 +511,68 @@ import SwiftSyntax @_spi(Experimental) extension CatchClauseSyntax: ScopeSyntax { /// Implicit `error` when there are no catch items. - @_spi(Experimental) public var introducedNames: [LookupName] { - return catchItems.isEmpty ? [.implicit(.error(self))] : [] + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { + let extractedNames = catchItems.flatMap { item in + guard let pattern = item.pattern else { return [LookupName]() } + + return LookupName.getNames(from: pattern) + } + + return extractedNames.isEmpty ? [.implicit(.error(self))] : extractedNames } @_spi(Experimental) public var scopeDebugName: String { "CatchClauseScope" } + + /// Returns results matching lookup. Includes names possibly introduced by this scope + /// if `lookupPosition` is either in body or one of the where clauses of catch items. + @_spi(Experimental) public func lookup( + _ identifier: Identifier?, + at lookUpPosition: AbsolutePosition, + with config: LookupConfig + ) -> [LookupResult] { + if body.range.contains(lookUpPosition) || isLookupFromWhereClause(lookUpPosition) { + return defaultLookupImplementation( + identifier, + at: lookUpPosition, + with: config + ) + } else { + return lookupInParent( + identifier, + at: lookUpPosition, + with: config + ) + } + } + + /// Returns `true` if `checkedPosition` is in one + /// of the catch items' where clauses and `false` otherwise. + func isLookupFromWhereClause( + _ checkedPosition: AbsolutePosition + ) -> Bool { + catchItems.contains { item in + item.whereClause?.range.contains(checkedPosition) ?? false + } + } } @_spi(Experimental) extension SwitchCaseSyntax: SequentialScopeSyntax { /// Names introduced within `case` items. var namesFromLabel: [LookupName] { label.as(SwitchCaseLabelSyntax.self)?.caseItems.flatMap { child in - LookupName.getNames(from: child.pattern) + if let exprPattern = child.pattern.as(ExpressionPatternSyntax.self) { + return LookupName.getNames(from: exprPattern.expression) + } else { + return 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] { + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { statements.flatMap { codeBlockItem in LookupName.getNames(from: codeBlockItem.item, accessibleAfter: codeBlockItem.endPosition) } + namesFromLabel @@ -617,14 +600,14 @@ import SwiftSyntax at: lookUpPosition, with: config, propagateToParent: false - ) + (filteredNamesFromLabel.isEmpty ? [] : [.fromScope(self, withNames: filteredNamesFromLabel)]) + ) + LookupResult.getResultArray(for: self, withNames: filteredNamesFromLabel) + (config.finishInSequentialScope ? [] : lookupInParent(identifier, at: lookUpPosition, with: config)) } } @_spi(Experimental) extension ProtocolDeclSyntax: ScopeSyntax, LookInMembersScopeSyntax { /// Protocol declarations don't introduce names by themselves. - @_spi(Experimental) public var introducedNames: [LookupName] { + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { [.implicit(.Self(self))] } @@ -671,7 +654,13 @@ import SwiftSyntax ) } - let lookInMembers: [LookupResult] = memberBlock.range.contains(lookUpPosition) ? [.lookInMembers(self)] : [] + let lookInMembers: [LookupResult] + + if !(inheritanceClause?.range.contains(lookUpPosition) ?? false) { + lookInMembers = [.lookInMembers(self)] + } else { + lookInMembers = [] + } return results + defaultLookupImplementation( @@ -685,7 +674,7 @@ import SwiftSyntax @_spi(Experimental) extension GenericParameterClauseSyntax: GenericParameterScopeSyntax { /// Generic parameter names introduced by this clause. - @_spi(Experimental) public var introducedNames: [LookupName] { + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { parameters.children(viewMode: .fixedUp).flatMap { child in LookupName.getNames(from: child) } @@ -711,7 +700,7 @@ import SwiftSyntax @_spi(Experimental) extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax, CanInterleaveResultsLaterScopeSyntax { /// Parameters introduced by this subscript and possibly `self` keyword. - @_spi(Experimental) public var introducedNames: [LookupName] { + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { let parameters = parameterClause.parameters.flatMap { parameter in LookupName.getNames(from: parameter) } @@ -790,7 +779,7 @@ extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax, CanInterleaveRe @_spi(Experimental) extension AccessorBlockSyntax: SequentialScopeSyntax, CanInterleaveResultsLaterScopeSyntax { /// Names from the accessors or /// getters of this accessor block scope. - @_spi(Experimental) public var introducedNames: [LookupName] { + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { switch accessors { case .getter(let codeBlockItems): return codeBlockItems.flatMap { codeBlockItem in @@ -847,7 +836,7 @@ extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax, CanInterleaveRe @_spi(Experimental) extension TypeAliasDeclSyntax: WithGenericParametersScopeSyntax { /// Type alias doesn't introduce any names to it's children. - @_spi(Experimental) public var introducedNames: [LookupName] { + @_spi(Experimental) public var defaultIntroducedNames: [LookupName] { [] } @@ -860,7 +849,7 @@ extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax, CanInterleaveRe /// 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 defaultIntroducedNames: [LookupName] { [] } @@ -877,10 +866,10 @@ extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax, CanInterleaveRe with config: LookupConfig ) -> [LookupResult] { if bindings.first?.accessorBlock?.range.contains(lookUpPosition) ?? false { - let shouldIntroduceSelf = parentScope?.is(MemberBlockSyntax.self) ?? false + let isMember = parentScope?.is(MemberBlockSyntax.self) ?? false return defaultLookupImplementation( - in: LookupName.getNames(from: self) + (shouldIntroduceSelf ? [.implicit(.self(self))] : []), + in: (isMember ? [.implicit(.self(self))] : LookupName.getNames(from: self)), identifier, at: lookUpPosition, with: config diff --git a/Sources/SwiftLexicalLookup/Scopes/ScopeSyntax.swift b/Sources/SwiftLexicalLookup/Scopes/ScopeSyntax.swift index 1d7eb36f95c..64e2bc50d65 100644 --- a/Sources/SwiftLexicalLookup/Scopes/ScopeSyntax.swift +++ b/Sources/SwiftLexicalLookup/Scopes/ScopeSyntax.swift @@ -54,8 +54,10 @@ extension SyntaxProtocol { @_spi(Experimental) public protocol ScopeSyntax: SyntaxProtocol { /// Parent of this scope, or `nil` if it is the root. var parentScope: ScopeSyntax? { get } - /// Names found in this scope. Ordered from first to last introduced. - var introducedNames: [LookupName] { get } + /// Names introduced by default in this scope. + /// Don't include names that might be added depending on the lookup position (like `self`). + /// Ordered from first to last introduced. + var defaultIntroducedNames: [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. @@ -94,14 +96,13 @@ extension SyntaxProtocol { propagateToParent: Bool = true ) -> [LookupResult] { let filteredNames = - (names ?? introducedNames) + (names ?? defaultIntroducedNames) .filter { introducedName in checkIdentifier(identifier, refersTo: introducedName, at: lookUpPosition) } - let fromThisScope = filteredNames.isEmpty ? [] : [LookupResult.fromScope(self, withNames: filteredNames)] - - return fromThisScope + (propagateToParent ? lookupInParent(identifier, at: lookUpPosition, with: config) : []) + return LookupResult.getResultArray(for: self, withNames: filteredNames) + + (propagateToParent ? lookupInParent(identifier, at: lookUpPosition, with: config) : []) } /// Looks up in parent scope. @@ -118,7 +119,7 @@ extension SyntaxProtocol { refersTo introducedName: LookupName, at lookUpPosition: AbsolutePosition ) -> Bool { - introducedName.isAccessible(at: lookUpPosition) && (identifier == nil || introducedName.identifier == identifier!) + introducedName.isAccessible(at: lookUpPosition) && introducedName.refersTo(identifier) } /// Debug description of this scope. diff --git a/Sources/SwiftLexicalLookup/Scopes/WithGenericParametersScopeSyntax.swift b/Sources/SwiftLexicalLookup/Scopes/WithGenericParametersScopeSyntax.swift index be6582c5005..8bf75dfa824 100644 --- a/Sources/SwiftLexicalLookup/Scopes/WithGenericParametersScopeSyntax.swift +++ b/Sources/SwiftLexicalLookup/Scopes/WithGenericParametersScopeSyntax.swift @@ -12,7 +12,7 @@ import SwiftSyntax -protocol WithGenericParametersScopeSyntax: ScopeSyntax { +@_spi(Experimental) public protocol WithGenericParametersScopeSyntax: ScopeSyntax { var genericParameterClause: GenericParameterClauseSyntax? { get } func returningLookupFromGenericParameterScope( @@ -73,7 +73,7 @@ protocol WithGenericParametersScopeSyntax: ScopeSyntax { /// function declaration scope and then to generic parameter /// scope (`WithGenericParametersScopeSyntax`) /// with this method (instead of using standard `lookupInParent`). - func lookupThroughGenericParameterScope( + @_spi(Experimental) public func lookupThroughGenericParameterScope( _ identifier: Identifier?, at lookUpPosition: AbsolutePosition, with config: LookupConfig @@ -85,7 +85,7 @@ protocol WithGenericParametersScopeSyntax: ScopeSyntax { } } - func returningLookupFromGenericParameterScope( + @_spi(Experimental) public func returningLookupFromGenericParameterScope( _ identifier: Identifier?, at lookUpPosition: AbsolutePosition, with config: LookupConfig diff --git a/Sources/SwiftSyntax/Identifier.swift b/Sources/SwiftSyntax/Identifier.swift index 320e125e2b3..a4554f21814 100644 --- a/Sources/SwiftSyntax/Identifier.swift +++ b/Sources/SwiftSyntax/Identifier.swift @@ -17,6 +17,8 @@ public struct Identifier: Equatable, Hashable, Sendable { String(syntaxText: raw.name) } + public let dollarIdentifierStr: String? + @_spi(RawSyntax) public let raw: RawIdentifier private let arena: SyntaxArenaRef? @@ -26,6 +28,13 @@ public struct Identifier: Equatable, Hashable, Sendable { case .identifier, .keyword(.self), .keyword(.Self): self.raw = RawIdentifier(token.tokenView) self.arena = token.raw.arenaReference + + self.dollarIdentifierStr = nil + case .dollarIdentifier(let dollarIdentifierStr): + self.raw = RawIdentifier(token.tokenView) + self.arena = token.raw.arenaReference + + self.dollarIdentifierStr = dollarIdentifierStr default: return nil } @@ -34,11 +43,25 @@ public struct Identifier: Equatable, Hashable, Sendable { public init(_ staticString: StaticString) { self.raw = RawIdentifier(staticString) self.arena = nil + + let name = String(syntaxText: raw.name) + + if name.first == "$" && Int(name.dropFirst()) != nil { + self.dollarIdentifierStr = name + } else { + self.dollarIdentifierStr = nil + } } public static func == (lhs: Self, rhs: Self) -> Bool { lhs.name == rhs.name } + + private static func getDollarIdentifierNumber(str: String) -> Int? { + guard str.first == "$" else { return nil } + + return Int(str.dropFirst()) + } } @_spi(RawSyntax) diff --git a/Tests/SwiftLexicalLookupTest/ExpectedName.swift b/Tests/SwiftLexicalLookupTest/ExpectedName.swift index 4047ef90f3b..059a4a6d1db 100644 --- a/Tests/SwiftLexicalLookupTest/ExpectedName.swift +++ b/Tests/SwiftLexicalLookupTest/ExpectedName.swift @@ -62,11 +62,13 @@ enum NameExpectation: ExpectedName { case identifier(String) case declaration(String) case implicit(ImplicitNameExpectation) + case dollarIdentifier(String, String) var marker: String { switch self { case .identifier(let marker), - .declaration(let marker): + .declaration(let marker), + .dollarIdentifier(let marker, _): return marker case .implicit(let implicitName): return implicitName.marker @@ -79,6 +81,11 @@ enum NameExpectation: ExpectedName { case (.declaration, .declaration): break case (.implicit(let implicitName), .implicit(let implicitNameExpectation)): implicitNameExpectation.assertExpectation(marker: marker, for: implicitName) + case (.dollarIdentifier(_, let actualStr), .dollarIdentifier(_, let expectedStr)): + XCTAssert( + actualStr == expectedStr, + "For marker \(marker), actual identifier \(actualStr) doesn't match expected \(expectedStr)" + ) default: XCTFail("For marker \(marker), actual name kind \(name) doesn't match expected \(self)") } diff --git a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift index 4a3d283f174..61e30abf590 100644 --- a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift +++ b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift @@ -136,6 +136,31 @@ final class testNameLookup: XCTestCase { ) } + func testDollarIdentifiers() { + assertLexicalNameLookup( + source: """ + func foo() { + let 0️⃣a = 1 + let x = 5️⃣{ + print(2️⃣a, 3️⃣$0, 4️⃣$123) + } + } + """, + references: [ + "2️⃣": [ + .mightIntroduceDollarIdentifiers, + .fromScope(CodeBlockSyntax.self, expectedNames: ["0️⃣"]), + ], + "3️⃣": [ + .fromScope(ClosureExprSyntax.self, expectedNames: [NameExpectation.dollarIdentifier("5️⃣", "$0")]) + ], + "4️⃣": [ + .fromScope(ClosureExprSyntax.self, expectedNames: [NameExpectation.dollarIdentifier("5️⃣", "$123")]) + ], + ] + ) + } + func testClosureCaptureLookup() { assertLexicalNameLookup( source: """ @@ -152,17 +177,19 @@ final class testNameLookup: XCTestCase { references: [ "5️⃣": [ .fromScope(ClosureExprSyntax.self, expectedNames: [NameExpectation.identifier("2️⃣")]), + .mightIntroduceDollarIdentifiers, .fromScope(FunctionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("9️⃣"))]), .lookInMembers(ClassDeclSyntax.self), ], "6️⃣": [ .fromScope(ClosureExprSyntax.self, expectedNames: ["3️⃣"]), + .mightIntroduceDollarIdentifiers, .fromScope(CodeBlockSyntax.self, expectedNames: ["1️⃣"]), .lookInMembers(ClassDeclSyntax.self), - .fromFileScope(expectedNames: ["7️⃣"]), ], "8️⃣": [ .fromScope(ClosureExprSyntax.self, expectedNames: ["4️⃣"]), + .mightIntroduceDollarIdentifiers, .lookInMembers(ClassDeclSyntax.self), ], ], @@ -294,55 +321,6 @@ final class testNameLookup: XCTestCase { ) } - func testMemberBlockScope() { - assertLexicalNameLookup( - source: """ - class x { - var 1️⃣a = 1 - - 2️⃣class b {} - 3️⃣struct b {} - - 4️⃣func a { - 5️⃣a - 6️⃣b - 7️⃣c - 8️⃣d - } - - 9️⃣actor c {} - 0️⃣protocol d {} - } - """, - references: [ - "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, - "2️⃣": ClassDeclSyntax.self, - "3️⃣": StructDeclSyntax.self, - "4️⃣": FunctionDeclSyntax.self, - "9️⃣": ActorDeclSyntax.self, - "0️⃣": ProtocolDeclSyntax.self, - ]) - ) - } - func testLookupInDeclaration() { assertLexicalNameLookup( source: """ @@ -362,26 +340,21 @@ final class testNameLookup: XCTestCase { """, references: [ "2️⃣": [ - .fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "9️⃣"]), - .lookInMembers(ClassDeclSyntax.self), + .lookInMembers(ClassDeclSyntax.self) ], "0️⃣": [ - .fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "9️⃣"]), - .lookInMembers(ClassDeclSyntax.self), + .lookInMembers(ClassDeclSyntax.self) ], "4️⃣": [ - .fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "9️⃣"]), - .lookInMembers(ClassDeclSyntax.self), + .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), ], ], @@ -442,9 +415,7 @@ final class testNameLookup: XCTestCase { 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️⃣"]), @@ -454,9 +425,7 @@ final class testNameLookup: XCTestCase { FunctionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("3️⃣"))] ), - .fromScope(MemberBlockSyntax.self, expectedNames: ["1️⃣", "2️⃣", "3️⃣"]), .lookInMembers(ClassDeclSyntax.self), - .fromFileScope(expectedNames: ["🔟"]), ], ], expectedResultTypes: .all( @@ -524,97 +493,6 @@ final class testNameLookup: XCTestCase { ) } - func testSimpleFileScope() { - assertLexicalNameLookup( - source: """ - 1️⃣class a {} - - 2️⃣class b { - let x = 3️⃣a + 4️⃣b + 5️⃣c + 6️⃣d - } - - let 8️⃣a = 0 - - 7️⃣class c {} - - if a == 0 {} - - 9️⃣class d {} - - let 🔟a = 0️⃣d - """, - references: [ - "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]) - ) - } - - func testFileScopeAsMember() { - assertLexicalNameLookup( - source: """ - 1️⃣class a {} - - 2️⃣class b { - let x = 3️⃣a + 4️⃣b + 5️⃣c + 6️⃣d - } - - let 8️⃣a = 0 - - 7️⃣class c {} - - if a == 0 {} - - 9️⃣class d {} - - let 🔟a = 0️⃣d - """, - references: [ - "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, - "🔟": IdentifierPatternSyntax.self, - ] - ), - config: LookupConfig(fileScopeHandling: .memberBlock) - ) - } - func testDeclarationAvailabilityInCodeBlock() { assertLexicalNameLookup( source: """ @@ -648,12 +526,10 @@ final class testNameLookup: XCTestCase { """, references: [ "4️⃣": [ - .fromFileScope(expectedNames: ["1️⃣"]), - .fromScope(GuardStmtSyntax.self, expectedNames: ["2️⃣"]), - .fromFileScope(expectedNames: ["3️⃣"]), + .fromScope(GuardStmtSyntax.self, expectedNames: ["2️⃣"]) ] ], - expectedResultTypes: .all(IdentifierPatternSyntax.self, except: ["3️⃣": ClassDeclSyntax.self]) + expectedResultTypes: .all(IdentifierPatternSyntax.self) ) } @@ -676,19 +552,23 @@ final class testNameLookup: XCTestCase { "3️⃣": [ .lookInMembers(StructDeclSyntax.self), .fromScope(ExtensionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.Self("7️⃣"))]), + .lookInGenericParametersOfExtendedType, .lookInMembers(ExtensionDeclSyntax.self), ], "4️⃣": [ .fromScope(FunctionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("2️⃣"))]), .lookInMembers(StructDeclSyntax.self), + .lookInGenericParametersOfExtendedType, .lookInMembers(ExtensionDeclSyntax.self), ], "5️⃣": [ .fromScope(ExtensionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.Self("7️⃣"))]), + .lookInGenericParametersOfExtendedType, .lookInMembers(ExtensionDeclSyntax.self), ], "6️⃣": [ .fromScope(FunctionDeclSyntax.self, expectedNames: [NameExpectation.implicit(.self("1️⃣"))]), + .lookInGenericParametersOfExtendedType, .lookInMembers(ExtensionDeclSyntax.self), ], ] @@ -816,7 +696,7 @@ final class testNameLookup: XCTestCase { do { try x.bar() 2️⃣error - } catch SomeError { + } 6️⃣catch SomeError { 3️⃣error } 4️⃣catch { 5️⃣error @@ -825,7 +705,10 @@ final class testNameLookup: XCTestCase { """, references: [ "2️⃣": [.fromScope(CodeBlockSyntax.self, expectedNames: [NameExpectation.identifier("1️⃣")])], - "3️⃣": [.fromScope(CodeBlockSyntax.self, expectedNames: [NameExpectation.identifier("1️⃣")])], + "3️⃣": [ + .fromScope(CatchClauseSyntax.self, expectedNames: [NameExpectation.implicit(.error("6️⃣"))]), + .fromScope(CodeBlockSyntax.self, expectedNames: [NameExpectation.identifier("1️⃣")]), + ], "5️⃣": [ .fromScope(CatchClauseSyntax.self, expectedNames: [NameExpectation.implicit(.error("4️⃣"))]), .fromScope(CodeBlockSyntax.self, expectedNames: [NameExpectation.identifier("1️⃣")]), @@ -960,11 +843,10 @@ final class testNameLookup: XCTestCase { ], "8️⃣": [ .lookInMembers(ClassDeclSyntax.self), - .fromScope(MemberBlockSyntax.self, expectedNames: ["7️⃣"]), .lookInMembers(ClassDeclSyntax.self), ], ], - expectedResultTypes: .all(GenericParameterSyntax.self, except: ["7️⃣": IdentifierPatternSyntax.self]) + expectedResultTypes: .all(GenericParameterSyntax.self) ) } @@ -1007,10 +889,11 @@ final class testNameLookup: XCTestCase { references: [ "1️⃣": [ .fromScope(MemberBlockSyntax.self, expectedNames: ["3️⃣"]), - .fromFileScope(expectedNames: ["0️⃣"]), + .lookInMembers(ProtocolDeclSyntax.self), ], "2️⃣": [ - .fromScope(MemberBlockSyntax.self, expectedNames: ["4️⃣"]) + .fromScope(MemberBlockSyntax.self, expectedNames: ["4️⃣"]), + .lookInMembers(ProtocolDeclSyntax.self), ], ], expectedResultTypes: .all( @@ -1049,7 +932,6 @@ final class testNameLookup: XCTestCase { ], "0️⃣": [ .fromScope(FunctionDeclSyntax.self, expectedNames: ["5️⃣"]), - .fromScope(MemberBlockSyntax.self, expectedNames: ["2️⃣"]), .lookInMembers(ClassDeclSyntax.self), ], "🔟": [ @@ -1060,7 +942,6 @@ final class testNameLookup: XCTestCase { expectedResultTypes: .all( GenericParameterSyntax.self, except: [ - "2️⃣": IdentifierPatternSyntax.self, "5️⃣": FunctionParameterSyntax.self, "7️⃣": FunctionParameterSyntax.self, ] @@ -1101,14 +982,12 @@ final class testNameLookup: XCTestCase { .lookInMembers(ClassDeclSyntax.self), ], "🔟": [ - .fromScope(MemberBlockSyntax.self, expectedNames: ["0️⃣"]), - .lookInMembers(ClassDeclSyntax.self), + .lookInMembers(ClassDeclSyntax.self) ], ], expectedResultTypes: .all( GenericParameterSyntax.self, except: [ - "0️⃣": IdentifierPatternSyntax.self, "3️⃣": FunctionParameterSyntax.self, "5️⃣": FunctionParameterSyntax.self, ] @@ -1121,17 +1000,15 @@ final class testNameLookup: XCTestCase { source: """ typealias SomeType<1️⃣A> = X<2️⃣A, 3️⃣NoMatch> - 7️⃣typealias SomeOtherType<4️⃣A> = X<5️⃣A, 6️⃣SomeOtherType> + typealias SomeOtherType<4️⃣A> = X<5️⃣A, 6️⃣SomeOtherType> """, references: [ "2️⃣": [.fromScope(GenericParameterClauseSyntax.self, expectedNames: ["1️⃣"])], "3️⃣": [], "5️⃣": [.fromScope(GenericParameterClauseSyntax.self, expectedNames: ["4️⃣"])], - "6️⃣": [.fromFileScope(expectedNames: ["7️⃣"])], ], expectedResultTypes: .all( - GenericParameterSyntax.self, - except: ["7️⃣": TypeAliasDeclSyntax.self] + GenericParameterSyntax.self ) ) } diff --git a/Tests/SwiftLexicalLookupTest/ResultExpectation.swift b/Tests/SwiftLexicalLookupTest/ResultExpectation.swift index 89f30f1ef3a..68e38e194a2 100644 --- a/Tests/SwiftLexicalLookupTest/ResultExpectation.swift +++ b/Tests/SwiftLexicalLookupTest/ResultExpectation.swift @@ -19,6 +19,8 @@ enum ResultExpectation { case fromScope(ScopeSyntax.Type, expectedNames: [ExpectedName]) case fromFileScope(expectedNames: [ExpectedName]) case lookInMembers(LookInMembersScopeSyntax.Type) + case lookInGenericParametersOfExtendedType + case mightIntroduceDollarIdentifiers var expectedNames: [ExpectedName] { switch self { @@ -26,7 +28,9 @@ enum ResultExpectation { return expectedNames case .fromFileScope(expectedNames: let expectedNames): return expectedNames - case .lookInMembers: + case .lookInMembers, + .lookInGenericParametersOfExtendedType, + .mightIntroduceDollarIdentifiers: return [] } } @@ -39,6 +43,10 @@ enum ResultExpectation { return "fromFileScope" case .lookInMembers: return "lookInMembers" + case .lookInGenericParametersOfExtendedType: + return "lookInGenericParametersOfExtendedType" + case .mightIntroduceDollarIdentifiers: + return "mightIntroduceDollarIdentifiers" } } @@ -67,6 +75,10 @@ enum ResultExpectation { scope.syntaxNodeType == expectedType, "For marker \(marker), scope result type of \(scope.syntaxNodeType) doesn't match expected \(expectedType)" ) + case (.lookInGenericParametersOfExtendedType, .lookInGenericParametersOfExtendedType): + break + case (.mightIntroduceDollarIdentifiers, .mightIntroduceDollarIdentifiers): + break default: XCTFail( "For marker \(marker), actual result kind \(actual.debugDescription) doesn't match expected \(expected.debugDescription)"