Skip to content

Commit 61a4c7d

Browse files
committed
Improved diagnostics for misplaced AttributeList in variable declaration
1 parent b6adf00 commit 61a4c7d

File tree

9 files changed

+104
-16
lines changed

9 files changed

+104
-16
lines changed

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/SyntaxRewriterFile.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,19 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: generateCopyrightHeader
3030
open class SyntaxRewriter
3131
"""
3232
) {
33-
DeclSyntax("public init() {}")
33+
DeclSyntax(
34+
"""
35+
let arena: SyntaxArena?
36+
"""
37+
)
38+
39+
DeclSyntax(
40+
"""
41+
public init(arena: SyntaxArena? = nil) {
42+
self.arena = arena
43+
}
44+
"""
45+
)
3446

3547
for node in SYNTAX_NODES where node.isVisitable {
3648
if node.baseType.baseName == "Syntax" && node.name != "MissingSyntax" {
@@ -322,7 +334,7 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: generateCopyrightHeader
322334
// Sanity check, ensure the new children are the same length.
323335
assert(newLayout.count == node.raw.layoutView!.children.count)
324336
325-
let arena = SyntaxArena()
337+
let arena = self.arena ?? SyntaxArena()
326338
let newRaw = node.raw.layoutView!.replacingLayout(with: Array(newLayout), arena: arena)
327339
// 'withExtendedLifetime' to keep 'SyntaxArena's of them alive until here.
328340
return withExtendedLifetime((arena, rewrittens)) {

Sources/SwiftDiagnostics/DiagnosticsFormatter.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -301,10 +301,17 @@ public struct DiagnosticsFormatter {
301301
}
302302
}
303303

304-
for diag in diags.dropLast(1) {
305-
annotatedSource.append("\(preMessage)├─ \(colorizeIfRequested(diag.diagMessage))\n")
304+
for (index, diag) in diags.enumerated() {
305+
if index < (diags.count - 1) {
306+
annotatedSource.append("\(preMessage)├─ \(colorizeIfRequested(diag.diagMessage))\n")
307+
} else {
308+
annotatedSource.append("\(preMessage)╰─ \(colorizeIfRequested(diag.diagMessage))\n")
309+
}
310+
for fixIt in diag.fixIts {
311+
annotatedSource.append("\(preMessage) 🔨 \(fixIt.message.message)\n")
312+
}
306313
}
307-
annotatedSource.append("\(preMessage)╰─ \(colorizeIfRequested(diags.last!.diagMessage))\n")
314+
308315
}
309316

310317
// Add suffix text.

Sources/SwiftParser/Declarations.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,12 +1521,31 @@ extension Parser {
15211521
let (unexpectedBeforeIntroducer, introducer) = self.eat(handle)
15221522
let hasTryBeforeIntroducer = unexpectedBeforeIntroducer?.containsToken(where: { TokenSpec(.try) ~= $0 }) ?? false
15231523

1524+
var unexpectedBetweenBindingKeywordAndBindings: RawUnexpectedNodesSyntax?
1525+
var attrs = attrs
15241526
var elements = [RawPatternBindingSyntax]()
15251527
do {
15261528
var keepGoing: RawTokenSyntax? = nil
15271529
var loopProgress = LoopProgressCondition()
15281530
repeat {
15291531

1532+
if self.at(.atSign),
1533+
attrs.attributes?.isEmpty ?? true,
1534+
let recoveredAttributes = self.parseAttributeList()
1535+
{
1536+
unexpectedBetweenBindingKeywordAndBindings = RawUnexpectedNodesSyntax([recoveredAttributes], arena: self.arena)
1537+
1538+
class TokenMissingMaker: SyntaxRewriter {
1539+
override func visit(_ token: TokenSyntax) -> TokenSyntax {
1540+
return .init(token.tokenKind, presence: .missing)
1541+
}
1542+
}
1543+
let tokenMissingMaker = TokenMissingMaker(arena: self.arena)
1544+
let missingAttributes = tokenMissingMaker.visit(Syntax(raw: recoveredAttributes.raw)).raw
1545+
attrs.attributes = RawAttributeListSyntax(missingAttributes)
1546+
1547+
}
1548+
15301549
var (pattern, typeAnnotation) = self.parseTypedPattern()
15311550

15321551
// Parse an initializer if present.
@@ -1614,6 +1633,7 @@ extension Parser {
16141633
modifiers: attrs.modifiers,
16151634
unexpectedBeforeIntroducer,
16161635
bindingKeyword: introducer,
1636+
unexpectedBetweenBindingKeywordAndBindings,
16171637
bindings: RawPatternBindingListSyntax(elements: elements, arena: self.arena),
16181638
arena: self.arena
16191639
)

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
204204
unexpectedTokenCondition: { EffectSpecifier(token: $0) != nil },
205205
correctTokens: [effectSpecifiers?.throwsSpecifier, effectSpecifiers?.asyncSpecifier],
206206
message: { EffectsSpecifierAfterArrow(effectsSpecifiersAfterArrow: $0) },
207-
moveFixIt: { MoveTokensInFrontOfFixIt(movedTokens: $0, inFrontOf: .arrow) },
207+
moveFixIt: { MoveNodesInFrontOfFixIt(movedNodes: $0, inFrontOf: .arrow) },
208208
removeRedundantFixIt: { RemoveRedundantFixIt(removeTokens: $0) }
209209
)
210210
}
@@ -251,7 +251,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
251251
unexpectedTokenCondition: { AsyncEffectSpecifier(token: $0) != nil },
252252
correctTokens: [node.asyncSpecifier],
253253
message: { AsyncMustPrecedeThrows(asyncKeywords: $0, throwsKeyword: throwsSpecifier) },
254-
moveFixIt: { MoveTokensInFrontOfFixIt(movedTokens: $0, inFrontOf: throwsSpecifier.tokenKind) },
254+
moveFixIt: { MoveNodesInFrontOfFixIt(movedNodes: $0, inFrontOf: throwsSpecifier.tokenKind) },
255255
removeRedundantFixIt: { RemoveRedundantFixIt(removeTokens: $0) }
256256
)
257257
}
@@ -1170,6 +1170,30 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
11701170
moveFixIt: { MoveTokensAfterFixIt(movedTokens: $0, after: .equal) },
11711171
removeRedundantFixIt: { RemoveRedundantFixIt(removeTokens: $0) }
11721172
)
1173+
1174+
if let attributes = node.attributes,
1175+
attributes.isMissingAllTokens,
1176+
let unexpected = node.unexpectedBetweenBindingKeywordAndBindings,
1177+
unexpected.only?.is(AttributeListSyntax.self) ?? false
1178+
{
1179+
1180+
let fixit = FixIt(
1181+
message: MoveNodesInFrontOfFixIt(movedNodes: [unexpected], inFrontOf: node.bindingKeyword.tokenKind),
1182+
changes: [
1183+
.makeMissing(unexpected),
1184+
.makePresent(attributes, trailingTrivia: .space),
1185+
]
1186+
)
1187+
1188+
addDiagnostic(
1189+
unexpected,
1190+
.misplacedAttributeInVarDecl,
1191+
fixIts: [fixit],
1192+
handledNodes: [attributes.id, unexpected.id]
1193+
)
1194+
1195+
}
1196+
11731197
return .visitChildren
11741198
}
11751199

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,9 @@ extension DiagnosticMessage where Self == StaticParserError {
212212
public static var unexpectedSemicolon: Self {
213213
.init("unexpected ';' separator")
214214
}
215+
public static var misplacedAttributeInVarDecl: Self {
216+
.init("misplaced attribute in variable declaration")
217+
}
215218
}
216219

217220
// MARK: - Diagnostics (please sort alphabetically)
@@ -509,15 +512,15 @@ public struct MoveTokensAfterFixIt: ParserFixIt {
509512
}
510513
}
511514

512-
public struct MoveTokensInFrontOfFixIt: ParserFixIt {
513-
/// The token that should be moved
514-
public let movedTokens: [TokenSyntax]
515+
public struct MoveNodesInFrontOfFixIt<Node: SyntaxProtocol>: ParserFixIt {
516+
/// The nodes that should be moved
517+
public let movedNodes: [Node]
515518

516-
/// The token before which 'movedTokens' should be moved
519+
/// The token after which `movedNodes` should be moved
517520
public let inFrontOf: TokenKind
518521

519522
public var message: String {
520-
"move \(nodesDescription(movedTokens, format: false)) in front of '\(inFrontOf.nameForDiagnostics)'"
523+
"move \(nodesDescription(movedNodes, format: false)) in front of '\(inFrontOf.nameForDiagnostics)'"
521524
}
522525
}
523526

Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
/// All typed raw syntax nodes conform to this protocol.
14-
/// `RawXXXSyntax` is a typed wrappeer of `RawSyntax`.
14+
/// `RawXXXSyntax` is a typed wrapper of `RawSyntax`.
1515
@_spi(RawSyntax)
1616
public protocol RawSyntaxNodeProtocol: CustomStringConvertible, TextOutputStreamable {
1717
/// Returns `true` if `raw` can be cast to this concrete raw syntax type.

Sources/SwiftSyntax/generated/SyntaxRewriter.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
//===----------------------------------------------------------------------===//
2323

2424
open class SyntaxRewriter {
25-
public init() {
25+
let arena: SyntaxArena?
26+
27+
public init(arena: SyntaxArena? = nil) {
28+
self.arena = arena
2629
}
2730

2831
/// Visit a `AccessPathComponentSyntax`.
@@ -6755,7 +6758,7 @@ open class SyntaxRewriter {
67556758
// Sanity check, ensure the new children are the same length.
67566759
assert(newLayout.count == node.raw.layoutView!.children.count)
67576760

6758-
let arena = SyntaxArena()
6761+
let arena = self.arena ?? SyntaxArena()
67596762
let newRaw = node.raw.layoutView!.replacingLayout(with: Array(newLayout), arena: arena)
67606763
// 'withExtendedLifetime' to keep 'SyntaxArena's of them alive until here.
67616764
return withExtendedLifetime((arena, rewrittens)) {

Sources/swift-parser-cli/swift-parser-cli.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ class PrintDiags: ParsableCommand {
217217
let annotatedSource = DiagnosticsFormatter.annotatedSource(
218218
tree: tree,
219219
diags: diags,
220-
colorize: colorize || TerminalHelper.isConnectedToTerminal
220+
colorize: (colorize || TerminalHelper.isConnectedToTerminal && ProcessInfo.processInfo.environment["__XCODE_BUILT_PRODUCTS_DIR_PATHS"] == nil)
221221
)
222222

223223
print(annotatedSource)

Tests/SwiftParserTest/AttributeTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,4 +570,23 @@ final class AttributeTests: XCTestCase {
570570
"""
571571
)
572572
}
573+
574+
func testMisplacedAttributeInVariableDecl() {
575+
AssertParse(
576+
"""
577+
struct A {
578+
var 1️⃣@State name: String
579+
}
580+
""",
581+
diagnostics: [
582+
DiagnosticSpec(message: "misplaced attribute in variable declaration", fixIts: ["move attributes in front of 'var'"])
583+
],
584+
fixedSource:
585+
"""
586+
struct A {
587+
@State var name: String
588+
}
589+
"""
590+
)
591+
}
573592
}

0 commit comments

Comments
 (0)