Skip to content

Commit bd2d1ee

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 bd2d1ee

File tree

6 files changed

+238
-8
lines changed

6 files changed

+238
-8
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,20 @@ 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+
// TODO: Recovery for different numbers of dots (which also needs to be
322+
// done for regular variadics).
323+
guard self.at(anyIn: Operator.self) != nil else { return nil }
324+
let text = self.currentToken.tokenText
325+
guard text.hasPrefix("...") else { return nil }
326+
return self.consumePrefix(
327+
SyntaxText(rebasing: text.prefix(3)), as: .ellipsis)
328+
}
329+
316330
@_spi(RawSyntax)
317331
public mutating func parseGenericParameters() -> RawGenericParameterClauseSyntax {
318332
assert(self.currentToken.starts(with: "<"))
@@ -330,6 +344,9 @@ extension Parser {
330344
break
331345
}
332346

347+
// Parse the ellipsis for a type parameter pack 'T...'.
348+
let ellipsis = tryConsumeEllipsisPrefix()
349+
333350
// Parse the ':' followed by a type.
334351
let colon = self.consume(if: .colon)
335352
let inherited: RawTypeSyntax?
@@ -347,6 +364,7 @@ extension Parser {
347364
attributes: attributes,
348365
unexpectedBeforeName,
349366
name: name,
367+
ellipsis: ellipsis,
350368
colon: colon,
351369
inheritedType: inherited,
352370
trailingComma: keepGoing,
@@ -1102,6 +1120,13 @@ extension Parser {
11021120
arena: self.arena)
11031121
}
11041122

1123+
// Detect an attempt to use a type parameter pack.
1124+
var unexpectedAfterName: RawUnexpectedNodesSyntax?
1125+
if let ellipsis = tryConsumeEllipsisPrefix() {
1126+
unexpectedAfterName = RawUnexpectedNodesSyntax(
1127+
elements: [RawSyntax(ellipsis)], arena: self.arena)
1128+
}
1129+
11051130
// Parse optional inheritance clause.
11061131
let inheritance: RawTypeInheritanceClauseSyntax?
11071132
if self.at(.colon) {
@@ -1137,6 +1162,7 @@ extension Parser {
11371162
associatedtypeKeyword: assocKeyword,
11381163
unexpectedBeforeName,
11391164
identifier: name,
1165+
unexpectedAfterName,
11401166
inheritanceClause: inheritance,
11411167
initializer: defaultType,
11421168
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 StaticParserError.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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ public enum StaticParserError: String, DiagnosticMessage {
8585
case tryMustBePlacedOnThrownExpr = "'try' must be placed on the thrown expression"
8686
case tryOnInitialValueExpression = "'try' must be placed on the initial value expression"
8787
case unexpectedSemicolon = "unexpected ';' separator"
88+
case associatedTypeCannotUsePack = "associated types cannot be variadic"
8889

8990
public var message: String { self.rawValue }
9091

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: 169 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,24 +169,187 @@ final class TypeTests: XCTestCase {
169169
}
170170
""")
171171
}
172+
}
172173

173-
func testPackExpansion() throws {
174+
final class TypeParameterPackTests: XCTestCase {
175+
func testParameterPacks1() throws {
174176
AssertParse(
175177
"""
176-
func f1<@_typeSequence T>() -> T... {}
177-
func f2<@_typeSequence T>() -> (T...) {}
178-
func f3<@_typeSequence T>() -> G<T... > {}
178+
func f1<T...>() -> T... {}
179179
""")
180-
180+
}
181+
func testParameterPacks2() throws {
182+
AssertParse(
183+
"""
184+
func f2<T...>() -> (T...) {}
185+
""")
186+
}
187+
func testParameterPacks3() throws {
188+
AssertParse(
189+
"""
190+
func f3<T...>() -> G<T... > {}
191+
""")
192+
}
193+
func testParameterPacks4() throws {
194+
AssertParse(
195+
"""
196+
protocol P {
197+
associatedtype T1️⃣...
198+
}
199+
""",
200+
diagnostics: [
201+
DiagnosticSpec(message: "associated types cannot be variadic",
202+
fixIts: ["remove '...'"])
203+
])
204+
}
205+
func testParameterPacks5() throws {
206+
AssertParse(
207+
"""
208+
typealias Alias<T...> = (T...)
209+
""")
210+
}
211+
func testParameterPacks6() throws {
212+
AssertParse(
213+
"""
214+
struct S<T...> {}
215+
""")
216+
}
217+
func testParameterPacks7() throws {
218+
AssertParse(
219+
"""
220+
struct S<T, U...> {}
221+
""")
222+
}
223+
func testParameterPacks8() throws {
224+
AssertParse(
225+
"""
226+
struct S<T..., U> {}
227+
""")
228+
}
229+
func testParameterPacks9() throws {
230+
AssertParse(
231+
"""
232+
struct S<T...:P, U> {}
233+
""")
234+
}
235+
func testParameterPacks10() throws {
236+
AssertParse(
237+
"""
238+
struct S<T... :P, U> {}
239+
""")
240+
}
241+
func testParameterPacks11() throws {
242+
AssertParse(
243+
"""
244+
struct S<T...: P> {}
245+
""")
246+
}
247+
func testParameterPacks12() throws {
248+
AssertParse(
249+
"""
250+
struct S<T... : P> {}
251+
""")
252+
}
253+
func testParameterPacks13() throws {
254+
AssertParse(
255+
"""
256+
func foo<T...>(_ x: T...) {}
257+
""")
258+
}
259+
func testParameterPacks14() throws {
260+
AssertParse(
261+
"""
262+
func foo<T...:P>(_ x: T...) {}
263+
""")
264+
}
265+
func testParameterPacks15() throws {
181266
AssertParse(
182267
"""
183-
enum E<@_typeSequence T> {
268+
func foo<T... :P>(_ x: T...) {}
269+
""")
270+
}
271+
func testParameterPacks16() throws {
272+
AssertParse(
273+
"""
274+
func foo<T... : P>(_ x: T...) {}
275+
""")
276+
}
277+
func testParameterPacks17() throws {
278+
AssertParse(
279+
"""
280+
func foo<T...: P>(_ x: T...) {}
281+
""")
282+
}
283+
func testParameterPacks18() throws {
284+
AssertParse(
285+
"""
286+
func foo<T, U, V...>(x: T, y: U, z: V...) { }
287+
""")
288+
}
289+
func testParameterPacks19() throws {
290+
AssertParse(
291+
"""
292+
func foo<T, U..., V>(x: T, y: U..., z: V) { }
293+
""")
294+
}
295+
func testParameterPacks20() throws {
296+
AssertParse(
297+
"""
298+
func foo<T..., U..., V...>(x: T..., y: U..., z: V...) { }
299+
""")
300+
}
301+
func testParameterPacks21() throws {
302+
AssertParse(
303+
"""
304+
enum E<T...> {
184305
case f1(_: T...)
306+
}
307+
""")
308+
}
309+
func testParameterPacks22() throws {
310+
AssertParse(
311+
"""
312+
enum E<T...> {
185313
case f2(_: G<T... >)
314+
}
315+
""")
316+
}
317+
func testParameterPacks23() throws {
318+
AssertParse(
319+
"""
320+
enum E<T...> {
186321
var x: T... { fatalError() }
322+
}
323+
""")
324+
}
325+
func testParameterPacks24() throws {
326+
AssertParse(
327+
"""
328+
enum E<T...> {
187329
var x: (T...) { fatalError() }
330+
}
331+
""")
332+
}
333+
func testParameterPacks25() throws {
334+
AssertParse(
335+
"""
336+
enum E<T...> {
188337
subscript(_: T...) -> Int { fatalError() }
338+
}
339+
""")
340+
}
341+
func testParameterPacks26() throws {
342+
AssertParse(
343+
"""
344+
enum E<T...> {
189345
subscript() -> T... { fatalError() }
346+
}
347+
""")
348+
}
349+
func testParameterPacks27() throws {
350+
AssertParse(
351+
"""
352+
enum E<T...> {
190353
subscript() -> (T...) { fatalError() }
191354
}
192355
""")

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)