Skip to content

Commit 4182d27

Browse files
committed
Add diagnostic for array type
1 parent e92c961 commit 4182d27

File tree

6 files changed

+92
-74
lines changed

6 files changed

+92
-74
lines changed

Sources/SwiftParser/Types.swift

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1052,7 +1052,38 @@ extension Parser {
10521052
)
10531053
)
10541054
} else {
1055-
return self.parseType()
1055+
var result = self.parseType()
1056+
1057+
guard !result.hasError else {
1058+
return result
1059+
}
1060+
1061+
if self.at(.rightSquareBracket) {
1062+
let (unexpectedBeforeRSquareBracket, rightSquareBracket) = self.expect(.rightSquareBracket)
1063+
result = RawTypeSyntax(
1064+
RawArrayTypeSyntax(
1065+
leftSquareBracket: missingToken(.leftSquareBracket),
1066+
elementType: result,
1067+
unexpectedBeforeRSquareBracket,
1068+
rightSquareBracket: rightSquareBracket,
1069+
arena: self.arena
1070+
)
1071+
)
1072+
}
1073+
1074+
// After recovering, we continue parse the remaing `?` or `!` if they are present.
1075+
var continueParsingQuestionOrExclamationMark = true
1076+
while continueParsingQuestionOrExclamationMark {
1077+
if self.at(TokenSpec(.postfixQuestionMark, allowAtStartOfLine: false)) {
1078+
result = RawTypeSyntax(self.parseOptionalType(result))
1079+
} else if self.at(TokenSpec(.exclamationMark, allowAtStartOfLine: false)) {
1080+
result = RawTypeSyntax(self.parseImplicitlyUnwrappedOptionalType(result))
1081+
} else {
1082+
continueParsingQuestionOrExclamationMark = false
1083+
}
1084+
}
1085+
1086+
return result
10561087
}
10571088
}
10581089
}

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,23 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
426426
return .visitChildren
427427
}
428428

429+
public override func visit(_ node: ArrayTypeSyntax) -> SyntaxVisitorContinueKind {
430+
if shouldSkip(node) {
431+
return .skipChildren
432+
}
433+
434+
if node.leftSquareBracket.presence == .missing && node.rightSquareBracket.presence == .present {
435+
addDiagnostic(
436+
node.rightSquareBracket,
437+
.extraRightBracket,
438+
fixIts: [.init(message: InsertFixIt(tokenToBeInserted: node.leftSquareBracket), changes: .makePresent(node.leftSquareBracket))],
439+
handledNodes: [node.leftSquareBracket.id]
440+
)
441+
}
442+
443+
return .visitChildren
444+
}
445+
429446
public override func visit(_ node: AttributeSyntax) -> SyntaxVisitorContinueKind {
430447
if shouldSkip(node) {
431448
return .skipChildren

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ extension DiagnosticMessage where Self == StaticParserError {
140140
public static var expectedSequenceExpressionInForEachLoop: Self {
141141
.init("expected Sequence expression for for-each loop")
142142
}
143+
public static var extraRightBracket: Self {
144+
.init("unexpected ']' in type; did you mean to write an array type?")
145+
}
143146
public static var initializerInPattern: Self {
144147
.init("unexpected initializer in pattern; did you mean to use '='?")
145148
}
@@ -557,6 +560,14 @@ extension FixItMessage where Self == StaticParserFixIt {
557560
}
558561
}
559562

563+
public struct InsertFixIt: ParserFixIt {
564+
public let tokenToBeInserted: TokenSyntax
565+
566+
public var message: String {
567+
"insert '\(tokenToBeInserted.text)'"
568+
}
569+
}
570+
560571
public struct MoveTokensAfterFixIt: ParserFixIt {
561572
/// The token that should be moved
562573
public let movedTokens: [TokenSyntax]

Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ public extension RawSyntaxNodeProtocol {
4646
var isEmpty: Bool {
4747
return raw.byteLength == 0
4848
}
49+
50+
/// Whether the tree contained by this layout has any
51+
/// - missing nodes or
52+
/// - unexpected nodes or
53+
/// - tokens with a `TokenDiagnostic` of severity `error`
54+
var hasError: Bool {
55+
return raw.recursiveFlags.contains(.hasError)
56+
}
4957
}
5058

5159
/// `RawSyntax` itself conforms to `RawSyntaxNodeProtocol`.

Sources/SwiftSyntax/Syntax.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,9 @@ public extension SyntaxProtocol {
238238
/// Whether the tree contained by this layout has any
239239
/// - missing nodes or
240240
/// - unexpected nodes or
241-
/// - tokens with a `TokenDiagnostic` of severity `error`
241+
/// - tokens with a ``TokenDiagnostic`` of severity ``TokenDiagnostic/Severity-swift.enum/error``.
242242
var hasError: Bool {
243-
return raw.recursiveFlags.contains(.hasError)
243+
return raw.hasError
244244
}
245245

246246
/// Whether the tree contained by this layout has any tokens with a

Tests/SwiftParserTest/translated/RecoveryTests.swift

Lines changed: 22 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,15 +1119,27 @@ final class RecoveryTests: XCTestCase {
11191119
)
11201120
}
11211121

1122-
func testRecovery98a() {
1123-
assertParse(
1124-
"""
1125-
let a1: Swift.Int1️⃣]
1126-
""",
1127-
diagnostics: [
1128-
DiagnosticSpec(message: "extraneous code ']' at top level")
1129-
]
1130-
)
1122+
func testRecovery98() {
1123+
let testCases: [UInt: (testCase: String, fixedSource: String)] = [
1124+
#line: ("let a1: Swift.Int1️⃣]", "let a1: [Swift.Int]"),
1125+
#line: ("let a3: Set<Int>1️⃣]", "let a3: [Set<Int>]"),
1126+
#line: ("let a4: Int1️⃣]?", "let a4: [Int]?"),
1127+
#line: ("let a5: Int?1️⃣]", "let a5: [Int?]"),
1128+
#line: ("let a6: [Int]1️⃣]", "let a6: [[Int]]"),
1129+
#line: ("let a7: [String: Int]1️⃣]", "let a7: [[String: Int]]"),
1130+
#line: ("func foo() -> Int1️⃣]??", "func foo() -> [Int]??"),
1131+
]
1132+
1133+
for (line, testCase) in testCases {
1134+
assertParse(
1135+
testCase.testCase,
1136+
diagnostics: [
1137+
DiagnosticSpec(message: "unexpected ']' in type; did you mean to write an array type?", fixIts: ["insert '['"], line: line)
1138+
],
1139+
fixedSource: testCase.fixedSource,
1140+
line: line
1141+
)
1142+
}
11311143
}
11321144

11331145
func testRecovery98b() {
@@ -1142,66 +1154,6 @@ final class RecoveryTests: XCTestCase {
11421154
)
11431155
}
11441156

1145-
func testRecovery98c() {
1146-
assertParse(
1147-
"""
1148-
let a3: Set<Int>1️⃣]
1149-
""",
1150-
diagnostics: [
1151-
// TODO: Old parser expected error on line 4: unexpected ']' in type; did you mean to write an array type?, Fix-It replacements: 11 - 11 = '['
1152-
DiagnosticSpec(message: "extraneous code ']' at top level")
1153-
]
1154-
)
1155-
}
1156-
1157-
func testRecovery98d() {
1158-
assertParse(
1159-
"""
1160-
let a4: Int1️⃣]?
1161-
""",
1162-
diagnostics: [
1163-
// TODO: Old parser expected error on line 5: unexpected ']' in type; did you mean to write an array type?, Fix-It replacements: 11 - 11 = '['
1164-
DiagnosticSpec(message: "extraneous code ']?' at top level")
1165-
]
1166-
)
1167-
}
1168-
1169-
func testRecovery98e() {
1170-
assertParse(
1171-
"""
1172-
let a5: Int?1️⃣]
1173-
""",
1174-
diagnostics: [
1175-
// TODO: Old parser expected error on line 6: unexpected ']' in type; did you mean to write an array type?, Fix-It replacements: 11 - 11 = '['
1176-
DiagnosticSpec(message: "extraneous code ']' at top level")
1177-
]
1178-
)
1179-
}
1180-
1181-
func testRecovery98f() {
1182-
assertParse(
1183-
"""
1184-
let a6: [Int]1️⃣]
1185-
""",
1186-
diagnostics: [
1187-
// TODO: Old parser expected error on line 7: unexpected ']' in type; did you mean to write an array type?, Fix-It replacements: 11 - 11 = '['
1188-
DiagnosticSpec(message: "extraneous code ']' at top level")
1189-
]
1190-
)
1191-
}
1192-
1193-
func testRecovery98g() {
1194-
assertParse(
1195-
"""
1196-
let a7: [String: Int]1️⃣]
1197-
""",
1198-
diagnostics: [
1199-
// TODO: Old parser expected error on line 8: unexpected ']' in type; did you mean to write an array type?, Fix-It replacements: 11 - 11 = '['
1200-
DiagnosticSpec(message: "extraneous code ']' at top level")
1201-
]
1202-
)
1203-
}
1204-
12051157
func testRecovery99() {
12061158
assertParse(
12071159
"""
@@ -1241,8 +1193,7 @@ final class RecoveryTests: XCTestCase {
12411193
diagnostics: [
12421194
DiagnosticSpec(locationMarker: "1️⃣", message: "expected '}' to end struct"),
12431195
DiagnosticSpec(locationMarker: "2️⃣", message: "expected ']' to end array"),
1244-
// TODO: Old parser expected error on line 5: unexpected ']' in type; did you mean to write an array type?, Fix-It replacements: 17 - 17 = '['
1245-
DiagnosticSpec(locationMarker: "3️⃣", message: "unexpected code ']' in function"),
1196+
DiagnosticSpec(locationMarker: "3️⃣", message: "unexpected ']' in type; did you mean to write an array type?"),
12461197
DiagnosticSpec(locationMarker: "4️⃣", message: "extraneous brace at top level"),
12471198
]
12481199
)

0 commit comments

Comments
 (0)