-
Notifications
You must be signed in to change notification settings - Fork 440
[SwiftLexicalLookup][GSoC] Add initial name lookup functionality #2719
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
Changes from all commits
b9c4f51
3450029
6c40203
85be4f1
1cb4a9f
7fdb145
5519ccb
5af6d91
a0ff1be
caae677
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// 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 { | ||
/// 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. | ||
case memberBlockUpToLastDecl | ||
/// File scope behaves like member block scope. | ||
case memberBlock | ||
/// File scope behaves like code block scope. | ||
case codeBlock | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// 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 Foundation | ||
|
||
@_spi(Experimental) public struct LookupConfig { | ||
/// Specifies behaviour of file scope. | ||
/// `memberBlockUpToLastDecl` by default. | ||
public var fileScopeHandling: FileScopeHandlingConfig = .memberBlockUpToLastDecl | ||
|
||
public init(fileScopeHandling: FileScopeHandlingConfig = .memberBlockUpToLastDecl) { | ||
self.fileScopeHandling = fileScopeHandling | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// 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 | ||
|
||
/// Syntax node that can be refered to with an identifier. | ||
public protocol IdentifiableSyntax: SyntaxProtocol { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like the kind of thing that we should sink down into SwiftSyntax itself. It can be a refactoring we apply later, when we're more sure about the design. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's iterate with this protocol some more. We might want to sink it down into SwiftSyntax and make the NamedDeclSyntax protocol inherit from it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (this doesn't have to happen now) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll keep adding the protocol to more syntax nodes like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the fix with
MAJKFL marked this conversation as resolved.
Show resolved
Hide resolved
|
||
var identifier: TokenSyntax { get } | ||
} | ||
|
||
extension IdentifierPatternSyntax: IdentifiableSyntax {} | ||
|
||
extension ClosureParameterSyntax: IdentifiableSyntax { | ||
@_spi(Experimental) public var identifier: TokenSyntax { | ||
secondName ?? firstName | ||
} | ||
} | ||
|
||
extension ClosureShorthandParameterSyntax: IdentifiableSyntax { | ||
@_spi(Experimental) public var identifier: TokenSyntax { | ||
name | ||
} | ||
} | ||
|
||
extension ClosureCaptureSyntax: IdentifiableSyntax { | ||
@_spi(Experimental) public var identifier: TokenSyntax { | ||
/* Doesn't work with closures like: | ||
_ = { [y=1+2] in | ||
print(y) | ||
} | ||
*/ | ||
expression.as(DeclReferenceExprSyntax.self)!.baseName | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't going to handle all of the possible kinds of captures. For example, here are some captures that aren't a { [x=y+1, weak self] in print(x) } I think the best result here would involve reworking the syntax tree and parser to provide more structure for the parsed closure captures, so there's a single node describing the captured name that we can make There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This won't work for all closure captures. For example: { [x=y+1, weak self] in ... } I suspect the right answer is to rework the syntax tree so that the syntax structure always provides something that we can make an IdentifiableSyntax without the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (this doesn't have to happen now) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As for now, I left a comment next to the force unwrap so we don't forget about it. Do you have already an idea how could we alter the syntax tree? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think we want different syntactic forms for captures like |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// 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 | ||
|
||
@_spi(Experimental) public enum LookupName { | ||
DougGregor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// Identifier associated with the name. | ||
/// 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?) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I expect that we'll need another case here eventually for "implicit" names, such as the do {
try something()
} catch { // the catch clause introduces the name `error`.
} This can, of course, come later. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the suggestion. I'll introduce implicit names with the next PR. |
||
|
||
/// Syntax associated with this name. | ||
@_spi(Experimental) public var syntax: SyntaxProtocol { | ||
switch self { | ||
case .identifier(let syntax, _): | ||
syntax | ||
case .declaration(let syntax, _): | ||
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) | ||
} | ||
} | ||
|
||
/// 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 | ||
} | ||
} | ||
|
||
/// Checks if this name was introduced before the syntax used for lookup. | ||
func isAccessible(at lookedUpSyntax: SyntaxProtocol) -> Bool { | ||
guard let accessibleAfter else { return true } | ||
return accessibleAfter <= lookedUpSyntax.position | ||
} | ||
|
||
/// Checks if this name refers to the looked up phrase. | ||
func refersTo(_ lookedUpName: String) -> Bool { | ||
guard let name = identifier?.name else { return false } | ||
return name == lookedUpName | ||
} | ||
|
||
/// Extracts names introduced by the given `from` structure. | ||
static func getNames(from syntax: SyntaxProtocol, accessibleAfter: AbsolutePosition? = nil) -> [LookupName] { | ||
MAJKFL marked this conversation as resolved.
Show resolved
Hide resolved
|
||
switch Syntax(syntax).as(SyntaxEnum.self) { | ||
case .variableDecl(let variableDecl): | ||
variableDecl.bindings.flatMap { binding in | ||
getNames(from: binding.pattern, accessibleAfter: accessibleAfter) | ||
} | ||
ahoppen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
case .tuplePattern(let tuplePattern): | ||
tuplePattern.elements.flatMap { tupleElement in | ||
getNames(from: tupleElement.pattern, accessibleAfter: accessibleAfter) | ||
} | ||
case .valueBindingPattern(let valueBindingPattern): | ||
getNames(from: valueBindingPattern.pattern, accessibleAfter: accessibleAfter) | ||
case .expressionPattern(let expressionPattern): | ||
getNames(from: expressionPattern.expression, accessibleAfter: accessibleAfter) | ||
case .sequenceExpr(let sequenceExpr): | ||
sequenceExpr.elements.flatMap { expression in | ||
getNames(from: expression, accessibleAfter: accessibleAfter) | ||
} | ||
case .patternExpr(let patternExpr): | ||
getNames(from: patternExpr.pattern, accessibleAfter: accessibleAfter) | ||
case .optionalBindingCondition(let optionalBinding): | ||
getNames(from: optionalBinding.pattern, accessibleAfter: accessibleAfter) | ||
case .matchingPatternCondition(let matchingPatternCondition): | ||
getNames(from: matchingPatternCondition.pattern, accessibleAfter: accessibleAfter) | ||
case .functionCallExpr(let functionCallExpr): | ||
functionCallExpr.arguments.flatMap { argument in | ||
getNames(from: argument.expression, accessibleAfter: accessibleAfter) | ||
} | ||
case .guardStmt(let guardStmt): | ||
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) | ||
} else if let identifiable = Syntax(syntax).asProtocol(SyntaxProtocol.self) as? IdentifiableSyntax { | ||
handle(identifiable: identifiable, accessibleAfter: accessibleAfter) | ||
} else { | ||
[] | ||
} | ||
} | ||
} | ||
|
||
/// Extracts name introduced by `IdentifiableSyntax` node. | ||
private static func handle(identifiable: IdentifiableSyntax, accessibleAfter: AbsolutePosition? = nil) -> [LookupName] | ||
{ | ||
MAJKFL marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if identifiable.identifier.text != "_" { | ||
MAJKFL marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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)] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// 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 | ||
|
||
/// Represents resul | ||
MAJKFL marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@_spi(Experimental) 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]) | ||
|
||
/// Associated scope. | ||
@_spi(Experimental) public var scope: ScopeSyntax? { | ||
switch self { | ||
case .fromScope(let scopeSyntax, _): | ||
scopeSyntax | ||
case .fromFileScope(let fileScopeSyntax, _): | ||
fileScopeSyntax | ||
} | ||
} | ||
|
||
/// Names that matched lookup. | ||
@_spi(Experimental) public var names: [LookupName] { | ||
switch self { | ||
case .fromScope(_, let names), .fromFileScope(_, let names): | ||
names | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.