Skip to content

Commit 28449d5

Browse files
committed
Parse an ellipsis T... for type parameter packs
In a generic parameter list, parse an ellipsis to produce a type parameter pack. This replaces the previous `@_typeSequence` attribute.
1 parent a09903d commit 28449d5

File tree

6 files changed

+111
-6
lines changed

6 files changed

+111
-6
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,18 @@ extension Parser {
313313
}
314314

315315
extension Parser {
316+
/// Attempt to consume an ellipsis prefix, splitting the current token if
317+
/// necessary.
318+
mutating func tryConsumeEllipsisPrefix() -> RawTokenSyntax? {
319+
// It is not sufficient to check currentToken.isEllipsis here, as we may
320+
// have something like '...>'.
321+
guard self.at(anyIn: Operator.self) != nil else { return nil }
322+
let text = self.currentToken.tokenText
323+
guard text.hasPrefix("...") else { return nil }
324+
return self.consumePrefix(
325+
SyntaxText(rebasing: text.prefix(3)), as: .ellipsis)
326+
}
327+
316328
@_spi(RawSyntax)
317329
public mutating func parseGenericParameters() -> RawGenericParameterClauseSyntax {
318330
assert(self.currentToken.starts(with: "<"))
@@ -330,6 +342,9 @@ extension Parser {
330342
break
331343
}
332344

345+
// Parse the ellipsis for a type parameter pack 'T...'.
346+
let ellipsis = tryConsumeEllipsisPrefix()
347+
333348
// Parse the ':' followed by a type.
334349
let colon = self.consume(if: .colon)
335350
let inherited: RawTypeSyntax?
@@ -347,6 +362,7 @@ extension Parser {
347362
attributes: attributes,
348363
unexpectedBeforeName,
349364
name: name,
365+
ellipsis: ellipsis,
350366
colon: colon,
351367
inheritedType: inherited,
352368
trailingComma: keepGoing,
@@ -1102,6 +1118,13 @@ extension Parser {
11021118
arena: self.arena)
11031119
}
11041120

1121+
// Detect an attempt to use a type parameter pack.
1122+
var unexpectedAfterName: RawUnexpectedNodesSyntax?
1123+
if let ellipsis = tryConsumeEllipsisPrefix() {
1124+
unexpectedAfterName = RawUnexpectedNodesSyntax(
1125+
elements: [RawSyntax(ellipsis)], arena: self.arena)
1126+
}
1127+
11051128
// Parse optional inheritance clause.
11061129
let inheritance: RawTypeInheritanceClauseSyntax?
11071130
if self.at(.colon) {
@@ -1137,6 +1160,7 @@ extension Parser {
11371160
associatedtypeKeyword: assocKeyword,
11381161
unexpectedBeforeName,
11391162
identifier: name,
1163+
unexpectedAfterName,
11401164
inheritanceClause: inheritance,
11411165
initializer: defaultType,
11421166
genericWhereClause: whereClause,

Sources/SwiftParser/Diagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,29 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
145145
addDiagnostic(incorrectContainer, message(misplacedTokens), fixIts: fixIts, handledNodes: [incorrectContainer.id] + correctAndMissingTokens.map(\.id))
146146
}
147147

148+
/// Utility function to remove a misplaced token with a custom error message.
149+
public func removeToken(
150+
_ unexpected: UnexpectedNodesSyntax?,
151+
where predicate: (TokenSyntax) -> Bool,
152+
message: (TokenSyntax) -> DiagnosticMessage
153+
) {
154+
guard let unexpected = unexpected,
155+
let misplacedToken = unexpected.onlyToken(where: predicate)
156+
else {
157+
// If there is no unexpected node or the unexpected doesn't have the
158+
// expected token, don't emit a diagnostic.
159+
return
160+
}
161+
let fixit = FixIt(
162+
message: RemoveNodesFixIt(unexpected),
163+
changes: [.remove(unexpected: unexpected)]
164+
)
165+
addDiagnostic(
166+
unexpected, message(misplacedToken), fixIts: [fixit],
167+
handledNodes: [unexpected.id]
168+
)
169+
}
170+
148171
// MARK: - Generic diagnostic generation
149172

150173
public override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
@@ -563,6 +586,20 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
563586
return .visitChildren
564587
}
565588

589+
public override func visit(_ node: AssociatedtypeDeclSyntax) -> SyntaxVisitorContinueKind {
590+
if shouldSkip(node) {
591+
return .skipChildren
592+
}
593+
// Emit a custom diagnostic for an unexpected '...' after an associatedtype
594+
// name.
595+
removeToken(
596+
node.unexpectedBetweenIdentifierAndInheritanceClause,
597+
where: { $0.tokenKind == .ellipsis },
598+
message: { _ in AssociatedTypeCannotUsePack() }
599+
)
600+
return .visitChildren
601+
}
602+
566603
public override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
567604
if shouldSkip(node) {
568605
return .skipChildren

Sources/SwiftParser/Diagnostics/ParserDiagnosticMessages.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ public enum StaticParserError: String, DiagnosticMessage {
9797

9898
// MARK: - Diagnostics (please sort alphabetically)
9999

100+
public struct AssociatedTypeCannotUsePack: ParserError {
101+
public var message: String {
102+
"associated types may not use type parameter packs"
103+
}
104+
}
105+
100106
public struct EffectsSpecifierAfterArrow: ParserError {
101107
public let effectsSpecifiersAfterArrow: [TokenSyntax]
102108

Sources/SwiftSyntax/SyntaxFactory.swift.gyb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,8 @@ public enum SyntaxFactory {
185185
@available(*, deprecated, message: "Use initializer on GenericParameterSyntax")
186186
public static func makeGenericParameter(name: TokenSyntax,
187187
trailingComma: TokenSyntax) -> GenericParameterSyntax {
188-
return makeGenericParameter(attributes: nil, name: name, colon: nil,
189-
inheritedType: nil,
188+
return makeGenericParameter(attributes: nil, name: name, ellipsis: nil,
189+
colon: nil, inheritedType: nil,
190190
trailingComma: trailingComma)
191191
}
192192

Tests/SwiftParserTest/Types.swift

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,14 +173,49 @@ final class TypeTests: XCTestCase {
173173
func testPackExpansion() throws {
174174
AssertParse(
175175
"""
176-
func f1<@_typeSequence T>() -> T... {}
177-
func f2<@_typeSequence T>() -> (T...) {}
178-
func f3<@_typeSequence T>() -> G<T... > {}
176+
func f1<T...>() -> T... {}
177+
func f2<T...>() -> (T...) {}
178+
func f3<T...>() -> G<T... > {}
179179
""")
180180

181181
AssertParse(
182182
"""
183-
enum E<@_typeSequence T> {
183+
protocol P {
184+
associatedtype T1️⃣...
185+
}
186+
""",
187+
diagnostics: [
188+
DiagnosticSpec(message: "associated types may not use type parameter packs",
189+
fixIts: ["remove '...'"])
190+
])
191+
192+
AssertParse(
193+
"""
194+
typealias Alias<T...> = (T...)
195+
196+
struct S1<T...> {}
197+
struct S2<T, U...> {}
198+
struct S3<T..., U> {}
199+
struct S4<T...:P, U> {}
200+
struct S5<T... :P, U> {}
201+
struct S6<T...: P> {}
202+
struct S7<T... : P> {}
203+
204+
func foo<T...>(_ x: T...) {}
205+
206+
func bar<T...:P>(_ x: T...) {}
207+
func baz<T... :P>(_ x: T...) {}
208+
func qux<T... : P>(_ x: T...) {}
209+
func quux<T...: P>(_ x: T...) {}
210+
211+
func foobar<T, U, V...>(x: T, y: U, z: V...) { }
212+
func foobaz<T, U..., V>(x: T, y: U..., z: V) { }
213+
func fooqux<T..., U..., V...>(x: T..., y: U..., z: V...) { }
214+
""")
215+
216+
AssertParse(
217+
"""
218+
enum E<T...> {
184219
case f1(_: T...)
185220
case f2(_: G<T... >)
186221
var x: T... { fatalError() }

gyb_syntax_support/GenericNodes.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@
7676
Child('Attributes', kind='AttributeList',
7777
collection_element_name='Attribute', is_optional=True),
7878
Child('Name', name_for_diagnostics='name', kind='IdentifierToken'),
79+
Child('Ellipsis',
80+
name_for_diagnostics='parameter pack specifier',
81+
kind='EllipsisToken', is_optional=True),
7982
Child('Colon', kind='ColonToken',
8083
is_optional=True),
8184
Child('InheritedType', name_for_diagnostics='inherited type', kind='Type',

0 commit comments

Comments
 (0)