Skip to content
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

[RFC][SwiftLexicalLookup] Make unqualified name lookup entry-point public #2952

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions Release Notes/602.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## New APIs

- `SwiftLexicalLookup` - A new Swift unqualified lookup library
- Description: The library provides a new Swift unqualified lookup implementation detached from the compiler and accessible to outside clients. The query is stateless and can be directly run on swift-syntax syntax tree, with any syntax node functioning as an entry point. It produces an enum-based data structure as a result that partitions collected names based on the lexical scope of introduction.
- Pull Request: https://github.com/swiftlang/swift-syntax/pull/2952
- Notes: The library follows the behavior of the compiler implementation with some minor differences, such as a different way of handling dollar identifiers `$x` and generic parameters inside extensions. Furthermore, in the future, once the compiler adopts `SwiftLexicalLookup` and it becomes the canonical implementation, results produced by the query will be guaranteed to be correct.

## API Behavior Changes

## Deprecations
Expand Down
32 changes: 16 additions & 16 deletions Sources/SwiftLexicalLookup/IdentifiableSyntax.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,50 +13,50 @@
import SwiftSyntax

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

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

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

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

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

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

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

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

@_spi(Experimental) extension PrimaryAssociatedTypeSyntax: IdentifiableSyntax {
@_spi(Experimental) public var identifier: TokenSyntax {
extension PrimaryAssociatedTypeSyntax: IdentifiableSyntax {
public var identifier: TokenSyntax {
name
}
}
8 changes: 4 additions & 4 deletions Sources/SwiftLexicalLookup/LookupConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import SwiftIfConfig

@_spi(Experimental) public struct LookupConfig {
public struct LookupConfig {
/// Specifies whether lookup should finish in the closest sequential scope.
///
/// ### Example
Expand All @@ -32,14 +32,14 @@ import SwiftIfConfig
/// 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
@_spi(Experimental) public var configuredRegions: ConfiguredRegions?
public var finishInSequentialScope: Bool
public var configuredRegions: ConfiguredRegions?

/// Creates a new lookup configuration.
///
/// - `finishInSequentialScope` - specifies whether lookup should finish
/// in the closest sequential scope. `false` by default.
@_spi(Experimental) public init(
public init(
finishInSequentialScope: Bool = false,
configuredRegions: ConfiguredRegions? = nil
) {
Expand Down
20 changes: 10 additions & 10 deletions Sources/SwiftLexicalLookup/LookupName.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import SwiftSyntax

/// An entity that is implicitly declared based on the syntactic structure of the program.
@_spi(Experimental) public enum ImplicitDecl {
public enum ImplicitDecl {
/// `self` keyword representing object instance.
/// Could be associated with type declaration, extension,
/// or closure captures. Introduced at function edge.
Expand All @@ -30,7 +30,7 @@ import SwiftSyntax
case oldValue(AccessorDeclSyntax)

/// Syntax associated with this name.
@_spi(Experimental) public var syntax: SyntaxProtocol {
public var syntax: SyntaxProtocol {
switch self {
case .self(let syntax):
return syntax
Expand All @@ -46,7 +46,7 @@ import SwiftSyntax
}

/// The name of the implicit declaration.
private var name: StaticString {
public var name: StaticString {
switch self {
case .self:
return "self"
Expand Down Expand Up @@ -85,12 +85,12 @@ import SwiftSyntax
/// ```
/// `self` and `Self` identifers override implicit `self` and `Self` introduced by
/// the `Foo` class declaration.
var identifier: Identifier {
public var identifier: Identifier {
Identifier(canonicalName: name)
}

/// Position of this implicit name.
@_spi(Experimental) public var position: AbsolutePosition {
public var position: AbsolutePosition {
switch self {
case .self(let declSyntax):
switch Syntax(declSyntax).as(SyntaxEnum.self) {
Expand Down Expand Up @@ -128,7 +128,7 @@ import SwiftSyntax
}
}

@_spi(Experimental) public enum LookupName {
public enum LookupName {
/// Identifier associated with the name.
/// Could be an identifier of a variable, function or closure parameter and more.
case identifier(IdentifiableSyntax, accessibleAfter: AbsolutePosition?)
Expand All @@ -154,7 +154,7 @@ import SwiftSyntax
case equivalentNames([LookupName])

/// Syntax associated with this name.
@_spi(Experimental) public var syntax: SyntaxProtocol {
public var syntax: SyntaxProtocol {
switch self {
case .identifier(let syntax, _):
return syntax
Expand All @@ -170,7 +170,7 @@ import SwiftSyntax
}

/// Identifier used for name comparison.
@_spi(Experimental) public var identifier: Identifier? {
public var identifier: Identifier? {
switch self {
case .identifier(let syntax, _):
return Identifier(syntax.identifier)
Expand All @@ -192,7 +192,7 @@ import SwiftSyntax
/// 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 {
public var position: AbsolutePosition {
switch self {
case .identifier(let syntax, _):
return syntax.identifier.positionAfterSkippingLeadingTrivia
Expand Down Expand Up @@ -317,7 +317,7 @@ import SwiftSyntax
}

/// Debug description of this lookup name.
@_spi(Experimental) public var debugDescription: String {
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)"
Expand Down
37 changes: 11 additions & 26 deletions Sources/SwiftLexicalLookup/LookupResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@
import SwiftSyntax

/// Represents result from a specific scope.
@_spi(Experimental) public enum LookupResult {
public enum LookupResult {
/// Scope and the names that matched lookup.
case fromScope(ScopeSyntax, withNames: [LookupName])
/// File scope and names that matched lookup.
case fromFileScope(SourceFileSyntax, withNames: [LookupName])
case fromScope(SyntaxProtocol, withNames: [LookupName])
/// Indicates where to perform member lookup.
case lookInMembers(LookInMembersScopeSyntax)
/// Indicates to lookup generic parameters of extended type.
Expand Down Expand Up @@ -52,12 +50,10 @@ import SwiftSyntax
case mightIntroduceDollarIdentifiers(ClosureExprSyntax)

/// Associated scope.
@_spi(Experimental) public var scope: ScopeSyntax {
public var scope: SyntaxProtocol {
switch self {
case .fromScope(let scopeSyntax, _):
return scopeSyntax
case .fromFileScope(let fileScopeSyntax, _):
return fileScopeSyntax
case .lookInMembers(let lookInMemb):
return lookInMemb
case .lookInGenericParametersOfExtendedType(let extensionDecl):
Expand All @@ -68,9 +64,9 @@ import SwiftSyntax
}

/// Names that matched lookup.
@_spi(Experimental) public var names: [LookupName] {
public var names: [LookupName] {
switch self {
case .fromScope(_, let names), .fromFileScope(_, let names):
case .fromScope(_, let names):
return names
case .lookInMembers(_),
.lookInGenericParametersOfExtendedType(_),
Expand All @@ -79,28 +75,19 @@ import SwiftSyntax
}
}

/// Returns result specific for the particular `scope` kind with provided `names`.
static func getResult(for scope: ScopeSyntax, withNames names: [LookupName]) -> LookupResult {
switch Syntax(scope).as(SyntaxEnum.self) {
case .sourceFile(let sourceFileSyntax):
return .fromFileScope(sourceFileSyntax, withNames: names)
default:
return .fromScope(scope, withNames: names)
}
}

/// 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)]
return [.fromScope(scope, withNames: names)]
}

/// Debug description of this lookup name.
@_spi(Experimental) public var debugDescription: String {
public var debugDescription: String {
var description =
resultKindDebugName + ": " + scope.scopeDebugDescription
resultKindDebugName + ": "
+ ((Syntax(scope).asProtocol(SyntaxProtocol.self) as? ScopeSyntax)?.scopeDebugDescription ?? "NOT-A-SCOPE")

switch self {
case .lookInMembers:
Expand All @@ -127,8 +114,6 @@ import SwiftSyntax
switch self {
case .fromScope:
return "fromScope"
case .fromFileScope:
return "fromFileScope"
case .lookInMembers:
return "lookInMembers"
case .lookInGenericParametersOfExtendedType(_):
Expand All @@ -139,9 +124,9 @@ import SwiftSyntax
}
}

@_spi(Experimental) extension [LookupResult] {
extension [LookupResult] {
/// Debug description this array of lookup results.
@_spi(Experimental) public var debugDescription: String {
public var debugDescription: String {
return self.map(\.debugDescription).joined(separator: "\n")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import SwiftSyntax

@_spi(Experimental) public protocol LookInMembersScopeSyntax: ScopeSyntax {
public protocol LookInMembersScopeSyntax: SyntaxProtocol {
/// Position used for member lookup.
var lookupMembersPosition: AbsolutePosition { get }
}
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ import SwiftSyntax
"EnumDeclScope"
}
}
@_spi(Experimental) extension ExtensionDeclSyntax: LookInMembersScopeSyntax {
@_spi(Experimental) extension ExtensionDeclSyntax: ScopeSyntax, LookInMembersScopeSyntax {
@_spi(Experimental) public var lookupMembersPosition: AbsolutePosition {
if let memberType = extendedType.as(MemberTypeSyntax.self) {
return memberType.name.positionAfterSkippingLeadingTrivia
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftLexicalLookup/Scopes/ScopeSyntax.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ extension SyntaxProtocol {
/// declaration, followed by the first function name, and then the second function name,
/// in this exact order. The constant declaration within the function body is omitted
/// due to the ordering rules that prioritize visibility within the function body.
@_spi(Experimental) public func lookup(
public func lookup(
_ identifier: Identifier?,
with config: LookupConfig = LookupConfig()
) -> [LookupResult] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,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(.fromScope(self, withNames: currentChunk))
currentChunk = []
}

Expand All @@ -107,7 +107,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(.fromScope(self, withNames: currentChunk))
currentChunk = []
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ A library for performing lexical queries on Swift syntax trees.

## Overview

> Important: `SwiftLexicalLookup` is currently in it's experimental phase. Use `@_spi(Experimental) import SwiftLexicalLookup` to work with the library. Clients should not rely on the current implementation.

`SwiftLexicalLookup` provides APIs that traverse and extract information from the Swift syntax tree. Queries on the syntax tree can include responses to questions like: "Where is an error thrown here handled?" or "What is the source and destination of this `fallthrough` keyword?".

```swift
Expand Down
2 changes: 1 addition & 1 deletion Tests/SwiftLexicalLookupTest/NameLookupTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
//===----------------------------------------------------------------------===//

import Foundation
@_spi(Experimental) import SwiftLexicalLookup
import SwiftLexicalLookup
import SwiftSyntax
import XCTest

Expand Down
7 changes: 0 additions & 7 deletions Tests/SwiftLexicalLookupTest/ResultExpectation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import XCTest
/// Used to define lookup result assertion.
enum ResultExpectation {
case fromScope(ScopeSyntax.Type, expectedNames: [ExpectedName])
case fromFileScope(expectedNames: [ExpectedName])
case lookInMembers(LookInMembersScopeSyntax.Type)
case lookInGenericParametersOfExtendedType
case mightIntroduceDollarIdentifiers
Expand All @@ -26,8 +25,6 @@ enum ResultExpectation {
switch self {
case .fromScope(_, let expectedNames):
return expectedNames
case .fromFileScope(expectedNames: let expectedNames):
return expectedNames
case .lookInMembers,
.lookInGenericParametersOfExtendedType,
.mightIntroduceDollarIdentifiers:
Expand All @@ -39,8 +36,6 @@ enum ResultExpectation {
switch self {
case .fromScope:
return "fromScope"
case .fromFileScope:
return "fromFileScope"
case .lookInMembers:
return "lookInMembers"
case .lookInGenericParametersOfExtendedType:
Expand All @@ -67,8 +62,6 @@ enum ResultExpectation {
"For marker \(marker), scope result type of \(scope.syntaxNodeType) doesn't match expected \(expectedType)"
)

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(
Expand Down