Skip to content

[SwiftLexicalLookup][GSoC] Fix Swift 5.8 compatibility and update documentation. #2755

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 4 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@ import SwiftSyntax

/// Specifies how names should be introduced at the file scope.
@_spi(Experimental) public enum FileScopeHandlingConfig {
/// Default behavior. Names introduced sequentially like in member block
/// scope up to the first non-declaration after and including which,
/// the declarations are treated like in code block scope.
/// This is the behavior that is being used
/// for Swift files with top-level code.
case memberBlockUpToLastDecl
/// File scope behaves like member block scope.
/// This is the behavior that is being used
/// for Swift files that don’t allow top-level code.
case memberBlock
/// File scope behaves like code block scope.
case codeBlock
}
13 changes: 9 additions & 4 deletions Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@
import Foundation

@_spi(Experimental) public struct LookupConfig {
/// Specifies behaviour of file scope.
/// `memberBlockUpToLastDecl` by default.
public var fileScopeHandling: FileScopeHandlingConfig = .memberBlockUpToLastDecl
/// Specifies behavior of file scope.
@_spi(Experimental) public var fileScopeHandling: FileScopeHandlingConfig

public init(fileScopeHandling: FileScopeHandlingConfig = .memberBlockUpToLastDecl) {
/// Creates a new lookup configuration.
///
/// - `fileScopeHandling` - specifies behavior of file scope.
/// `memberBlockUpToLastDecl` by default.
@_spi(Experimental) public init(
fileScopeHandling: FileScopeHandlingConfig = .memberBlockUpToLastDecl
) {
self.fileScopeHandling = fileScopeHandling
}
}
10 changes: 5 additions & 5 deletions Sources/SwiftLexicalLookup/IdentifiableSyntax.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,25 @@
import SwiftSyntax

/// Syntax node that can be refered to with an identifier.
public protocol IdentifiableSyntax: SyntaxProtocol {
@_spi(Experimental) public protocol IdentifiableSyntax: SyntaxProtocol {
var identifier: TokenSyntax { get }
}

extension IdentifierPatternSyntax: IdentifiableSyntax {}
@_spi(Experimental) extension IdentifierPatternSyntax: IdentifiableSyntax {}

extension ClosureParameterSyntax: IdentifiableSyntax {
@_spi(Experimental) extension ClosureParameterSyntax: IdentifiableSyntax {
@_spi(Experimental) public var identifier: TokenSyntax {
secondName ?? firstName
}
}

extension ClosureShorthandParameterSyntax: IdentifiableSyntax {
@_spi(Experimental) extension ClosureShorthandParameterSyntax: IdentifiableSyntax {
@_spi(Experimental) public var identifier: TokenSyntax {
name
}
}

extension ClosureCaptureSyntax: IdentifiableSyntax {
@_spi(Experimental) extension ClosureCaptureSyntax: IdentifiableSyntax {
@_spi(Experimental) public var identifier: TokenSyntax {
/* Doesn't work with closures like:
_ = { [y=1+2] in
Expand Down
78 changes: 48 additions & 30 deletions Sources/SwiftLexicalLookup/LookupName.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,40 @@ import SwiftSyntax

@_spi(Experimental) public enum LookupName {
/// Identifier associated with the name.
/// Could be an identifier of a variable, function or closure parameter and more
/// Could be an identifier of a variable, function or closure parameter and more.
case identifier(IdentifiableSyntax, accessibleAfter: AbsolutePosition?)
/// Declaration associated with the name.
/// Could be class, struct, actor, protocol, function and more
case declaration(NamedDeclSyntax, accessibleAfter: AbsolutePosition?)
/// Could be class, struct, actor, protocol, function and more.
case declaration(NamedDeclSyntax)

/// Syntax associated with this name.
@_spi(Experimental) public var syntax: SyntaxProtocol {
switch self {
case .identifier(let syntax, _):
syntax
case .declaration(let syntax, _):
syntax
return syntax
case .declaration(let syntax):
return syntax
}
}

/// Introduced name.
@_spi(Experimental) public var identifier: Identifier? {
switch self {
case .identifier(let syntax, _):
Identifier(syntax.identifier)
case .declaration(let syntax, _):
Identifier(syntax.name)
return Identifier(syntax.identifier)
case .declaration(let syntax):
return Identifier(syntax.name)
}
}

/// 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? {
switch self {
case .identifier(_, let absolutePosition), .declaration(_, let absolutePosition):
absolutePosition
case .identifier(_, let absolutePosition):
return absolutePosition
default:
return nil
}
}

Expand All @@ -61,62 +63,78 @@ import SwiftSyntax
return name == lookedUpName
}

/// Extracts names introduced by the given `from` structure.
static func getNames(from syntax: SyntaxProtocol, accessibleAfter: AbsolutePosition? = nil) -> [LookupName] {
/// Extracts names introduced by the given `syntax` structure.
///
/// When e.g. looking up a variable declaration like `let a = a`,
/// we expect `a` to be visible after the whole declaration.
/// That's why we can't just use `syntax.endPosition` for the `a` identifier pattern,
/// as the name would already be visible at the `a` reference withing the declaration.
/// That’s why code block and file scopes have to set
/// `accessibleAfter` to be the end position of the entire declaration syntax node.
static func getNames(
from syntax: SyntaxProtocol,
accessibleAfter: AbsolutePosition? = nil
) -> [LookupName] {
switch Syntax(syntax).as(SyntaxEnum.self) {
case .variableDecl(let variableDecl):
variableDecl.bindings.flatMap { binding in
getNames(from: binding.pattern, accessibleAfter: accessibleAfter)
return variableDecl.bindings.flatMap { binding in
getNames(
from: binding.pattern,
accessibleAfter: accessibleAfter != nil ? binding.endPositionBeforeTrailingTrivia : nil
)
}
case .tuplePattern(let tuplePattern):
tuplePattern.elements.flatMap { tupleElement in
return tuplePattern.elements.flatMap { tupleElement in
getNames(from: tupleElement.pattern, accessibleAfter: accessibleAfter)
}
case .valueBindingPattern(let valueBindingPattern):
getNames(from: valueBindingPattern.pattern, accessibleAfter: accessibleAfter)
return getNames(from: valueBindingPattern.pattern, accessibleAfter: accessibleAfter)
case .expressionPattern(let expressionPattern):
getNames(from: expressionPattern.expression, accessibleAfter: accessibleAfter)
return getNames(from: expressionPattern.expression, accessibleAfter: accessibleAfter)
case .sequenceExpr(let sequenceExpr):
sequenceExpr.elements.flatMap { expression in
return sequenceExpr.elements.flatMap { expression in
getNames(from: expression, accessibleAfter: accessibleAfter)
}
case .patternExpr(let patternExpr):
getNames(from: patternExpr.pattern, accessibleAfter: accessibleAfter)
return getNames(from: patternExpr.pattern, accessibleAfter: accessibleAfter)
case .optionalBindingCondition(let optionalBinding):
getNames(from: optionalBinding.pattern, accessibleAfter: accessibleAfter)
return getNames(from: optionalBinding.pattern, accessibleAfter: accessibleAfter)
case .matchingPatternCondition(let matchingPatternCondition):
getNames(from: matchingPatternCondition.pattern, accessibleAfter: accessibleAfter)
return getNames(from: matchingPatternCondition.pattern, accessibleAfter: accessibleAfter)
case .functionCallExpr(let functionCallExpr):
functionCallExpr.arguments.flatMap { argument in
return functionCallExpr.arguments.flatMap { argument in
getNames(from: argument.expression, accessibleAfter: accessibleAfter)
}
case .guardStmt(let guardStmt):
guardStmt.conditions.flatMap { cond in
return guardStmt.conditions.flatMap { cond in
getNames(from: cond.condition, accessibleAfter: cond.endPosition)
}
default:
if let namedDecl = Syntax(syntax).asProtocol(SyntaxProtocol.self) as? NamedDeclSyntax {
handle(namedDecl: namedDecl, accessibleAfter: accessibleAfter)
return handle(namedDecl: namedDecl, accessibleAfter: accessibleAfter)
} else if let identifiable = Syntax(syntax).asProtocol(SyntaxProtocol.self) as? IdentifiableSyntax {
handle(identifiable: identifiable, accessibleAfter: accessibleAfter)
return handle(identifiable: identifiable, accessibleAfter: accessibleAfter)
} else {
[]
return []
}
}
}

/// Extracts name introduced by `IdentifiableSyntax` node.
private static func handle(identifiable: IdentifiableSyntax, accessibleAfter: AbsolutePosition? = nil) -> [LookupName]
{
if identifiable.identifier.text != "_" {
if identifiable.identifier.tokenKind != .wildcard {
return [.identifier(identifiable, accessibleAfter: accessibleAfter)]
} else {
return []
}
}

/// Extracts name introduced by `NamedDeclSyntax` node.
private static func handle(namedDecl: NamedDeclSyntax, accessibleAfter: AbsolutePosition? = nil) -> [LookupName] {
[.declaration(namedDecl, accessibleAfter: accessibleAfter)]
private static func handle(
namedDecl: NamedDeclSyntax,
accessibleAfter: AbsolutePosition? = nil
) -> [LookupName] {
[.declaration(namedDecl)]
}
}
8 changes: 4 additions & 4 deletions Sources/SwiftLexicalLookup/LookupResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import SwiftSyntax

/// Represents resul
/// Represents result from a specific scope.
@_spi(Experimental) public enum LookupResult {
/// Scope and the names that matched lookup.
case fromScope(ScopeSyntax, withNames: [LookupName])
Expand All @@ -23,17 +23,17 @@ import SwiftSyntax
@_spi(Experimental) public var scope: ScopeSyntax? {
switch self {
case .fromScope(let scopeSyntax, _):
scopeSyntax
return scopeSyntax
case .fromFileScope(let fileScopeSyntax, _):
fileScopeSyntax
return fileScopeSyntax
}
}

/// Names that matched lookup.
@_spi(Experimental) public var names: [LookupName] {
switch self {
case .fromScope(_, let names), .fromFileScope(_, let names):
names
return names
}
}
}
Loading