Skip to content

Commit f0cec9b

Browse files
committed
Improve diagnostics for invalid version tuples
1 parent dd976be commit f0cec9b

File tree

5 files changed

+90
-49
lines changed

5 files changed

+90
-49
lines changed

Sources/SwiftParser/Availability.swift

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,27 @@ extension Parser {
253253
return self.consumeAnyToken()
254254
}
255255

256+
/// Consume the unexpected version token(e.g. integerLiteral, floatingLiteral) until the period no longer appears.
257+
private mutating func parseUnexpectedVersionTokens() -> RawUnexpectedNodesSyntax? {
258+
var unexpectedTokens: [RawSyntax] = []
259+
var keepGoing: RawTokenSyntax? = nil
260+
var loopProgress = LoopProgressCondition()
261+
repeat {
262+
if let keepGoing {
263+
unexpectedTokens.append(RawSyntax(keepGoing))
264+
}
265+
if let unexpectedVersion = self.consume(if: .integerLiteral, .floatingLiteral, .identifier) {
266+
unexpectedTokens.append(RawSyntax(unexpectedVersion))
267+
}
268+
keepGoing = self.consume(if: .period)
269+
} while keepGoing != nil && loopProgress.evaluate(currentToken)
270+
271+
guard !unexpectedTokens.isEmpty else {
272+
return nil
273+
}
274+
return RawUnexpectedNodesSyntax(elements: unexpectedTokens, arena: self.arena)
275+
}
276+
256277
/// Parse a dot-separated list of version numbers.
257278
///
258279
/// Grammar
@@ -286,24 +307,29 @@ extension Parser {
286307
} else {
287308
trailingComponents.append(versionComponent)
288309
}
289-
}
290310

291-
var unexpectedTrailingComponents: RawUnexpectedNodesSyntax?
292-
293-
if !trailingComponents.isEmpty {
294-
unexpectedTrailingComponents = RawUnexpectedNodesSyntax(elements: trailingComponents.compactMap { $0.as(RawSyntax.self) }, arena: self.arena)
311+
if version.isMissing {
312+
break
313+
}
295314
}
296315

316+
let unexpectedTrailingComponents = RawUnexpectedNodesSyntax(elements: trailingComponents.map(RawSyntax.init), arena: self.arena)
317+
let unexpectedAfterComponents = self.parseUnexpectedVersionTokens()
297318
return RawVersionTupleSyntax(
298319
major: major,
299320
components: RawVersionComponentListSyntax(elements: components, arena: self.arena),
300-
unexpectedTrailingComponents,
321+
RawUnexpectedNodesSyntax(combining: unexpectedTrailingComponents, unexpectedAfterComponents, arena: self.arena),
301322
arena: self.arena
302323
)
303-
304324
} else {
305325
let major = self.expectDecimalIntegerWithoutRecovery()
306-
return RawVersionTupleSyntax(major: major, components: nil, arena: self.arena)
326+
let unexpectedAfterComponents = self.parseUnexpectedVersionTokens()
327+
return RawVersionTupleSyntax(
328+
major: major,
329+
components: nil,
330+
unexpectedAfterComponents,
331+
arena: self.arena
332+
)
307333
}
308334
}
309335
}

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1893,14 +1893,22 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
18931893
return .skipChildren
18941894
}
18951895

1896-
if let trailingComponents = node.unexpectedAfterComponents,
1897-
let components = node.components
1898-
{
1899-
addDiagnostic(
1900-
trailingComponents,
1901-
TrailingVersionAreIgnored(major: node.major, components: components),
1902-
handledNodes: [trailingComponents.id]
1903-
)
1896+
if let unexpectedAfterComponents = node.unexpectedAfterComponents {
1897+
if let components = node.components,
1898+
unexpectedAfterComponents.allSatisfy({ $0.is(VersionComponentSyntax.self) })
1899+
{
1900+
addDiagnostic(
1901+
unexpectedAfterComponents,
1902+
TrailingVersionAreIgnored(major: node.major, components: components),
1903+
handledNodes: [unexpectedAfterComponents.id]
1904+
)
1905+
} else {
1906+
addDiagnostic(
1907+
unexpectedAfterComponents,
1908+
CannotParseVersionTuple(versionTuple: unexpectedAfterComponents),
1909+
handledNodes: [node.major.id, node.components?.id, unexpectedAfterComponents.id].compactMap { $0 }
1910+
)
1911+
}
19041912
}
19051913

19061914
return .visitChildren

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ public struct CannotParseVersionTuple: ParserError {
286286
public let versionTuple: UnexpectedNodesSyntax
287287

288288
public var message: String {
289-
return "cannot parse version \(versionTuple)"
289+
return "cannot parse version component \(versionTuple.shortSingleLineContentDescription)"
290290
}
291291
}
292292

Tests/SwiftParserTest/AvailabilityTests.swift

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -142,19 +142,21 @@ final class AvailabilityTests: XCTestCase {
142142
)
143143
)
144144

145+
assertParse(
146+
"""
147+
@available(OSX 10.0.1, *)
148+
func test() {}
149+
"""
150+
)
151+
145152
assertParse(
146153
"""
147154
@available(OSX 1️⃣10e10)
148155
func test() {}
149156
""",
150157
diagnostics: [
151-
DiagnosticSpec(message: "expected version tuple in version restriction", fixIts: ["insert version tuple"]),
152-
DiagnosticSpec(message: "unexpected code '10e10' in attribute"),
153-
],
154-
fixedSource: """
155-
@available(OSX <#integer literal#>10e10)
156-
func test() {}
157-
"""
158+
DiagnosticSpec(message: "cannot parse version component code '10e10'")
159+
]
158160
)
159161

160162
assertParse(
@@ -163,13 +165,8 @@ final class AvailabilityTests: XCTestCase {
163165
func test() {}
164166
""",
165167
diagnostics: [
166-
DiagnosticSpec(message: "expected integer literal in version tuple", fixIts: ["insert integer literal"]),
167-
DiagnosticSpec(message: "unexpected code '0e10' in attribute"),
168-
],
169-
fixedSource: """
170-
@available(OSX 10.<#integer literal#>0e10)
171-
func test() {}
172-
"""
168+
DiagnosticSpec(message: "cannot parse version component code '0e10'")
169+
]
173170
)
174171

175172
assertParse(
@@ -178,13 +175,8 @@ final class AvailabilityTests: XCTestCase {
178175
func test() {}
179176
""",
180177
diagnostics: [
181-
DiagnosticSpec(message: "expected version tuple in version restriction", fixIts: ["insert version tuple"]),
182-
DiagnosticSpec(message: "unexpected code '0xff' in attribute"),
183-
],
184-
fixedSource: """
185-
@available(OSX <#integer literal#>0xff)
186-
func test() {}
187-
"""
178+
DiagnosticSpec(message: "cannot parse version component code '0xff'")
179+
]
188180
)
189181

190182
assertParse(
@@ -193,13 +185,28 @@ final class AvailabilityTests: XCTestCase {
193185
func test() {}
194186
""",
195187
diagnostics: [
196-
DiagnosticSpec(message: "expected integer literal in version tuple", fixIts: ["insert integer literal"]),
197-
DiagnosticSpec(message: "unexpected code '0xff' in attribute"),
198-
],
199-
fixedSource: """
200-
@available(OSX 1.0.<#integer literal#>0xff)
201-
func test() {}
202-
"""
188+
DiagnosticSpec(message: "cannot parse version component code '0xff'")
189+
]
190+
)
191+
192+
assertParse(
193+
"""
194+
@available(OSX 1.0.1️⃣0xff, *)
195+
func test() {}
196+
""",
197+
diagnostics: [
198+
DiagnosticSpec(message: "cannot parse version component code '0xff'")
199+
]
200+
)
201+
202+
assertParse(
203+
"""
204+
@available(OSX 1.0.1️⃣asdf)
205+
func test() {}
206+
""",
207+
diagnostics: [
208+
DiagnosticSpec(message: "cannot parse version component code 'asdf'")
209+
]
203210
)
204211
}
205212
}

Tests/SwiftParserTest/translated/IfconfigExprTests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ final class IfconfigExprTests: XCTestCase {
388388
#endif
389389
"""#,
390390
diagnostics: [
391-
DiagnosticSpec(message: "cannot parse version \"\"")
391+
DiagnosticSpec(message: #"cannot parse version component code '""'"#)
392392
]
393393
)
394394
}
@@ -401,7 +401,7 @@ final class IfconfigExprTests: XCTestCase {
401401
#endif
402402
""",
403403
diagnostics: [
404-
DiagnosticSpec(message: "cannot parse version >=2.2")
404+
DiagnosticSpec(message: "cannot parse version component code '>=2.2'")
405405
]
406406
)
407407
}
@@ -414,7 +414,7 @@ final class IfconfigExprTests: XCTestCase {
414414
#endif
415415
""",
416416
diagnostics: [
417-
DiagnosticSpec(message: "cannot parse version 20A301")
417+
DiagnosticSpec(message: "cannot parse version component code '20A301'")
418418
]
419419
)
420420
}
@@ -427,7 +427,7 @@ final class IfconfigExprTests: XCTestCase {
427427
#endif
428428
"""#,
429429
diagnostics: [
430-
DiagnosticSpec(message: "cannot parse version \"20A301\"")
430+
DiagnosticSpec(message: #"cannot parse version component code '"20A301"'"#)
431431
]
432432
)
433433
}

0 commit comments

Comments
 (0)