Skip to content

Commit a09903d

Browse files
authored
Merge pull request #943 from ahoppen/ahoppen/pound-literal-recovery
Improve recovery of unknown pound literals
2 parents a57d7eb + c4d4a8a commit a09903d

File tree

7 files changed

+112
-51
lines changed

7 files changed

+112
-51
lines changed

Sources/SwiftParser/Diagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,18 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
447447
return .visitChildren
448448
}
449449

450+
public override func visit(_ node: IdentifierExprSyntax) -> SyntaxVisitorContinueKind {
451+
if shouldSkip(node) {
452+
return .skipChildren
453+
}
454+
if node.identifier.presence == .missing,
455+
let unexpected = node.unexpectedBeforeIdentifier,
456+
unexpected.first?.as(TokenSyntax.self)?.tokenKind == .pound {
457+
addDiagnostic(unexpected, UnknownDirectiveError(unexpected: unexpected), handledNodes: [unexpected.id, node.identifier.id])
458+
}
459+
return .visitChildren
460+
}
461+
450462
public override func visit(_ node: PrecedenceGroupAssignmentSyntax) -> SyntaxVisitorContinueKind {
451463
if shouldSkip(node) {
452464
return .skipChildren

Sources/SwiftParser/Diagnostics/ParserDiagnosticMessages.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,14 @@ public struct UnexpectedNodesError: ParserError {
183183
}
184184
}
185185

186+
public struct UnknownDirectiveError: ParserError {
187+
public let unexpected: UnexpectedNodesSyntax
188+
189+
public var message: String {
190+
return "use of unknown directive \(nodesDescription([unexpected], format: false))"
191+
}
192+
}
193+
186194
// MARK: - Fix-Its (please sort alphabetically)
187195

188196
public enum StaticParserFixIt: String, FixItMessage {

Sources/SwiftParser/Expressions.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,6 +1112,8 @@ extension Parser {
11121112
wildcard: wild,
11131113
arena: self.arena
11141114
))
1115+
case (.pound, let handle)?:
1116+
return RawExprSyntax(self.parseUnknownPoundLiteral(poundHandle: handle))
11151117
case (.poundSelectorKeyword, _)?:
11161118
return RawExprSyntax(self.parseObjectiveCSelectorLiteral())
11171119
case (.poundKeyPathKeyword, _)?:
@@ -1209,6 +1211,32 @@ extension Parser {
12091211
}
12101212
}
12111213

1214+
extension Parser {
1215+
/// Parse an unknown pound literal, that starts with a `#` at `poundHandle`
1216+
/// into a `RawIdentifierExprSyntax` that is missing the identifier and
1217+
/// instead contains all the tokens of the pound literal as unexpected tokens.
1218+
mutating func parseUnknownPoundLiteral(poundHandle: TokenConsumptionHandle) -> RawIdentifierExprSyntax {
1219+
var unexpected: [RawSyntax] = []
1220+
unexpected.append(RawSyntax(self.eat(poundHandle)))
1221+
if let name = self.consume(if: .identifier) {
1222+
unexpected.append(RawSyntax(name))
1223+
}
1224+
// If there is a parenthesis, consume all tokens up to the closing parenthesis.
1225+
if let leftParen = self.consume(if: .leftParen) {
1226+
unexpected.append(RawSyntax(leftParen))
1227+
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
1228+
unexpected += unexpectedBeforeRightParen?.elements ?? []
1229+
unexpected.append(RawSyntax(rightParen))
1230+
}
1231+
return RawIdentifierExprSyntax(
1232+
RawUnexpectedNodesSyntax(elements: unexpected, arena: self.arena),
1233+
identifier: missingToken(.identifier, text: nil),
1234+
declNameArguments: nil,
1235+
arena: self.arena
1236+
)
1237+
}
1238+
}
1239+
12121240
extension Parser {
12131241
/// Parse a string literal expression.
12141242
///

Sources/SwiftParser/RawTokenKindSubset.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,7 @@ enum PrimaryExpressionStart: RawTokenKindSubset {
737737
case leftSquareBracket
738738
case nilKeyword
739739
case period
740+
case pound // For recovery of unknown directives
740741
case poundColorLiteralKeyword
741742
case poundColumnKeyword
742743
case poundDsohandleKeyword
@@ -776,6 +777,7 @@ enum PrimaryExpressionStart: RawTokenKindSubset {
776777
case .leftSquareBracket: self = .leftSquareBracket
777778
case .nilKeyword: self = .nilKeyword
778779
case .period: self = .period
780+
case .pound: self = .pound
779781
case .poundColorLiteralKeyword: self = .poundColorLiteralKeyword
780782
case .poundColumnKeyword: self = .poundColumnKeyword
781783
case .poundDsohandleKeyword: self = .poundDsohandleKeyword
@@ -818,6 +820,7 @@ enum PrimaryExpressionStart: RawTokenKindSubset {
818820
case .leftSquareBracket: return .leftSquareBracket
819821
case .nilKeyword: return .nilKeyword
820822
case .period: return .period
823+
case .pound: return .pound
821824
case .poundColorLiteralKeyword: return .poundColorLiteralKeyword
822825
case .poundColumnKeyword: return .poundColumnKeyword
823826
case .poundDsohandleKeyword: return .poundDsohandleKeyword

Tests/SwiftParserTest/translated/DollarIdentifierTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ final class DollarIdentifierTests: XCTestCase {
236236
// TODO: Old parser expected error on line 32: cannot declare entity named '$Protocol'; the '$' prefix is reserved
237237
// TODO: Old parser expected error on line 33: cannot declare entity named '$Precedence'; the '$' prefix is reserved
238238
// TODO: Old parser expected error on line 37: use of unknown directive '#$UnknownDirective'
239-
DiagnosticSpec(message: "extraneous code '#$UnknownDirective()' at top level"),
239+
DiagnosticSpec(message: "use of unknown directive '#$UnknownDirective()'"),
240240
]
241241
)
242242
}

Tests/SwiftParserTest/translated/ObjectLiteralsTests.swift

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,50 @@
33
import XCTest
44

55
final class ObjectLiteralsTests: XCTestCase {
6-
func testObjectLiterals1() {
6+
func testObjectLiterals1a() {
77
AssertParse(
88
"""
9-
let _ = [1️⃣#Color(colorLiteralRed: red, green: green, blue: blue, alpha: alpha)#]
10-
let _ = [2️⃣#Image(imageLiteral: localResourceNameAsString)#]
11-
let _ = [3️⃣#FileReference(fileReferenceLiteral: localResourceNameAsString)#]
9+
let _ = [1️⃣#Color(colorLiteralRed: red, green: green, blue: blue, alpha: alpha)2️⃣#]
1210
""",
1311
diagnostics: [
14-
// TODO: Old parser expected error on line 1: '[#Color(...)#]' has been renamed to '#colorLiteral(...), Fix-It replacements: 9 - 10 = '', 11 - 16 = 'colorLiteral', 17 - 32 = 'red', 78 - 80 = ''
15-
DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code '#Color(colorLiteralRed: red, green: green, blue: blue, alpha: alpha)#' in array"),
16-
// TODO: Old parser expected error on line 2: '[#Image(...)#]' has been renamed to '#imageLiteral(...)', Fix-It replacements: 9 - 10 = '', 11 - 16 = 'imageLiteral', 17 - 29 = 'resourceName', 57 - 59 = ''
17-
DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code '#Image(imageLiteral: localResourceNameAsString)#' in array"),
18-
// TODO: Old parser expected error on line 3: '[#FileReference(...)#]' has been renamed to '#fileLiteral(...)', Fix-It replacements: 9 - 10 = '', 11 - 24 = 'fileLiteral', 25 - 45 = 'resourceName', 73 - 75 = ''
19-
DiagnosticSpec(locationMarker: "3️⃣", message: "unexpected code '#FileReference(fileReferenceLiteral: localResourceNameAsString)#' in array"),
12+
DiagnosticSpec(locationMarker: "1️⃣", message: "use of unknown directive '#Color(colorLiteralRed: red, green: green, blue: blue, alpha: alpha)'"),
13+
DiagnosticSpec(locationMarker: "2️⃣", message: "use of unknown directive '#'"),
2014
]
2115
)
2216
}
2317

18+
func testObjectLiterals1b() {
19+
AssertParse(
20+
"""
21+
let _ = [1️⃣#Image(imageLiteral: localResourceNameAsString)2️⃣#]
22+
""",
23+
diagnostics: [
24+
DiagnosticSpec(locationMarker: "1️⃣", message: "use of unknown directive '#Image(imageLiteral: localResourceNameAsString)'"),
25+
DiagnosticSpec(locationMarker: "2️⃣", message: "use of unknown directive '#'"),
26+
]
27+
)
28+
}
29+
30+
func testObjectLiterals1c() {
31+
AssertParse(
32+
"""
33+
let _ = [1️⃣#FileReference(fileReferenceLiteral: localResourceNameAsString)2️⃣#]
34+
""",
35+
diagnostics: [
36+
DiagnosticSpec(locationMarker: "1️⃣", message: "use of unknown directive '#FileReference(fileReferenceLiteral: localResourceNameAsString)'"),
37+
DiagnosticSpec(locationMarker: "2️⃣", message: "use of unknown directive '#'"),
38+
]
39+
)
40+
}
41+
42+
2443
func testObjectLiterals2a() {
2544
AssertParse(
2645
"""
2746
let _ = 1️⃣#Color(colorLiteralRed: red, green: green, blue: blue, alpha: alpha)
2847
""",
2948
diagnostics: [
30-
// TODO: Old parser expected error on line 1: '#Color(...)' has been renamed to '#colorLiteral(...), Fix-It replacements: 10 - 15 = 'colorLiteral', 16 - 31 = 'red'
31-
DiagnosticSpec(message: "expected expression in variable"),
32-
DiagnosticSpec(message: "extraneous code '#Color(colorLiteralRed: red, green: green, blue: blue, alpha: alpha)' at top level"),
49+
DiagnosticSpec(message: "use of unknown directive '#Color(colorLiteralRed: red, green: green, blue: blue, alpha: alpha)'"),
3350
]
3451
)
3552
}
@@ -40,9 +57,7 @@ final class ObjectLiteralsTests: XCTestCase {
4057
let _ = 1️⃣#Image(imageLiteral: localResourceNameAsString)
4158
""",
4259
diagnostics: [
43-
// TODO: Old parser expected error on line 1: '#Image(...)' has been renamed to '#imageLiteral(...)', Fix-It replacements: 10 - 15 = 'imageLiteral', 16 - 28 = 'resourceName'
44-
DiagnosticSpec(message: "expected expression in variable"),
45-
DiagnosticSpec(message: "extraneous code '#Image(imageLiteral: localResourceNameAsString)' at top level"),
60+
DiagnosticSpec(message: "use of unknown directive '#Image(imageLiteral: localResourceNameAsString)'"),
4661
]
4762
)
4863
}
@@ -54,9 +69,7 @@ final class ObjectLiteralsTests: XCTestCase {
5469
let _ = 1️⃣#FileReference(fileReferenceLiteral: localResourceNameAsString)
5570
""",
5671
diagnostics: [
57-
// TODO: Old parser expected error on line 1: '#FileReference(...)' has been renamed to '#fileLiteral(...)', Fix-It replacements: 10 - 23 = 'fileLiteral', 24 - 44 = 'resourceName'
58-
DiagnosticSpec(message: "expected expression in variable"),
59-
DiagnosticSpec(message: "extraneous code '#FileReference(fileReferenceLiteral: localResourceNameAsString)' at top level"),
72+
DiagnosticSpec(message: "use of unknown directive '#FileReference(fileReferenceLiteral: localResourceNameAsString)'"),
6073
]
6174
)
6275
}
@@ -68,9 +81,7 @@ final class ObjectLiteralsTests: XCTestCase {
6881
let _ = 1️⃣#notAPound
6982
""",
7083
diagnostics: [
71-
// TODO: Old parser expected error on line 1: use of unknown directive '#notAPound'
72-
DiagnosticSpec(message: "expected expression in variable"),
73-
DiagnosticSpec(message: "extraneous code '#notAPound' at top level"),
84+
DiagnosticSpec(message: "use of unknown directive '#notAPound'"),
7485
]
7586
)
7687
}
@@ -81,72 +92,67 @@ final class ObjectLiteralsTests: XCTestCase {
8192
let _ = 1️⃣#notAPound(1, 2)
8293
""",
8394
diagnostics: [
84-
// TODO: Old parser expected error on line 1: use of unknown directive '#notAPound'
85-
DiagnosticSpec(message: "expected expression in variable"),
86-
DiagnosticSpec(message: "extraneous code '#notAPound(1, 2)' at top level"),
95+
DiagnosticSpec(message: "use of unknown directive '#notAPound(1, 2)'"),
8796
]
8897
)
8998
}
9099

91100
func testObjectLiterals3c() {
92101
AssertParse(
93102
"""
94-
let _ = 1️⃣#Color // {{none}}
103+
let _ = 1️⃣#Color
95104
""",
96105
diagnostics: [
97-
// TODO: Old parser expected error on line 1: expected argument list in object literal
98-
DiagnosticSpec(message: "expected expression in variable"),
99-
DiagnosticSpec(message: "extraneous code '#Color' at top level"),
106+
DiagnosticSpec(message: "use of unknown directive '#Color'"),
100107
]
101108
)
102109
}
103110

104111
func testObjectLiterals4() {
105112
AssertParse(
106113
"""
107-
let _ = [1️⃣##] // {{none}}
114+
let _ = [1️⃣#2️⃣#]
108115
""",
109116
diagnostics: [
110-
// TODO: Old parser expected error on line 1: expected expression in container literal
111-
DiagnosticSpec(message: "unexpected code '##' in array"),
117+
DiagnosticSpec(locationMarker: "1️⃣", message: "use of unknown directive '#'"),
118+
DiagnosticSpec(locationMarker: "2️⃣", message: "use of unknown directive '#'"),
112119
]
113120
)
114121
}
115122

116123
func testObjectLiterals5() {
117124
AssertParse(
118125
"""
119-
let _ = [1️⃣#Color(_: 1, green: 1, 2)
126+
let _ = [1️⃣#Color(_: 1, green: 1, 2)2️⃣
120127
""",
121128
diagnostics: [
122-
// TODO: Old parser expected error on line 1: '[#Color(...)#]' has been renamed to '#colorLiteral(...)', Fix-It replacements: 9 - 10 = '', 11 - 16 = 'colorLiteral', 17 - 18 = 'red'
123-
DiagnosticSpec(message: "expected ']' to end array"),
124-
DiagnosticSpec(message: "extraneous code '#Color(_: 1, green: 1, 2)' at top level"),
129+
DiagnosticSpec(locationMarker: "1️⃣", message: "use of unknown directive '#Color(_: 1, green: 1, 2)'"),
130+
DiagnosticSpec(locationMarker: "2️⃣", message: "expected ']' to end array"),
125131
]
126132
)
127133
}
128134

129135
func testObjectLiterals6() {
130136
AssertParse(
131137
"""
132-
let _ = [1️⃣#Color(red: 1, green: 1, blue: 1)#
138+
let _ = [1️⃣#Color(red: 1, green: 1, blue: 1)2️⃣#3️⃣
133139
""",
134140
diagnostics: [
135-
// TODO: Old parser expected error on line 1: '[#Color(...)#]' has been renamed to '#colorLiteral(...)', Fix-It replacements: 9 - 10 = '', 11 - 16 = 'colorLiteral', 17 - 20 = 'red', 43 - 44 = ''
136-
DiagnosticSpec(message: "expected ']' to end array"),
137-
DiagnosticSpec(message: "extraneous code '#Color(red: 1, green: 1, blue: 1)#' at top level"),
141+
DiagnosticSpec(locationMarker: "1️⃣", message: "use of unknown directive '#Color(red: 1, green: 1, blue: 1)'"),
142+
DiagnosticSpec(locationMarker: "2️⃣", message: "use of unknown directive '#'"),
143+
DiagnosticSpec(locationMarker: "3️⃣", message: "expected ']' to end array"),
138144
]
139145
)
140146
}
141147

142148
func testObjectLiterals7() {
143149
AssertParse(
144150
"""
145-
let _ = [1️⃣#Color(withRed: 1, green: 1, whatever: 2)#]
151+
let _ = [1️⃣#Color(withRed: 1, green: 1, whatever: 2)2️⃣#]
146152
""",
147153
diagnostics: [
148-
// TODO: Old parser expected error on line 1: '[#Color(...)#]' has been renamed to '#colorLiteral(...)', Fix-It replacements: 9 - 10 = '', 11 - 16 = 'colorLiteral', 17 - 24 = 'red', 51 - 53 = ''
149-
DiagnosticSpec(message: "unexpected code '#Color(withRed: 1, green: 1, whatever: 2)#' in array"),
154+
DiagnosticSpec(message: "use of unknown directive '#Color(withRed: 1, green: 1, whatever: 2)'"),
155+
DiagnosticSpec(locationMarker: "2️⃣", message: "use of unknown directive '#'")
150156
]
151157
)
152158
}
@@ -157,9 +163,7 @@ final class ObjectLiteralsTests: XCTestCase {
157163
let _ = 1️⃣#Color(_: 1, green: 1)
158164
""",
159165
diagnostics: [
160-
// TODO: Old parser expected error on line 1: '#Color(...)' has been renamed to '#colorLiteral(...)', Fix-It replacements: 10 - 15 = 'colorLiteral', 16 - 17 = 'red'
161-
DiagnosticSpec(message: "expected expression in variable"),
162-
DiagnosticSpec(message: "extraneous code '#Color(_: 1, green: 1)' at top level"),
166+
DiagnosticSpec(message: "use of unknown directive '#Color(_: 1, green: 1)'"),
163167
]
164168
)
165169
}

Tests/SwiftParserTest/translated/RawStringErrorsTests.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,16 @@ final class RawStringErrorsTests: XCTestCase {
3434
func testRawStringErrors3() {
3535
AssertParse(
3636
#####"""
37-
let _ = ###"""invalid"###1️⃣###
37+
let _ = ###"""invalid"###1️⃣#2️⃣#3️⃣#
3838
"""#####,
3939
diagnostics: [
4040
// TODO: Old parser expected error on line 1: too many '#' characters in closing delimiter, Fix-It replacements: 26 - 29 = ''
4141
// TODO: Old parser expected error on line 1: consecutive statements on a line must be separated by ';'
4242
// TODO: Old parser expected error on line 1: expected expression
43-
DiagnosticSpec(message: "extraneous code '###' at top level"),
43+
DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive statements on a line must be separated by ';'"),
44+
DiagnosticSpec(locationMarker: "1️⃣", message: "use of unknown directive '#'"),
45+
DiagnosticSpec(locationMarker: "2️⃣", message: "use of unknown directive '#'"),
46+
DiagnosticSpec(locationMarker: "3️⃣", message: "use of unknown directive '#'"),
4447
]
4548
)
4649
}
@@ -60,13 +63,16 @@ final class RawStringErrorsTests: XCTestCase {
6063
func testRawStringErrors5() {
6164
AssertParse(
6265
#####"""
63-
let _ = ###"invalid"###1️⃣###
66+
let _ = ###"invalid"###1️⃣#2️⃣#3️⃣#
6467
"""#####,
6568
diagnostics: [
6669
// TODO: Old parser expected error on line 1: too many '#' characters in closing delimiter, Fix-It replacements: 24 - 27 = ''
6770
// TODO: Old parser expected error on line 1: consecutive statements on a line must be separated by ';'
6871
// TODO: Old parser expected error on line 1: expected expression
69-
DiagnosticSpec(message: "extraneous code '###' at top level"),
72+
DiagnosticSpec(locationMarker: "1️⃣", message: "consecutive statements on a line must be separated by ';'"),
73+
DiagnosticSpec(locationMarker: "1️⃣", message: "use of unknown directive '#'"),
74+
DiagnosticSpec(locationMarker: "2️⃣", message: "use of unknown directive '#'"),
75+
DiagnosticSpec(locationMarker: "3️⃣", message: "use of unknown directive '#'"),
7076
]
7177
)
7278
}

0 commit comments

Comments
 (0)