Skip to content

[SwiftLexicalLookup] Add dollar identifier, better extension handling and more ASTScope related fixes #2877

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions Sources/SwiftLexicalLookup/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
}
99 changes: 60 additions & 39 deletions Sources/SwiftLexicalLookup/LookupName.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
}
}
Expand All @@ -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 {
Expand All @@ -120,6 +143,8 @@ import SwiftSyntax
return syntax
case .implicit(let implicitName):
return implicitName.syntax
case .dollarIdentifier(let closureExpr, _):
return closureExpr
}
}

Expand All @@ -132,6 +157,8 @@ import SwiftSyntax
return Identifier(syntax.name)
case .implicit(let kind):
return kind.identifier
case .dollarIdentifier(_, strRepresentation: _):
return nil
}
}

Expand All @@ -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
}
}

Expand All @@ -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`,
Expand All @@ -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):
Expand Down Expand Up @@ -288,6 +307,8 @@ import SwiftSyntax
return "declaration: \(strName)"
case .implicit:
return "implicit: \(strName)"
case .dollarIdentifier(_, strRepresentation: let str):
return "dollarIdentifier: \(str)"
}
}
}
50 changes: 49 additions & 1 deletion Sources/SwiftLexicalLookup/LookupResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
}

Expand All @@ -38,7 +72,9 @@ import SwiftSyntax
switch self {
case .fromScope(_, let names), .fromFileScope(_, let names):
return names
case .lookInMembers(_):
case .lookInMembers(_),
.lookInGenericParametersOfExtendedType(_),
.mightIntroduceDollarIdentifiers(_):
return []
}
}
Expand All @@ -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 =
Expand Down Expand Up @@ -87,6 +131,10 @@ import SwiftSyntax
return "fromFileScope"
case .lookInMembers:
return "lookInMembers"
case .lookInGenericParametersOfExtendedType(_):
return "lookInGenericParametersOfExtendedType"
case .mightIntroduceDollarIdentifiers(_):
return "mightIntroduceDollarIdentifiers"
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions Sources/SwiftLexicalLookup/Scopes/FunctionScopeSyntax.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))] : [])
Expand All @@ -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,
Expand Down
Loading