Skip to content

Commit ab2a4ad

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

File tree

4 files changed

+119
-39
lines changed

4 files changed

+119
-39
lines changed

Sources/SwiftParser/Availability.swift

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,28 @@ 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(unexpectedTokens: [RawSyntax] = []) -> RawUnexpectedNodesSyntax? {
258+
var copiedUnexpectedTokens = unexpectedTokens
259+
var keepGoing: RawTokenSyntax? = nil
260+
var loopProgress = LoopProgressCondition()
261+
repeat {
262+
if let keepGoing {
263+
copiedUnexpectedTokens.append(RawSyntax(keepGoing))
264+
}
265+
if let (_, handle) = self.at(anyIn: VersionTupleWhiteList.self) {
266+
let versionToken = self.eat(handle)
267+
copiedUnexpectedTokens.append(RawSyntax(versionToken))
268+
}
269+
keepGoing = self.consume(if: .period)
270+
} while keepGoing != nil && loopProgress.evaluate(currentToken)
271+
272+
guard !copiedUnexpectedTokens.isEmpty else {
273+
return nil
274+
}
275+
return RawUnexpectedNodesSyntax(elements: copiedUnexpectedTokens, arena: self.arena)
276+
}
277+
256278
/// Parse a dot-separated list of version numbers.
257279
///
258280
/// Grammar
@@ -286,24 +308,28 @@ extension Parser {
286308
} else {
287309
trailingComponents.append(versionComponent)
288310
}
289-
}
290311

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

317+
let unexpectedAfterComponents = self.parseUnexpectedVersionTokens(unexpectedTokens: trailingComponents.map(RawSyntax.init))
297318
return RawVersionTupleSyntax(
298319
major: major,
299320
components: RawVersionComponentListSyntax(elements: components, arena: self.arena),
300-
unexpectedTrailingComponents,
321+
unexpectedAfterComponents,
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/SwiftParser/TokenSpecSet.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,3 +901,28 @@ enum EffectSpecifiers: TokenSpecSet {
901901
}
902902
}
903903
}
904+
905+
enum VersionTupleWhiteList: TokenSpecSet {
906+
case integerLiteral
907+
case floatingLiteral
908+
909+
init?(lexeme: Lexer.Lexeme) {
910+
switch PrepareForKeywordMatch(lexeme) {
911+
case TokenSpec(.integerLiteral):
912+
self = .integerLiteral
913+
case TokenSpec(.floatingLiteral):
914+
self = .floatingLiteral
915+
default:
916+
return nil
917+
}
918+
}
919+
920+
var spec: TokenSpec {
921+
switch self {
922+
case .integerLiteral:
923+
return .integerLiteral
924+
case .floatingLiteral:
925+
return .floatingLiteral
926+
}
927+
}
928+
}

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1893,14 +1893,31 @@ 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+
var handledNodes: [SyntaxIdentifier] = [unexpectedAfterComponents.id]
1907+
if node.major.hasError {
1908+
handledNodes.append(node.major.id)
1909+
}
1910+
if let components = node.components,
1911+
components.hasError
1912+
{
1913+
handledNodes.append(components.id)
1914+
}
1915+
addDiagnostic(
1916+
unexpectedAfterComponents,
1917+
CannotParseVersionTuple(versionTuple: unexpectedAfterComponents),
1918+
handledNodes: handledNodes
1919+
)
1920+
}
19041921
}
19051922

19061923
return .visitChildren

Tests/SwiftParserTest/AvailabilityTests.swift

Lines changed: 35 additions & 23 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 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 0e10")
169+
]
173170
)
174171

175172
assertParse(
@@ -178,26 +175,41 @@ 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 0xff")
179+
]
188180
)
189181

190182
assertParse(
191183
"""
192184
@available(OSX 1.0.1️⃣0xff)
193185
func test() {}
194186
""",
187+
diagnostics: [
188+
DiagnosticSpec(message: "cannot parse version 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 0xff")
199+
]
200+
)
201+
202+
assertParse(
203+
"""
204+
@available(OSX 1.0.1️⃣asdf)
205+
func test() {}
206+
""",
195207
diagnostics: [
196208
DiagnosticSpec(message: "expected integer literal in version tuple", fixIts: ["insert integer literal"]),
197-
DiagnosticSpec(message: "unexpected code '0xff' in attribute"),
209+
DiagnosticSpec(message: "unexpected code 'asdf' in attribute"),
198210
],
199211
fixedSource: """
200-
@available(OSX 1.0.<#integer literal#>0xff)
212+
@available(OSX 1.0.<#integer literal#>asdf)
201213
func test() {}
202214
"""
203215
)

0 commit comments

Comments
 (0)