Skip to content

[Variadic Generics] diagnostics + fixits for parameter pack syntax change: T... -> each T #1393

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions Sources/SwiftParser/Declarations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -474,13 +474,24 @@ extension Parser {
let attributes = self.parseAttributeList()

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

let (unexpectedBetweenEachAndName, name) = self.expectIdentifier()
if attributes == nil && unexpectedBetweenEachAndName == nil && name.isMissing && elements.isEmpty {
if attributes == nil && each == nil && unexpectedBetweenEachAndName == nil && name.isMissing && elements.isEmpty {
break
}

// Parse the unsupported ellipsis for a type parameter pack 'T...'.
let unexpectedBetweenNameAndColon: RawUnexpectedNodesSyntax?
if let ellipsis = tryConsumeEllipsisPrefix() {
unexpectedBetweenNameAndColon = RawUnexpectedNodesSyntax([ellipsis], arena: self.arena)
if each == nil {
each = missingToken(.each)
}
} else {
unexpectedBetweenNameAndColon = nil
}

// Parse the ':' followed by a type.
let colon = self.consume(if: .colon)
let unexpectedBeforeInherited: RawUnexpectedNodesSyntax?
Expand Down Expand Up @@ -513,6 +524,7 @@ extension Parser {
each: each,
unexpectedBetweenEachAndName,
name: name,
unexpectedBetweenNameAndColon,
colon: colon,
unexpectedBeforeInherited,
inheritedType: inherited,
Expand Down Expand Up @@ -937,7 +949,15 @@ extension Parser {
_ handle: RecoveryConsumptionHandle
) -> RawAssociatedtypeDeclSyntax {
let (unexpectedBeforeAssocKeyword, assocKeyword) = self.eat(handle)
let (unexpectedBeforeName, name) = self.expectIdentifier(keywordRecovery: true)

// Detect an attempt to use a type parameter pack.
let eachKeyword = self.consume(if: .keyword(.each))

var (unexpectedBeforeName, name) = self.expectIdentifier(keywordRecovery: true)
if eachKeyword != nil {
unexpectedBeforeName = RawUnexpectedNodesSyntax(combining: eachKeyword, unexpectedBeforeName, arena: self.arena)
}

if unexpectedBeforeName == nil && name.isMissing {
return RawAssociatedtypeDeclSyntax(
attributes: attrs.attributes,
Expand All @@ -953,7 +973,7 @@ extension Parser {
)
}

// Detect an attempt to use a type parameter pack.
// Detect an attempt to use (early syntax) type parameter pack.
let ellipsis = tryConsumeEllipsisPrefix()

// Parse optional inheritance clause.
Expand Down
33 changes: 33 additions & 0 deletions Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,13 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
if shouldSkip(node) {
return .skipChildren
}
// Emit a custom diagnostic for an unexpected 'each' before an associatedtype
// name.
removeToken(
node.unexpectedBetweenAssociatedtypeKeywordAndIdentifier,
where: { $0.tokenKind == .keyword(.each) },
message: { _ in .associatedTypeCannotUsePack }
)
// Emit a custom diagnostic for an unexpected '...' after an associatedtype
// name.
removeToken(
Expand Down Expand Up @@ -615,6 +622,32 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
if shouldSkip(node) {
return .skipChildren
}
// Emit a custom diagnostic for an unexpected '...' after the type name.
if node.each?.presence == .present {
removeToken(
node.unexpectedBetweenNameAndColon,
where: { $0.tokenKind == .ellipsis },
message: { _ in .typeParameterPackEllipsis }
)
} else if let unexpected = node.unexpectedBetweenNameAndColon,
let unexpectedEllipsis = unexpected.onlyToken(where: { $0.tokenKind == .ellipsis }),
let each = node.each
{
addDiagnostic(
unexpected,
.typeParameterPackEllipsis,
fixIts: [
FixIt(
message: ReplaceTokensFixIt(replaceTokens: [unexpectedEllipsis], replacement: .keyword(.each)),
changes: [
.makeMissing(unexpected),
.makePresent(each, trailingTrivia: .space),
]
)
],
handledNodes: [unexpected.id, each.id]
)
}
if let inheritedTypeName = node.inheritedType?.as(SimpleTypeIdentifierSyntax.self)?.name {
exchangeTokens(
unexpected: node.unexpectedBetweenColonAndInheritedType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ extension DiagnosticMessage where Self == StaticParserError {
public static var tryOnInitialValueExpression: Self {
.init("'try' must be placed on the initial value expression")
}
public static var typeParameterPackEllipsis: Self {
.init("ellipsis operator cannot be used with a type parameter pack")
}
public static var unexpectedPoundElseSpaceIf: Self {
.init("unexpected 'if' keyword following '#else' conditional compilation directive; did you mean '#elseif'?")
}
Expand Down Expand Up @@ -504,7 +507,7 @@ public struct MoveTokensInFrontOfFixIt: ParserFixIt {
/// The token that should be moved
public let movedTokens: [TokenSyntax]

/// The token after which 'try' should be moved
/// The token before which 'movedTokens' should be moved
public let inFrontOf: TokenKind

public var message: String {
Expand Down
46 changes: 46 additions & 0 deletions Tests/SwiftParserTest/VariadicGenericsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,37 @@ final class VariadicGenericsTests: XCTestCase {
)
}

func testTypeParameterPackEllipsis() {
AssertParse(
"""
func invalid<T1️⃣...>(_ t: repeat each T) {}
""",
diagnostics: [
DiagnosticSpec(
message: "ellipsis operator cannot be used with a type parameter pack",
fixIts: ["replace '...' with 'each'"]
)
],
fixedSource: """
func invalid<each T>(_ t: repeat each T) {}
"""
)
}

func testTypeParameterPackEachEllipsis() {
AssertParse(
"""
func invalid<each T1️⃣...>(_ t: repeat each T) {}
""",
diagnostics: [
DiagnosticSpec(
message: "ellipsis operator cannot be used with a type parameter pack",
fixIts: ["remove '...'"]
)
]
)
}

func testPackElementExprSimple() {
AssertParse(
"""
Expand Down Expand Up @@ -293,6 +324,21 @@ final class TypeParameterPackTests: XCTestCase {
)
}
func testParameterPacks4() {
AssertParse(
"""
protocol P {
associatedtype 1️⃣each T
}
""",
diagnostics: [
DiagnosticSpec(
message: "associated types cannot be variadic",
fixIts: ["remove 'each'"]
)
]
)
}
func testParameterPacks4EarlySyntax() {
AssertParse(
"""
protocol P {
Expand Down
13 changes: 13 additions & 0 deletions Tests/SwiftParserTest/translated/RecoveryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,19 @@ final class RecoveryTests: XCTestCase {
)
}

func testRecovery87b() {
AssertParse(
"""
struct ErrorGenericParameterList1<each1️⃣
""",
diagnostics: [
DiagnosticSpec(message: "expected name in generic parameter"),
DiagnosticSpec(message: "expected '>' to end generic parameter clause"),
DiagnosticSpec(message: "expected member block in struct"),
]
)
}

func testRecovery88() {
AssertParse(
"""
Expand Down