Skip to content

[SwiftLexicalLookup][GSoC] Add switch, generic parameter and function scopes. #2782

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
1 change: 1 addition & 0 deletions Sources/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ add_subdirectory(_SwiftSyntaxCShims)
add_subdirectory(SwiftBasicFormat)
add_subdirectory(SwiftSyntax)
add_subdirectory(SwiftDiagnostics)
add_subdirectory(SwiftLexicalLookup)
add_subdirectory(SwiftLibraryPluginProvider)
add_subdirectory(SwiftParser)
add_subdirectory(SwiftParserDiagnostics)
Expand Down
29 changes: 29 additions & 0 deletions Sources/SwiftLexicalLookup/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# 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 http://swift.org/LICENSE.txt for license information
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors

add_swift_syntax_library(SwiftLexicalLookup
IdentifiableSyntax.swift
LookupName.swift
LookupResult.swift
SimpleLookupQueries.swift

Configurations/FileScopeHandlingConfig.swift
Configurations/LookupConfig.swift

Scopes/GenericParameterScopeSyntax.swift
Scopes/IntroducingToSequentialParentScopeSyntax.swift
Scopes/ScopeImplementations.swift
Scopes/ScopeSyntax.swift
Scopes/SequentialScopeSyntax.swift
Scopes/TypeScopeSyntax.swift
Scopes/WithGenericParametersScopeSyntax.swift
)

target_link_swift_syntax_libraries(SwiftLexicalLookup PUBLIC
SwiftSyntax)

18 changes: 18 additions & 0 deletions Sources/SwiftLexicalLookup/IdentifiableSyntax.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ import SwiftSyntax
}
}

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

@_spi(Experimental) extension ClosureShorthandParameterSyntax: IdentifiableSyntax {
@_spi(Experimental) public var identifier: TokenSyntax {
name
Expand All @@ -42,3 +48,15 @@ import SwiftSyntax
name
}
}

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

@_spi(Experimental) extension PrimaryAssociatedTypeSyntax: IdentifiableSyntax {
@_spi(Experimental) public var identifier: TokenSyntax {
name
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//===----------------------------------------------------------------------===//
//
// 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

/// Scope that introduces generic parameter names and directs
/// futher lookup to its `WithGenericParametersScopeSyntax`
/// parent scope's parent scope (i.e. on return, bypasses names
/// introduced by its parent).
@_spi(Experimental) public protocol GenericParameterScopeSyntax: ScopeSyntax {}

@_spi(Experimental) extension GenericParameterScopeSyntax {
/// Returns names matching lookup and bypasses
/// `WithGenericParametersScopeSyntax` parent scope in futher lookup.
///
/// ### Example
/// ```swift
/// let a = 23
/// func foo<A>(a: A) {
/// a // <-- start lookup here
/// }
/// ```
/// When starting lookup at the `a` reference,
/// lookup first visits the code block scope associated
/// with the function's body. Then, it's forwarded to the
/// function declaration scope and then to generic parameter
/// scope (`WithGenericParametersScopeSyntax`).
/// Then, to ensure there is no infinite cycle,
/// this method passes lookup to function scope's parent scope
/// (in this case: file scope).
@_spi(Experimental) public func lookup(
_ identifier: Identifier?,
at lookUpPosition: AbsolutePosition,
with config: LookupConfig
) -> [LookupResult] {
return defaultLookupImplementation(
identifier,
at: lookUpPosition,
with: config,
propagateToParent: false
)
+ lookupBypassingParentResults(
identifier,
at: lookUpPosition,
with: config
)
}

/// Bypasses names introduced by `WithGenericParametersScopeSyntax` parent scope.
///
/// ### Example
/// ```swift
/// let a = 23
/// func foo<A>(a: A) {
/// a // <-- start lookup here
/// }
/// ```
/// When starting lookup at the `a` reference,
/// lookup first visits the code block scope associated
/// with the function's body. Then, it's forwarded to the
/// function declaration scope and then to generic parameter
/// scope (`WithGenericParametersScopeSyntax`).
/// Then, to ensure there is no infinite cycle,
/// we use this method instead of the standard `lookupInParent`
/// to pass lookup to the function scope's parent scope (in this case: file scope)
/// and effectively bypass names already looked up before.
private func lookupBypassingParentResults(
_ identifier: Identifier?,
at lookUpPosition: AbsolutePosition,
with config: LookupConfig
) -> [LookupResult] {
guard let parentScope else { return [] }

if let parentScope = Syntax(parentScope).asProtocol(SyntaxProtocol.self)
as? WithGenericParametersScopeSyntax
{
return parentScope.lookupInParent(identifier, at: lookUpPosition, with: config)
} else {
return lookupInParent(identifier, at: lookUpPosition, with: config)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ import SwiftSyntax
/// All names introduced by the closure signature.
/// Could be closure captures or (shorthand) parameters.
///
/// Example:
/// ### Example
/// ```swift
/// let x = { [weak self, a] b, _ in
/// // <--
Expand Down Expand Up @@ -222,7 +222,7 @@ import SwiftSyntax

/// Finds parent scope, omitting ancestor `if` statements if part of their `else if` clause.
///
/// Example:
/// ### Example
/// ```swift
/// func foo() {
/// if let a = x {
Expand Down Expand Up @@ -262,7 +262,7 @@ import SwiftSyntax
/// Lookup triggered from inside of `else`
/// clause is immediately forwarded to parent scope.
///
/// Example:
/// ### Example
/// ```swift
/// if let a = x {
/// // <-- a is visible here
Expand Down Expand Up @@ -290,6 +290,24 @@ import SwiftSyntax
LookupName.getNames(from: member.decl)
}
}

/// Creates a result from associated type declarations
/// made by it's members.
func lookupAssociatedTypeDeclarations(
_ identifier: Identifier?,
at lookUpPosition: AbsolutePosition,
with config: LookupConfig
) -> [LookupResult] {
let filteredNames = members.flatMap { member in
guard member.decl.kind == .associatedTypeDecl else { return [LookupName]() }

return LookupName.getNames(from: member.decl)
}.filter { name in
checkIdentifier(identifier, refersTo: name, at: lookUpPosition)
}

return filteredNames.isEmpty ? [] : [.fromScope(self, withNames: filteredNames)]
}
}

@_spi(Experimental) extension GuardStmtSyntax: IntroducingToSequentialParentScopeSyntax {
Expand All @@ -308,7 +326,7 @@ import SwiftSyntax
/// Lookup triggered from within of the `else` body
/// returns no names.
///
/// Example:
/// ### Example
/// ```swift
/// guard let a = x else {
/// return // a is not visible here
Expand All @@ -330,10 +348,10 @@ import SwiftSyntax
}
}

@_spi(Experimental) extension ActorDeclSyntax: TypeScopeSyntax {}
@_spi(Experimental) extension ClassDeclSyntax: TypeScopeSyntax {}
@_spi(Experimental) extension StructDeclSyntax: TypeScopeSyntax {}
@_spi(Experimental) extension EnumDeclSyntax: TypeScopeSyntax {}
@_spi(Experimental) extension ActorDeclSyntax: TypeScopeSyntax, WithGenericParametersScopeSyntax {}
@_spi(Experimental) extension ClassDeclSyntax: TypeScopeSyntax, WithGenericParametersScopeSyntax {}
@_spi(Experimental) extension StructDeclSyntax: TypeScopeSyntax, WithGenericParametersScopeSyntax {}
@_spi(Experimental) extension EnumDeclSyntax: TypeScopeSyntax, WithGenericParametersScopeSyntax {}
@_spi(Experimental) extension ExtensionDeclSyntax: TypeScopeSyntax {}

@_spi(Experimental) extension AccessorDeclSyntax: ScopeSyntax {
Expand All @@ -356,7 +374,95 @@ import SwiftSyntax

@_spi(Experimental) extension CatchClauseSyntax: ScopeSyntax {
/// Implicit `error` when there are no catch items.
public var introducedNames: [LookupName] {
@_spi(Experimental) public var introducedNames: [LookupName] {
return catchItems.isEmpty ? [.implicit(.error(self))] : []
}
}

@_spi(Experimental) extension SwitchCaseSyntax: ScopeSyntax {
/// Names introduced within `case` items.
@_spi(Experimental) public var introducedNames: [LookupName] {
label.as(SwitchCaseLabelSyntax.self)?.caseItems.flatMap { child in
LookupName.getNames(from: child.pattern)
} ?? []
}
}

@_spi(Experimental) extension ProtocolDeclSyntax: ScopeSyntax {
/// Protocol declarations don't introduce names by themselves.
@_spi(Experimental) public var introducedNames: [LookupName] {
[]
}

/// For the lookup initiated from inside primary
/// associated type clause, this function also finds
/// all associated type declarations made inside the
/// protocol member block.
///
/// ### Example
/// ```swift
/// class A {}
///
/// protocol Foo<A/*<-- lookup here>*/> {
/// associatedtype A
/// class A {}
/// }
/// ```
/// For the lookup started at the primary associated type `A`,
/// the function returns exactly two results. First associated with the member
/// block that consists of the `associatedtype A` declaration and
/// the latter one from the file scope and `class A` exactly in this order.
public func lookup(
_ identifier: Identifier?,
at lookUpPosition: AbsolutePosition,
with config: LookupConfig
) -> [LookupResult] {
var results: [LookupResult] = []

if let primaryAssociatedTypeClause,
primaryAssociatedTypeClause.range.contains(lookUpPosition)
{
results = memberBlock.lookupAssociatedTypeDeclarations(
identifier,
at: lookUpPosition,
with: config
)
}

return results + defaultLookupImplementation(identifier, at: lookUpPosition, with: config)
}
}

@_spi(Experimental) extension GenericParameterClauseSyntax: GenericParameterScopeSyntax {
/// Generic parameter names introduced by this clause.
@_spi(Experimental) public var introducedNames: [LookupName] {
parameters.children(viewMode: .fixedUp).flatMap { child in
LookupName.getNames(from: child, accessibleAfter: child.endPosition)
}
}
}

@_spi(Experimental) extension FunctionDeclSyntax: WithGenericParametersScopeSyntax {
/// Function parameters introduced by this function's signature.
@_spi(Experimental) public var introducedNames: [LookupName] {
signature.parameterClause.parameters.flatMap { parameter in
LookupName.getNames(from: parameter)
}
}
}

@_spi(Experimental) extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax {
/// Parameters introduced by this subscript.
@_spi(Experimental) public var introducedNames: [LookupName] {
parameterClause.parameters.flatMap { parameter in
LookupName.getNames(from: parameter)
}
}
}

@_spi(Experimental) extension TypeAliasDeclSyntax: WithGenericParametersScopeSyntax {
/// Type alias doesn't introduce any names to it's children.
@_spi(Experimental) public var introducedNames: [LookupName] {
[]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,20 +87,18 @@ extension SyntaxProtocol {
func defaultLookupImplementation(
_ identifier: Identifier?,
at lookUpPosition: AbsolutePosition,
with config: LookupConfig
with config: LookupConfig,
propagateToParent: Bool = true
) -> [LookupResult] {
let filteredNames =
introducedNames
.filter { introducedName in
checkIdentifier(identifier, refersTo: introducedName, at: lookUpPosition)
}

if filteredNames.isEmpty {
return lookupInParent(identifier, at: lookUpPosition, with: config)
} else {
return [.fromScope(self, withNames: filteredNames)]
+ lookupInParent(identifier, at: lookUpPosition, with: config)
}
let fromThisScope = filteredNames.isEmpty ? [] : [LookupResult.fromScope(self, withNames: filteredNames)]

return fromThisScope + (propagateToParent ? lookupInParent(identifier, at: lookUpPosition, with: config) : [])
}

/// Looks up in parent scope.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ extension SequentialScopeSyntax {
/// and included `IntroducingToSequentialParentScopeSyntax` children
/// scopes that match the lookup.
///
/// Example:
/// ### Example
/// ```swift
/// func foo() {
/// let a = 1
Expand Down
Loading