Skip to content

Commit a344469

Browse files
committed
[Variadic Generics] add diagnostics and fixits for parameter pack syntax change: T... -> each T
1 parent 183aa3e commit a344469

File tree

5 files changed

+118
-5
lines changed

5 files changed

+118
-5
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -474,13 +474,24 @@ extension Parser {
474474
let attributes = self.parseAttributeList()
475475

476476
// Parse the 'each' keyword for a type parameter pack 'each T'.
477-
let each = self.consume(if: .keyword(.each))
477+
var each = self.consume(if: .keyword(.each))
478478

479479
let (unexpectedBetweenEachAndName, name) = self.expectIdentifier()
480-
if attributes == nil && unexpectedBetweenEachAndName == nil && name.isMissing && elements.isEmpty {
480+
if attributes == nil && each == nil && unexpectedBetweenEachAndName == nil && name.isMissing && elements.isEmpty {
481481
break
482482
}
483483

484+
// Parse the unsupported ellipsis for a type parameter pack 'T...'.
485+
let unexpectedBetweenNameAndColon: RawUnexpectedNodesSyntax?
486+
if let ellipsis = tryConsumeEllipsisPrefix() {
487+
unexpectedBetweenNameAndColon = RawUnexpectedNodesSyntax([ellipsis], arena: self.arena)
488+
if each == nil {
489+
each = missingToken(.each)
490+
}
491+
} else {
492+
unexpectedBetweenNameAndColon = nil
493+
}
494+
484495
// Parse the ':' followed by a type.
485496
let colon = self.consume(if: .colon)
486497
let unexpectedBeforeInherited: RawUnexpectedNodesSyntax?
@@ -513,6 +524,7 @@ extension Parser {
513524
each: each,
514525
unexpectedBetweenEachAndName,
515526
name: name,
527+
unexpectedBetweenNameAndColon,
516528
colon: colon,
517529
unexpectedBeforeInherited,
518530
inheritedType: inherited,
@@ -937,7 +949,15 @@ extension Parser {
937949
_ handle: RecoveryConsumptionHandle
938950
) -> RawAssociatedtypeDeclSyntax {
939951
let (unexpectedBeforeAssocKeyword, assocKeyword) = self.eat(handle)
940-
let (unexpectedBeforeName, name) = self.expectIdentifier(keywordRecovery: true)
952+
953+
// Detect an attempt to use a type parameter pack.
954+
let eachKeyword = self.consume(if: .keyword(.each))
955+
956+
var (unexpectedBeforeName, name) = self.expectIdentifier(keywordRecovery: true)
957+
if eachKeyword != nil {
958+
unexpectedBeforeName = RawUnexpectedNodesSyntax(combining: eachKeyword, unexpectedBeforeName, arena: self.arena)
959+
}
960+
941961
if unexpectedBeforeName == nil && name.isMissing {
942962
return RawAssociatedtypeDeclSyntax(
943963
attributes: attrs.attributes,
@@ -953,7 +973,7 @@ extension Parser {
953973
)
954974
}
955975

956-
// Detect an attempt to use a type parameter pack.
976+
// Detect an attempt to use (early syntax) type parameter pack.
957977
let ellipsis = tryConsumeEllipsisPrefix()
958978

959979
// Parse optional inheritance clause.

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,13 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
389389
if shouldSkip(node) {
390390
return .skipChildren
391391
}
392+
// Emit a custom diagnostic for an unexpected 'each' before an associatedtype
393+
// name.
394+
removeToken(
395+
node.unexpectedBetweenAssociatedtypeKeywordAndIdentifier,
396+
where: { $0.tokenKind == .keyword(.each) },
397+
message: { _ in .associatedTypeCannotUsePack }
398+
)
392399
// Emit a custom diagnostic for an unexpected '...' after an associatedtype
393400
// name.
394401
removeToken(
@@ -615,6 +622,30 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
615622
if shouldSkip(node) {
616623
return .skipChildren
617624
}
625+
// Emit a custom diagnostic for an unexpected '...' after the type name.
626+
if node.each?.presence == .present {
627+
removeToken(
628+
node.unexpectedBetweenNameAndColon,
629+
where: { $0.tokenKind == .ellipsis },
630+
message: { _ in .typeParameterPackEllipsis }
631+
)
632+
} else if let unexpected = node.unexpectedBetweenNameAndColon,
633+
let unexpectedEllipsis = unexpected.onlyToken(where: { $0.tokenKind == .ellipsis }) {
634+
addDiagnostic(
635+
unexpected,
636+
.typeParameterPackEllipsis,
637+
fixIts: [
638+
FixIt(
639+
message: ReplaceTokensFixIt(replaceTokens: [unexpectedEllipsis], replacement: .keyword(.each)),
640+
changes: [
641+
.makeMissing(unexpected),
642+
.makePresent(node.each!, trailingTrivia: .space)
643+
]
644+
)
645+
],
646+
handledNodes: [unexpected.id, node.each!.id]
647+
)
648+
}
618649
if let inheritedTypeName = node.inheritedType?.as(SimpleTypeIdentifierSyntax.self)?.name {
619650
exchangeTokens(
620651
unexpected: node.unexpectedBetweenColonAndInheritedType,

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ extension DiagnosticMessage where Self == StaticParserError {
197197
public static var tryOnInitialValueExpression: Self {
198198
.init("'try' must be placed on the initial value expression")
199199
}
200+
public static var typeParameterPackEllipsis: Self {
201+
.init("ellipsis operator cannot be used with a type parameter pack")
202+
}
200203
public static var unexpectedPoundElseSpaceIf: Self {
201204
.init("unexpected 'if' keyword following '#else' conditional compilation directive; did you mean '#elseif'?")
202205
}
@@ -504,7 +507,7 @@ public struct MoveTokensInFrontOfFixIt: ParserFixIt {
504507
/// The token that should be moved
505508
public let movedTokens: [TokenSyntax]
506509

507-
/// The token after which 'try' should be moved
510+
/// The token before which 'movedTokens' should be moved
508511
public let inFrontOf: TokenKind
509512

510513
public var message: String {

Tests/SwiftParserTest/VariadicGenericsTests.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,37 @@ final class VariadicGenericsTests: XCTestCase {
7474
)
7575
}
7676

77+
func testTypeParameterPackEllipsis() {
78+
AssertParse(
79+
"""
80+
func invalid<T1️⃣...>(_ t: repeat each T) {}
81+
""",
82+
diagnostics: [
83+
DiagnosticSpec(
84+
message: "ellipsis operator cannot be used with a type parameter pack",
85+
fixIts: ["replace '...' with 'each'"]
86+
)
87+
],
88+
fixedSource: """
89+
func invalid<each T>(_ t: repeat each T) {}
90+
"""
91+
)
92+
}
93+
94+
func testTypeParameterPackEachEllipsis() {
95+
AssertParse(
96+
"""
97+
func invalid<each T1️⃣...>(_ t: repeat each T) {}
98+
""",
99+
diagnostics: [
100+
DiagnosticSpec(
101+
message: "ellipsis operator cannot be used with a type parameter pack",
102+
fixIts: ["remove '...'"]
103+
)
104+
]
105+
)
106+
}
107+
77108
func testPackElementExprSimple() {
78109
AssertParse(
79110
"""
@@ -293,6 +324,21 @@ final class TypeParameterPackTests: XCTestCase {
293324
)
294325
}
295326
func testParameterPacks4() {
327+
AssertParse(
328+
"""
329+
protocol P {
330+
associatedtype 1️⃣each T
331+
}
332+
""",
333+
diagnostics: [
334+
DiagnosticSpec(
335+
message: "associated types cannot be variadic",
336+
fixIts: ["remove 'each'"]
337+
)
338+
]
339+
)
340+
}
341+
func testParameterPacks4EarlySyntax() {
296342
AssertParse(
297343
"""
298344
protocol P {

Tests/SwiftParserTest/translated/RecoveryTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,19 @@ final class RecoveryTests: XCTestCase {
10071007
)
10081008
}
10091009

1010+
func testRecovery87b() {
1011+
AssertParse(
1012+
"""
1013+
struct ErrorGenericParameterList1<each1️⃣
1014+
""",
1015+
diagnostics: [
1016+
DiagnosticSpec(message: "expected name in generic parameter"),
1017+
DiagnosticSpec(message: "expected '>' to end generic parameter clause"),
1018+
DiagnosticSpec(message: "expected member block in struct"),
1019+
]
1020+
)
1021+
}
1022+
10101023
func testRecovery88() {
10111024
AssertParse(
10121025
"""

0 commit comments

Comments
 (0)