Skip to content

Commit 45282e9

Browse files
committed
Recover and detect a missing comma in a tupel
1 parent b9f5805 commit 45282e9

File tree

3 files changed

+112
-9
lines changed

3 files changed

+112
-9
lines changed

Sources/SwiftParser/Parser.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ public struct Parser: TokenConsumer {
149149
}
150150

151151
@_spi(RawSyntax)
152-
public mutating func missingToken(_ kind: RawTokenKind, text: SyntaxText?) -> RawTokenSyntax {
152+
public mutating func missingToken(_ kind: RawTokenKind, text: SyntaxText? = nil) -> RawTokenSyntax {
153153
return RawTokenSyntax(missing: kind, text: text, arena: self.arena)
154154
}
155155

@@ -327,7 +327,7 @@ extension Parser {
327327
if let remapping = remapping {
328328
return $0.missingToken(remapping, text: kind.defaultText)
329329
} else {
330-
return $0.missingToken(kind, text: nil)
330+
return $0.missingToken(kind)
331331
}
332332
}
333333
)
@@ -366,7 +366,7 @@ extension Parser {
366366
return expectImpl(
367367
consume: { $0.consume(ifAny: kinds, contextualKeywords: contextualKeywords) },
368368
canRecoverTo: { $0.canRecoverTo(kinds, contextualKeywords: contextualKeywords) },
369-
makeMissing: { $0.missingToken(defaultKind, text: nil) }
369+
makeMissing: { $0.missingToken(defaultKind) }
370370
)
371371
}
372372

@@ -390,7 +390,7 @@ extension Parser {
390390
if let unknown = self.consume(if: .unknown) {
391391
return (
392392
RawUnexpectedNodesSyntax(elements: [RawSyntax(unknown)], arena: self.arena),
393-
self.missingToken(.identifier, text: nil)
393+
self.missingToken(.identifier)
394394
)
395395
}
396396
if let number = self.consume(ifAny: [.integerLiteral, .floatingLiteral]) {
@@ -407,7 +407,7 @@ extension Parser {
407407
}
408408
return (
409409
nil,
410-
self.missingToken(.identifier, text: nil)
410+
self.missingToken(.identifier)
411411
)
412412
}
413413

@@ -419,12 +419,12 @@ extension Parser {
419419
if let unknown = self.consume(if: .unknown) {
420420
return (
421421
RawUnexpectedNodesSyntax(elements: [RawSyntax(unknown)], arena: self.arena),
422-
self.missingToken(.identifier, text: nil)
422+
self.missingToken(.identifier)
423423
)
424424
}
425425
return (
426426
nil,
427-
self.missingToken(.identifier, text: nil)
427+
self.missingToken(.identifier)
428428
)
429429
}
430430

Sources/SwiftParser/Types.swift

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,10 +426,36 @@ extension Parser {
426426
unexpectedBeforeColon = nil
427427
colon = nil
428428
}
429+
430+
// In the case that the input is "(foo bar)" we have to decide whether we parse it as "(foo: bar)" or "(foo, bar)".
431+
// As most people write identifiers lowercase and types capitalized, we decide on the first character of the first token
432+
if let first = first,
433+
second == nil,
434+
colon?.isMissing == true,
435+
first.tokenText.description.first?.isUppercase == true {
436+
elements.append(RawTupleTypeElementSyntax(
437+
inOut: nil,
438+
name: nil,
439+
secondName: nil,
440+
unexpectedBeforeColon,
441+
colon: nil,
442+
type: RawTypeSyntax(RawSimpleTypeIdentifierSyntax(name: first, genericArgumentClause: nil, arena: self.arena)),
443+
ellipsis: nil,
444+
initializer: nil,
445+
trailingComma: self.missingToken(.comma),
446+
arena: self.arena
447+
))
448+
keepGoing = true
449+
continue
450+
}
429451
// Parse the type annotation.
430452
let type = self.parseType(misplacedSpecifiers: misplacedSpecifiers)
431453
let ellipsis = self.currentToken.isEllipsis ? self.consumeAnyToken() : nil
432-
let trailingComma = self.consume(if: .comma)
454+
var trailingComma = self.consume(if: .comma)
455+
if trailingComma == nil && self.withLookahead({ $0.canParseType() }) {
456+
// If the next token does not close the tuple, it is very likely the user forgot the comma.
457+
trailingComma = self.missingToken(.comma)
458+
}
433459
keepGoing = trailingComma != nil
434460
elements.append(RawTupleTypeElementSyntax(
435461
inOut: nil,

Tests/SwiftParserTest/Types.swift

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,10 +357,87 @@ final class TypeParameterPackTests: XCTestCase {
357357
func testParameterPacks28() throws {
358358
// We allow whitespace between the generic parameter and the '...', this is
359359
// consistent with regular variadic parameters.
360+
AssertParse(
361+
"""
362+
func f1<T ...>(_ x: T ...) -> (T ...) {}
363+
""")
364+
}
365+
366+
func testMissingCommaInType() throws {
360367
AssertParse(
361368
"""
362-
func f1<T ...>(_ x: T ...) -> (T ...) {}
369+
var foo: (Int)
363370
""")
371+
372+
AssertParse(
373+
"""
374+
var foo: (Int, Int)
375+
""")
376+
377+
AssertParse(
378+
"""
379+
var foo: (bar: Int 1️⃣bar2: Int)
380+
""",
381+
diagnostics: [
382+
DiagnosticSpec(message: "expected ',' in tuple type")
383+
])
384+
385+
AssertParse(
386+
"""
387+
var foo: (bar: Int 1️⃣Int)
388+
""",
389+
diagnostics: [
390+
DiagnosticSpec(message: "expected ',' in tuple type")
391+
])
392+
393+
AssertParse(
394+
"""
395+
var foo: (a 1️⃣Int)
396+
""",
397+
diagnostics: [
398+
DiagnosticSpec(message: "expected ':' in tuple type")
399+
])
400+
401+
AssertParse(
402+
"""
403+
var foo: (A 1️⃣Int)
404+
""",
405+
diagnostics: [
406+
DiagnosticSpec(message: "expected ',' in tuple type")
407+
])
408+
409+
AssertParse(
410+
"""
411+
var foo: (_ 1️⃣a 2️⃣Int)
412+
""",
413+
diagnostics: [
414+
DiagnosticSpec(locationMarker: "1️⃣", message: "expected ':' in tuple type"),
415+
DiagnosticSpec(locationMarker: "2️⃣", message: "expected ',' in tuple type")
416+
])
417+
418+
AssertParse(
419+
"""
420+
var foo: (Array<Foo> 1️⃣Array<Bar>)
421+
""",
422+
diagnostics: [
423+
DiagnosticSpec(message: "expected ',' in tuple type"),
424+
])
425+
426+
AssertParse(
427+
"""
428+
var foo: (a 1️⃣Array<Bar>)
429+
""",
430+
diagnostics: [
431+
DiagnosticSpec(message: "expected ':' in tuple type"),
432+
])
433+
434+
AssertParse(
435+
"""
436+
var foo: (Array<Foo> 1️⃣a)
437+
""",
438+
diagnostics: [
439+
DiagnosticSpec(message: "expected ',' in tuple type"),
440+
])
364441
}
365442
}
366443

0 commit comments

Comments
 (0)