Skip to content

Commit 1651c87

Browse files
committed
WIP: Keyword completions based on the syntax tree
rdar://98551200
1 parent 947ad68 commit 1651c87

21 files changed

+2724
-730
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ let package = Package(
202202
),
203203
.testTarget(
204204
name: "SwiftParserDiagnosticsTest",
205-
dependencies: ["SwiftDiagnostics", "SwiftParserDiagnostics"]
205+
dependencies: ["SwiftDiagnostics", "SwiftParserDiagnostics", "_SwiftSyntaxTestSupport"]
206206
),
207207
.testTarget(
208208
name: "SwiftOperatorsTest",
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import SwiftSyntax
2+
3+
extension SyntaxProtocol {
4+
static func completions(at keyPath: AnyKeyPath, visitedNodeKinds: inout [SyntaxProtocol.Type]) -> (completions: Set<TokenKind>, hasRequiredToken: Bool) {
5+
visitedNodeKinds.append(Self.self)
6+
if let keyPath = keyPath as? KeyPath<Self, TokenSyntax> {
7+
return (Set(keyPath.tokenChoices), true)
8+
} else if let keyPath = keyPath as? KeyPath<Self, TokenSyntax?> {
9+
return (Set(keyPath.tokenChoices), false)
10+
} else if let value = type(of: keyPath).valueType as? SyntaxOrOptionalProtocol.Type {
11+
switch value.syntaxOrOptionalProtocolType {
12+
case .optional(let syntaxType):
13+
return (syntaxType.completionsAtStartOfNode(visitedNodeKinds: &visitedNodeKinds).completions, false)
14+
case .nonOptional(let syntaxType):
15+
return (syntaxType.completionsAtStartOfNode(visitedNodeKinds: &visitedNodeKinds).completions, !syntaxType.layout.isCollection)
16+
}
17+
} else {
18+
assertionFailure("Unexpected keypath")
19+
return ([], false)
20+
}
21+
}
22+
23+
24+
static func completionsAtStartOfNode(visitedNodeKinds: inout [SyntaxProtocol.Type]) -> (completions: Set<TokenKind>, hasRequiredToken: Bool) {
25+
if visitedNodeKinds.contains(where: { $0 == Self.self }) {
26+
return ([], true)
27+
}
28+
if self == Syntax.self {
29+
// An arbitrary syntax node is probably a
30+
return ([], true)
31+
}
32+
33+
var hasRequiredToken: Bool
34+
var completions: Set<TokenKind> = []
35+
36+
switch self.layout {
37+
case .layout(let keyPaths):
38+
hasRequiredToken = false // Only relevant if keyPaths is empty and the loop below isn't traversed
39+
for keyPath in keyPaths {
40+
let res = self.completions(at: keyPath, visitedNodeKinds: &visitedNodeKinds)
41+
completions.formUnion(res.completions)
42+
hasRequiredToken = hasRequiredToken || res.hasRequiredToken
43+
if hasRequiredToken {
44+
break
45+
}
46+
}
47+
case .collection(let collectionElementType):
48+
return collectionElementType.completionsAtStartOfNode(visitedNodeKinds: &visitedNodeKinds)
49+
case .choices(let choices):
50+
hasRequiredToken = true
51+
for choice in choices {
52+
switch choice {
53+
case .node(let nodeType):
54+
let res = nodeType.completionsAtStartOfNode(visitedNodeKinds: &visitedNodeKinds)
55+
completions.formUnion(res.completions)
56+
hasRequiredToken = hasRequiredToken && res.hasRequiredToken
57+
case .token(let tokenKind):
58+
completions.insert(tokenKind)
59+
}
60+
}
61+
}
62+
63+
return (completions, hasRequiredToken)
64+
}
65+
}
66+
67+
extension SyntaxProtocol {
68+
func completions(afterIndex index: Int) -> Set<TokenKind> {
69+
var hasRequiredToken: Bool = false
70+
71+
var completions: Set<TokenKind> = []
72+
var visitedNodeKinds: [SyntaxProtocol.Type] = []
73+
if index < childrenKeyPaths.count {
74+
for keyPath in childrenKeyPaths[(index + 1)...] {
75+
let res = Self.completions(at: keyPath, visitedNodeKinds: &visitedNodeKinds)
76+
completions.formUnion(res.completions)
77+
hasRequiredToken = res.hasRequiredToken
78+
if hasRequiredToken {
79+
break
80+
}
81+
}
82+
}
83+
if !hasRequiredToken, let parent = parent {
84+
completions.formUnion(parent.asProtocol(SyntaxProtocol.self).completions(afterIndex: self.indexInParent))
85+
}
86+
return completions
87+
}
88+
89+
public func completions(at position: AbsolutePosition) -> Set<TokenKind> {
90+
if position <= self.positionAfterSkippingLeadingTrivia {
91+
var visitedNodeKinds: [SyntaxProtocol.Type] = []
92+
return Self.completionsAtStartOfNode(visitedNodeKinds: &visitedNodeKinds).completions
93+
}
94+
let finder = TokenFinder(targetPosition: position)
95+
finder.walk(self)
96+
guard let found = finder.found?.previousToken(viewMode: .sourceAccurate), let parent = found.parent else {
97+
return []
98+
}
99+
return parent.asProtocol(SyntaxProtocol.self).completions(afterIndex: found.indexInParent)
100+
}
101+
}
102+
103+
/// Finds the first token whose text (ignoring trivia) starts after targetPosition.
104+
class TokenFinder: SyntaxAnyVisitor {
105+
var targetPosition: AbsolutePosition
106+
var found: TokenSyntax? = nil
107+
108+
init(targetPosition: AbsolutePosition) {
109+
self.targetPosition = targetPosition
110+
super.init(viewMode: .sourceAccurate)
111+
}
112+
113+
override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
114+
if found != nil || node.endPosition < targetPosition {
115+
return .skipChildren
116+
} else {
117+
return .visitChildren
118+
}
119+
}
120+
121+
override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind {
122+
if targetPosition <= node.positionAfterSkippingLeadingTrivia, found == nil {
123+
found = node
124+
}
125+
return .skipChildren
126+
}
127+
}

Sources/SwiftSyntax/Misc.swift.gyb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,20 @@ extension Syntax {
5656
}
5757
}
5858

59+
public static var layout: SyntaxNodeLayoutDescription {
60+
return .choices([
61+
.node(UnknownSyntax.self),
62+
.node(TokenSyntax.self),
63+
% for node in NON_BASE_SYNTAX_NODES:
64+
% if node.is_base():
65+
.node(Unknown${node.name}.self),
66+
% else:
67+
.node(${node.name}.self),
68+
% end
69+
% end
70+
])
71+
}
72+
5973
public func childNameForDiagnostics(_ index: SyntaxChildrenIndex) -> String? {
6074
switch self.as(SyntaxEnum.self) {
6175
case .token(let node):

Sources/SwiftSyntax/Syntax.swift

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,38 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
public enum SyntaxChoice {
14+
case node(SyntaxProtocol.Type)
15+
case token(TokenKind)
16+
}
17+
18+
public enum SyntaxNodeLayoutDescription {
19+
case layout([AnyKeyPath])
20+
case collection(SyntaxProtocol.Type)
21+
case choices([SyntaxChoice])
22+
23+
public var isLayout: Bool {
24+
switch self {
25+
case .layout: return true
26+
default: return false
27+
}
28+
}
29+
30+
public var isCollection: Bool {
31+
switch self {
32+
case .collection: return true
33+
default: return false
34+
}
35+
}
36+
37+
public var isChoices: Bool {
38+
switch self {
39+
case .choices: return true
40+
default: return false
41+
}
42+
}
43+
}
44+
1345
/// A Syntax node represents a tree of nodes with tokens at the leaves.
1446
/// Each node has accessors for its known children, and allows efficient
1547
/// iteration over the children through its `children` property.
@@ -49,10 +81,6 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
4981
self = syntax._syntaxNode
5082
}
5183

52-
public static var childrenKeyPaths: [AnyKeyPath] {
53-
fatalError("Syntax is a base type and thus doesn't have statically known children")
54-
}
55-
5684
public func hash(into hasher: inout Hasher) {
5785
return data.nodeId.hash(into: &hasher)
5886
}
@@ -126,10 +154,25 @@ public extension SyntaxHashable {
126154
}
127155
}
128156

157+
public enum SyntaxOrOptionalProtocolType {
158+
case optional(SyntaxProtocol.Type)
159+
case nonOptional(SyntaxProtocol.Type)
160+
}
161+
162+
public protocol SyntaxOrOptionalProtocol {
163+
static var syntaxOrOptionalProtocolType: SyntaxOrOptionalProtocolType { get }
164+
}
165+
166+
extension Optional: SyntaxOrOptionalProtocol where Wrapped: SyntaxProtocol {
167+
public static var syntaxOrOptionalProtocolType: SyntaxOrOptionalProtocolType {
168+
return .optional(Wrapped.self)
169+
}
170+
}
171+
129172
/// Provide common functionality for specialized syntax nodes. Extend this
130173
/// protocol to provide common functionality for all syntax nodes.
131174
/// DO NOT CONFORM TO THIS PROTOCOL YOURSELF!
132-
public protocol SyntaxProtocol: CustomStringConvertible,
175+
public protocol SyntaxProtocol: SyntaxOrOptionalProtocol, CustomStringConvertible,
133176
CustomDebugStringConvertible, TextOutputStreamable {
134177

135178
/// Retrieve the generic syntax node that is represented by this node.
@@ -148,7 +191,7 @@ public protocol SyntaxProtocol: CustomStringConvertible,
148191
///
149192
/// Technically, this should have type `PartialKeyPath<Self>` but we cannot do
150193
/// that because then `SyntaxProtocol` would have a Self type requirement.
151-
static var childrenKeyPaths: [AnyKeyPath] { get }
194+
static var layout: SyntaxNodeLayoutDescription { get }
152195

153196
/// Return a name with which the child at the given `index` can be referred to
154197
/// in diagnostics.
@@ -157,9 +200,20 @@ public protocol SyntaxProtocol: CustomStringConvertible,
157200
func childNameForDiagnostics(_ index: SyntaxChildrenIndex) -> String?
158201
}
159202

203+
public extension SyntaxProtocol {
204+
static var syntaxOrOptionalProtocolType: SyntaxOrOptionalProtocolType {
205+
return .nonOptional(Self.self)
206+
}
207+
}
208+
160209
public extension SyntaxProtocol {
161210
/// Returns the value of the static `childrenKeyPaths` for this node's type.
162-
var childrenKeyPaths: [AnyKeyPath] { return Self.childrenKeyPaths }
211+
var childrenKeyPaths: [AnyKeyPath] {
212+
switch Self.layout {
213+
case .layout(let keyPaths): return keyPaths
214+
default: return []
215+
}
216+
}
163217
}
164218

165219
public extension SyntaxProtocol {

Sources/SwiftSyntax/SyntaxBaseNodes.swift.gyb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,14 @@ public struct ${node.name}: ${node.name}Protocol, SyntaxHashable {
142142
fatalError("${node.name} is a base type and thus doesn't have statically known children")
143143
}
144144

145-
public var childrenKeyPaths: [AnyKeyPath] {
146-
return Syntax(self).childrenKeyPaths
145+
public static var layout: SyntaxNodeLayoutDescription {
146+
return .choices([
147+
% for child_node in [child_node for child_node in SYNTAX_NODES if child_node.base_kind == node.syntax_kind]:
148+
.node(${child_node.name}.self),
149+
% end
150+
])
147151
}
148152

149-
150153
public func childNameForDiagnostics(_ index: SyntaxChildrenIndex) -> String? {
151154
return Syntax(self).childNameForDiagnostics(index)
152155
}

Sources/SwiftSyntax/SyntaxCollections.swift.gyb

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@
1818
//
1919
//===----------------------------------------------------------------------===//
2020

21-
public protocol SyntaxCollection: SyntaxProtocol, Sequence {
21+
public protocol SyntaxCollection: SyntaxProtocol, Sequence where Element: SyntaxProtocol {
2222
/// The number of elements, `present` or `missing`, in this collection.
2323
var count: Int { get }
2424
}
2525

2626
public extension SyntaxCollection {
2727
static var childrenKeyPaths: [AnyKeyPath] { [] }
28+
29+
static var layout: SyntaxNodeLayoutDescription {
30+
return .collection(Element.self)
31+
}
2832
}
2933

3034
% for node in SYNTAX_NODES:
@@ -79,6 +83,16 @@ public struct ${node.name}: SyntaxCollection, SyntaxHashable {
7983
% end
8084
return nil
8185
}
86+
87+
88+
public static var layout: SyntaxNodeLayoutDescription {
89+
return .choices([
90+
% for choice_name in node.collection_element_choices:
91+
% choice = NODE_MAP[choice_name]
92+
.node(${choice.name}.self),
93+
% end
94+
])
95+
}
8296
}
8397
% else:
8498
public typealias Element = ${node.collection_element_type}

Sources/SwiftSyntax/SyntaxNodes.swift.gyb.template

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,20 @@ public struct ${node.name}: ${base_type}Protocol, SyntaxHashable {
107107
% end
108108
return nil
109109
}
110+
111+
public static var layout: SyntaxNodeLayoutDescription {
112+
return .choices([
113+
% for choice in child.node_choices:
114+
% if choice.is_token() and choice.token.text:
115+
.token(.${choice.token.swift_kind()}),
116+
% elif choice.is_token() and not choice.token.text:
117+
.token(.${choice.token.swift_kind()}("")),
118+
% else:
119+
.node(${choice.type_name}.self),
120+
% end
121+
% end
122+
])
123+
}
110124
}
111125

112126
% end
@@ -236,12 +250,12 @@ public struct ${node.name}: ${base_type}Protocol, SyntaxHashable {
236250
}
237251
% end
238252
239-
public static var childrenKeyPaths: [AnyKeyPath] {
240-
[
253+
public static var layout: SyntaxNodeLayoutDescription {
254+
return .layout([
241255
% for (index, child) in enumerate(node.children):
242256
\Self.${child.swift_name},
243257
% end
244-
]
258+
])
245259
}
246260

247261
public func childNameForDiagnostics(_ index: SyntaxChildrenIndex) -> String? {

Sources/SwiftSyntax/SyntaxOtherNodes.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ public struct UnknownSyntax: SyntaxProtocol, SyntaxHashable {
3838
self.init(data)
3939
}
4040

41-
public static var childrenKeyPaths: [AnyKeyPath] { [] }
41+
public static var layout: SyntaxNodeLayoutDescription {
42+
return .layout([])
43+
}
4244

4345
public func childNameForDiagnostics(_ index: SyntaxChildrenIndex) -> String? {
4446
return nil
@@ -202,7 +204,9 @@ public struct TokenSyntax: SyntaxProtocol, SyntaxHashable {
202204
return raw.totalLength
203205
}
204206

205-
public static var childrenKeyPaths: [AnyKeyPath] { [] }
207+
public static var layout: SyntaxNodeLayoutDescription {
208+
return .layout([])
209+
}
206210

207211
public func childNameForDiagnostics(_ index: SyntaxChildrenIndex) -> String? {
208212
return nil

0 commit comments

Comments
 (0)