Skip to content

Commit ae4fe2b

Browse files
authored
Merge pull request #1496 from ahoppen/ahoppen/5.9/fixit
[5.9] Slightly clean up `FixIt.Change` and `FixIt.Changes`
2 parents c9e33bd + acb3dba commit ae4fe2b

14 files changed

+105
-66
lines changed

Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ extension PluginMessage.Diagnostic {
8484
self.fixIts = syntaxDiag.fixIts.compactMap {
8585
PluginMessage.Diagnostic.FixIt(
8686
message: $0.message.message,
87-
changes: $0.changes.changes.compactMap {
87+
changes: $0.changes.compactMap {
8888
let range: SourceManager.SourceRange?
8989
let text: String
9090
switch $0 {

Sources/SwiftDiagnostics/FixIt.swift

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,6 @@ public protocol FixItMessage {
2525

2626
/// A Fix-It that can be applied to resolve a diagnostic.
2727
public struct FixIt {
28-
public struct Changes: ExpressibleByArrayLiteral {
29-
public var changes: [Change]
30-
31-
public init(changes: [Change]) {
32-
self.changes = changes
33-
}
34-
35-
public init(arrayLiteral elements: FixIt.Change...) {
36-
self.init(changes: elements)
37-
}
38-
39-
public init(combining: [Changes]) {
40-
self.init(changes: combining.flatMap(\.changes))
41-
}
42-
}
43-
4428
public enum Change {
4529
/// Replace `oldNode` by `newNode`.
4630
case replace(oldNode: Syntax, newNode: Syntax)
@@ -54,10 +38,10 @@ public struct FixIt {
5438
public let message: FixItMessage
5539

5640
/// The changes that need to be performed when the Fix-It is applied.
57-
public let changes: Changes
41+
public let changes: [Change]
5842

59-
public init(message: FixItMessage, changes: Changes) {
60-
precondition(!changes.changes.isEmpty, "A Fix-It must have at least one change associated with it")
43+
public init(message: FixItMessage, changes: [Change]) {
44+
precondition(!changes.isEmpty, "A Fix-It must have at least one change associated with it")
6145
self.message = message
6246
self.changes = changes
6347
}

Sources/SwiftParserDiagnostics/DiagnosticExtensions.swift

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,51 @@ import SwiftBasicFormat
1515
import SwiftSyntax
1616

1717
extension FixIt {
18-
public init(message: FixItMessage, changes: [Changes]) {
19-
self.init(message: message, changes: FixIt.Changes(combining: changes))
18+
/// A more complex set of changes that affects multiple syntax nodes and thus
19+
/// produces multiple `FixIt.Change`s. This allows us to e.g. mark a node as
20+
/// missing but keep the trivia by transferring it to the previous or next
21+
/// token.
22+
struct MultiNodeChange {
23+
var primitiveChanges: [Change]
24+
25+
init(primitiveChanges: [Change]) {
26+
self.primitiveChanges = primitiveChanges
27+
}
28+
29+
init(_ primitiveChanges: Change...) {
30+
self.init(primitiveChanges: primitiveChanges)
31+
}
32+
33+
init(combining: [MultiNodeChange]) {
34+
self.init(primitiveChanges: combining.flatMap(\.primitiveChanges))
35+
}
36+
}
37+
38+
init(message: FixItMessage, changes: [MultiNodeChange]) {
39+
self.init(message: message, changes: MultiNodeChange(combining: changes))
40+
}
41+
42+
init(message: FixItMessage, changes: MultiNodeChange) {
43+
self.init(message: message, changes: changes.primitiveChanges)
2044
}
2145

2246
// These overloads shouldn't be needed, but are currently required for the
2347
// Swift 5.5 compiler to handle non-trivial FixIt initializations using
2448
// leading-dot syntax.
2549
// TODO: These can be dropped once we require a minimum of Swift 5.6 to
2650
// compile the library.
27-
init(message: StaticParserFixIt, changes: Changes) {
28-
self.init(message: message as FixItMessage, changes: changes)
51+
init(message: StaticParserFixIt, changes: MultiNodeChange) {
52+
self.init(message: message as FixItMessage, changes: changes.primitiveChanges)
2953
}
30-
init(message: StaticParserFixIt, changes: [Changes]) {
31-
self.init(message: message as FixItMessage, changes: FixIt.Changes(combining: changes))
54+
init(message: StaticParserFixIt, changes: [MultiNodeChange]) {
55+
self.init(message: message as FixItMessage, changes: MultiNodeChange(combining: changes).primitiveChanges)
56+
}
57+
public init(message: StaticParserFixIt, changes: [Change]) {
58+
self.init(message: message as FixItMessage, changes: changes)
3259
}
3360
}
3461

35-
extension FixIt.Changes {
62+
extension FixIt.MultiNodeChange {
3663
/// Replaced a present token with a missing node.
3764
/// If `transferTrivia` is `true`, the leading and trailing trivia of the
3865
/// removed node will be transferred to the trailing trivia of the previous token.
@@ -49,26 +76,26 @@ extension FixIt.Changes {
4976
var changes = tokens.map {
5077
FixIt.Change.replace(
5178
oldNode: Syntax($0),
52-
newNode: Syntax(TokenSyntax($0.tokenKind, leadingTrivia: [], trailingTrivia: [], presence: .missing))
79+
newNode: Syntax($0.with(\.presence, .missing))
5380
)
5481
}
5582
if transferTrivia {
56-
changes += FixIt.Changes.transferTriviaAtSides(from: tokens).changes
83+
changes += FixIt.MultiNodeChange.transferTriviaAtSides(from: tokens).primitiveChanges
5784
}
58-
return FixIt.Changes(changes: changes)
85+
return FixIt.MultiNodeChange(primitiveChanges: changes)
5986
}
6087

6188
/// If `transferTrivia` is `true`, the leading and trailing trivia of the
6289
/// removed node will be transferred to the trailing trivia of the previous token.
6390
static func makeMissing<SyntaxType: SyntaxProtocol>(_ node: SyntaxType?, transferTrivia: Bool = true) -> Self {
6491
guard let node = node else {
65-
return FixIt.Changes(changes: [])
92+
return FixIt.MultiNodeChange(primitiveChanges: [])
6693
}
6794
var changes = [FixIt.Change.replace(oldNode: Syntax(node), newNode: MissingMaker().visit(Syntax(node)))]
6895
if transferTrivia {
69-
changes += FixIt.Changes.transferTriviaAtSides(from: [node]).changes
96+
changes += FixIt.MultiNodeChange.transferTriviaAtSides(from: [node]).primitiveChanges
7097
}
71-
return FixIt.Changes(changes: changes)
98+
return FixIt.MultiNodeChange(primitiveChanges: changes)
7299
}
73100

74101
/// Make a node present. If `leadingTrivia` or `trailingTrivia` is specified,
@@ -89,13 +116,13 @@ extension FixIt.Changes {
89116
let nextToken = node.nextToken(viewMode: .sourceAccurate),
90117
leadingTrivia == nil
91118
{
92-
return [
119+
return FixIt.MultiNodeChange(
93120
.replace(
94121
oldNode: Syntax(node),
95122
newNode: Syntax(presentNode).with(\.leadingTrivia, nextToken.leadingTrivia)
96123
),
97-
.replaceLeadingTrivia(token: nextToken, newTrivia: []),
98-
]
124+
.replaceLeadingTrivia(token: nextToken, newTrivia: [])
125+
)
99126
} else if node.leadingTrivia.isEmpty,
100127
let previousToken = node.previousToken(viewMode: .fixedUp),
101128
previousToken.presence == .present,
@@ -105,19 +132,19 @@ extension FixIt.Changes {
105132
{
106133
/// If neither this nor the previous token are punctionation make sure they
107134
/// are separated by a space.
108-
return [
135+
return FixIt.MultiNodeChange(
109136
.replace(
110137
oldNode: Syntax(node),
111138
newNode: Syntax(presentNode).with(\.leadingTrivia, .space)
112139
)
113-
]
140+
)
114141
} else {
115-
return [
142+
return FixIt.MultiNodeChange(
116143
.replace(
117144
oldNode: Syntax(node),
118145
newNode: Syntax(presentNode)
119146
)
120-
]
147+
)
121148
}
122149
}
123150

@@ -128,10 +155,10 @@ extension FixIt.Changes {
128155
if !previousToken.trailingTrivia.isEmpty {
129156
presentToken = presentToken.with(\.trailingTrivia, previousToken.trailingTrivia)
130157
}
131-
return [
158+
return FixIt.MultiNodeChange(
132159
.replaceTrailingTrivia(token: previousToken, newTrivia: []),
133-
.replace(oldNode: Syntax(token), newNode: Syntax(presentToken)),
134-
]
160+
.replace(oldNode: Syntax(token), newNode: Syntax(presentToken))
161+
)
135162
} else {
136163
return .makePresent(token)
137164
}
@@ -149,11 +176,11 @@ extension FixIt.Changes {
149176
// Punctuation is generally not followed by spaces in Swift.
150177
// If this action would only add spaces to the punctuation, drop it.
151178
// This generally yields better results.
152-
return []
179+
return FixIt.MultiNodeChange()
153180
}
154-
return [.replaceTrailingTrivia(token: previousToken, newTrivia: mergedTrivia)]
181+
return FixIt.MultiNodeChange(.replaceTrailingTrivia(token: previousToken, newTrivia: mergedTrivia))
155182
} else {
156-
return []
183+
return FixIt.MultiNodeChange()
157184
}
158185
}
159186
}

Sources/SwiftParserDiagnostics/LexerDiagnosticMessages.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ public extension SwiftSyntax.TokenDiagnostic {
192192
.with(\.leadingTrivia, Trivia(pieces: token.leadingTrivia.map(replaceNonBreakingSpace)))
193193
.with(\.trailingTrivia, Trivia(pieces: token.trailingTrivia.map(replaceNonBreakingSpace)))
194194
return [
195-
FixIt(message: .replaceNonBreakingSpaceBySpace, changes: [[.replace(oldNode: Syntax(token), newNode: Syntax(fixedToken))]])
195+
FixIt(message: .replaceNonBreakingSpaceBySpace, changes: [.replace(oldNode: Syntax(token), newNode: Syntax(fixedToken))])
196196
]
197197
case .unicodeCurlyQuote:
198198
let (rawKind, text) = token.tokenKind.decomposeToRaw()
@@ -206,7 +206,7 @@ public extension SwiftSyntax.TokenDiagnostic {
206206

207207
let fixedToken = token.withKind(TokenKind.fromRaw(kind: rawKind, text: replacedText))
208208
return [
209-
FixIt(message: .replaceCurlyQuoteByNormalQuote, changes: [[.replace(oldNode: Syntax(token), newNode: Syntax(fixedToken))]])
209+
FixIt(message: .replaceCurlyQuoteByNormalQuote, changes: [.replace(oldNode: Syntax(token), newNode: Syntax(fixedToken))])
210210
]
211211
case .equalMustHaveConsistentWhitespaceOnBothSides:
212212
let hasLeadingSpace = token.previousToken(viewMode: .all)?.trailingTrivia.contains(where: { $0.isSpaceOrTab }) ?? false
@@ -226,7 +226,7 @@ public extension SwiftSyntax.TokenDiagnostic {
226226
}
227227

228228
return [
229-
FixIt(message: .insertWhitespace, changes: FixIt.Changes(changes: changes))
229+
FixIt(message: .insertWhitespace, changes: changes)
230230
]
231231
default:
232232
return []

Sources/SwiftParserDiagnostics/MissingNodesError.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ extension ParseDiagnosticsGenerator {
316316
func handleMissingSyntax<T: SyntaxProtocol>(
317317
_ node: T,
318318
overridePosition: AbsolutePosition? = nil,
319-
additionalChanges: [FixIt.Changes] = [],
319+
additionalChanges: [FixIt.MultiNodeChange] = [],
320320
additionalHandledNodes: [SyntaxIdentifier] = []
321321
) -> SyntaxVisitorContinueKind {
322322
if shouldSkip(node) {
@@ -356,7 +356,7 @@ extension ParseDiagnosticsGenerator {
356356
}
357357
}
358358

359-
let changes = missingNodes.enumerated().map { (index, missingNode) -> FixIt.Changes in
359+
let changes = missingNodes.enumerated().map { (index, missingNode) -> FixIt.MultiNodeChange in
360360
if index == 0,
361361
let token = missingNode.as(TokenSyntax.self),
362362
let previousTokenKind = missingNode.previousToken(viewMode: .sourceAccurate)?.tokenKind

Sources/SwiftParserDiagnostics/MissingTokenError.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ extension ParseDiagnosticsGenerator {
9797

9898
private func handleInvalidPeriod(invalidToken: TokenSyntax, missingToken: TokenSyntax, invalidTokenContainer: UnexpectedNodesSyntax) -> Bool {
9999
// Trailing trivia is the cause of this diagnostic, don't transfer it.
100-
let changes: [FixIt.Changes] = [
100+
let changes: [FixIt.MultiNodeChange] = [
101101
.makeMissing(invalidToken, transferTrivia: false),
102102
.makePresent(missingToken),
103103
]

Sources/SwiftParserDiagnostics/MultiLineStringLiteralDiagnosticsGenerator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ final class MultiLineStringLiteralIndentatinDiagnosticsGenerator: SyntaxVisitor
115115
message: InvalidIndentationInMultiLineStringLiteralError(kind: currentDiagnostic.kind, lines: currentDiagnostic.lines),
116116
highlights: [],
117117
notes: [Note(node: Syntax(closeQuote), message: .shouldMatchIndentationOfClosingQuote)],
118-
fixIts: [FixIt(message: .changeIndentationToMatchClosingDelimiter, changes: FixIt.Changes(changes: currentDiagnostic.changes))]
118+
fixIts: [FixIt(message: .changeIndentationToMatchClosingDelimiter, changes: currentDiagnostic.changes)]
119119
)
120120

121121
finishedDiagnostics.append((diagnostic, currentDiagnostic.handledNodes))

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,23 +143,23 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
143143

144144
// Ignore `correctTokens` that are already present.
145145
let correctAndMissingTokens = correctTokens.filter({ $0.presence == .missing })
146-
var changes: [FixIt.Changes] = []
146+
var changes: [FixIt.MultiNodeChange] = []
147147
if let misplacedToken = misplacedTokens.only, let correctToken = correctTokens.only,
148148
misplacedToken.nextToken(viewMode: .all) == correctToken || misplacedToken.previousToken(viewMode: .all) == correctToken,
149149
correctToken.presence == .missing
150150
{
151151
// We are exchanging two adjacent tokens, transfer the trivia from the incorrect token to the corrected token.
152-
changes += misplacedTokens.map { FixIt.Changes.makeMissing($0, transferTrivia: false) }
152+
changes += misplacedTokens.map { FixIt.MultiNodeChange.makeMissing($0, transferTrivia: false) }
153153
changes.append(
154-
FixIt.Changes.makePresent(
154+
FixIt.MultiNodeChange.makePresent(
155155
correctToken,
156156
leadingTrivia: misplacedToken.leadingTrivia,
157157
trailingTrivia: misplacedToken.trailingTrivia
158158
)
159159
)
160160
} else {
161-
changes += misplacedTokens.map { FixIt.Changes.makeMissing($0) }
162-
changes += correctAndMissingTokens.map { FixIt.Changes.makePresent($0) }
161+
changes += misplacedTokens.map { FixIt.MultiNodeChange.makeMissing($0) }
162+
changes += correctAndMissingTokens.map { FixIt.MultiNodeChange.makePresent($0) }
163163
}
164164
var fixIts: [FixIt] = []
165165
if changes.count > 1 {
@@ -309,7 +309,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
309309
node,
310310
.unexpectedSemicolon,
311311
fixIts: [
312-
FixIt(message: RemoveNodesFixIt(semicolons), changes: semicolons.map { FixIt.Changes.makeMissing($0) })
312+
FixIt(message: RemoveNodesFixIt(semicolons), changes: semicolons.map { FixIt.MultiNodeChange.makeMissing($0) })
313313
]
314314
)
315315
} else if node.first?.as(TokenSyntax.self)?.tokenKind.isIdentifier == true,
@@ -327,7 +327,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
327327
FixIt(
328328
message: .joinIdentifiers,
329329
changes: [
330-
[.replace(oldNode: Syntax(previousToken), newNode: Syntax(TokenSyntax(.identifier(joined), presence: .present)))],
330+
FixIt.MultiNodeChange(.replace(oldNode: Syntax(previousToken), newNode: Syntax(TokenSyntax(.identifier(joined), presence: .present)))),
331331
.makeMissing(tokens),
332332
]
333333
)
@@ -338,7 +338,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
338338
FixIt(
339339
message: .joinIdentifiersWithCamelCase,
340340
changes: [
341-
[.replace(oldNode: Syntax(previousToken), newNode: Syntax(TokenSyntax(.identifier(joinedUsingCamelCase), presence: .present)))],
341+
FixIt.MultiNodeChange(.replace(oldNode: Syntax(previousToken), newNode: Syntax(TokenSyntax(.identifier(joinedUsingCamelCase), presence: .present)))),
342342
.makeMissing(tokens),
343343
]
344344
)
@@ -821,7 +821,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
821821
message: RemoveNodesFixIt(unexpectedName),
822822
changes: [
823823
.makeMissing(unexpectedName),
824-
FixIt.Changes(changes: [.replaceTrailingTrivia(token: previous, newTrivia: [])]),
824+
FixIt.MultiNodeChange(.replaceTrailingTrivia(token: previous, newTrivia: [])),
825825
]
826826
)
827827
],

Sources/SwiftSyntax/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ add_swift_host_library(SwiftSyntax
1919
SyntaxArena.swift
2020
SyntaxChildren.swift
2121
SyntaxData.swift
22-
SyntaxOtherNodes.swift
2322
SyntaxText.swift
2423
SyntaxTreeViewMode.swift
2524
TokenDiagnostic.swift
25+
TokenSyntax.swift
2626
Utils.swift
2727

2828
Raw/RawSyntax.swift

Sources/SwiftSyntax/Raw/RawSyntaxTokenView.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,21 @@ public struct RawSyntaxTokenView {
197197
}
198198
}
199199

200+
/// Returns a `RawSyntax` node with the presence changed to `newValue`.
201+
@_spi(RawSyntax)
202+
public func withPresence(_ newValue: SourcePresence, arena: SyntaxArena) -> RawSyntax {
203+
switch raw.rawData.payload {
204+
case .parsedToken(var payload):
205+
payload.presence = newValue
206+
return RawSyntax(arena: arena, payload: .parsedToken(payload))
207+
case .materializedToken(var payload):
208+
payload.presence = newValue
209+
return RawSyntax(arena: arena, payload: .materializedToken(payload))
210+
default:
211+
preconditionFailure("'withKind()' is called on non-token raw syntax")
212+
}
213+
}
214+
200215
/// The length of the token without leading or trailing trivia, assuming this
201216
/// is a token node.
202217
@_spi(RawSyntax)

Sources/SwiftSyntax/SyntaxData.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,14 @@ struct SyntaxData {
336336
return self
337337
}
338338
}
339+
340+
func withPresence(_ presence: SourcePresence, arena: SyntaxArena) -> SyntaxData {
341+
if let raw = raw.tokenView?.withPresence(presence, arena: arena) {
342+
return replacingSelf(raw, arena: arena)
343+
} else {
344+
return self
345+
}
346+
}
339347
}
340348

341349
#if DEBUG

Sources/SwiftSyntax/SyntaxOtherNodes.swift renamed to Sources/SwiftSyntax/TokenSyntax.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,12 @@ public struct TokenSyntax: SyntaxProtocol, SyntaxHashable {
5555
}
5656

5757
public var presence: SourcePresence {
58-
return tokenView.presence
58+
get {
59+
return tokenView.presence
60+
}
61+
set {
62+
self = TokenSyntax(data.withPresence(newValue, arena: SyntaxArena()))
63+
}
5964
}
6065

6166
/// The text of the token as written in the source code, without any trivia.

0 commit comments

Comments
 (0)