Skip to content

Commit f5cffeb

Browse files
committed
Diagnose if class is being used as an inherited type in a generic parameter clause
1 parent a09125e commit f5cffeb

File tree

4 files changed

+37
-10
lines changed

4 files changed

+37
-10
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,14 +349,25 @@ extension Parser {
349349

350350
// Parse the ':' followed by a type.
351351
let colon = self.consume(if: .colon)
352+
let unexpectedBeforeInherited: RawUnexpectedNodesSyntax?
352353
let inherited: RawTypeSyntax?
353354
if colon != nil {
354355
if self.at(any: [.identifier, .protocolKeyword, .anyKeyword]) {
356+
unexpectedBeforeInherited = nil
355357
inherited = self.parseType()
358+
} else if let classKeyword = self.consume(if: .classKeyword) {
359+
unexpectedBeforeInherited = RawUnexpectedNodesSyntax([classKeyword], arena: self.arena)
360+
inherited = RawTypeSyntax(RawSimpleTypeIdentifierSyntax(
361+
name: missingToken(.identifier, text: "AnyObject"),
362+
genericArgumentClause: nil,
363+
arena: self.arena
364+
))
356365
} else {
357-
inherited = nil
366+
unexpectedBeforeInherited = nil
367+
inherited = RawTypeSyntax(RawMissingTypeSyntax(arena: self.arena))
358368
}
359369
} else {
370+
unexpectedBeforeInherited = nil
360371
inherited = nil
361372
}
362373
keepGoing = self.consume(if: .comma)
@@ -366,6 +377,7 @@ extension Parser {
366377
name: name,
367378
ellipsis: ellipsis,
368379
colon: colon,
380+
unexpectedBeforeInherited,
369381
inheritedType: inherited,
370382
trailingComma: keepGoing,
371383
arena: self.arena))

Sources/SwiftParser/Diagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,22 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
491491
return .visitChildren
492492
}
493493

494+
public override func visit(_ node: GenericParameterSyntax) -> SyntaxVisitorContinueKind {
495+
if shouldSkip(node) {
496+
return .skipChildren
497+
}
498+
if let inheritedTypeName = node.inheritedType?.as(SimpleTypeIdentifierSyntax.self)?.name {
499+
exchangeTokens(
500+
unexpected: node.unexpectedBetweenColonAndInheritedType,
501+
unexpectedTokenCondition: { $0.tokenKind == .classKeyword },
502+
correctTokens: [inheritedTypeName],
503+
message: { _ in StaticParserError.classConstraintCanOnlyBeUsedInProtocol },
504+
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacement: inheritedTypeName) }
505+
)
506+
}
507+
return .visitChildren
508+
}
509+
494510
public override func visit(_ node: IdentifierExprSyntax) -> SyntaxVisitorContinueKind {
495511
if shouldSkip(node) {
496512
return .skipChildren

Sources/SwiftParser/Diagnostics/ParserDiagnosticMessages.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public extension ParserFixIt {
6868
public enum StaticParserError: String, DiagnosticMessage {
6969
case allStatmentsInSwitchMustBeCoveredByCase = "all statements inside a switch must be covered by a 'case' or 'default' label"
7070
case caseOutsideOfSwitchOrEnum = "'case' can only appear inside a 'switch' statement or 'enum' declaration"
71+
case classConstraintCanOnlyBeUsedInProtocol = "'class' constraint can only appear on protocol declarations"
7172
case consecutiveDeclarationsOnSameLine = "consecutive declarations on a line must be separated by ';'"
7273
case consecutiveStatementsOnSameLine = "consecutive statements on a line must be separated by ';'"
7374
case cStyleForLoop = "C-style for statement has been removed in Swift 3"

Tests/SwiftParserTest/translated/InvalidTests.swift

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -489,19 +489,17 @@ final class InvalidTests: XCTestCase {
489489
func testInvalid29() {
490490
AssertParse(
491491
"""
492-
struct Weak<T: 1️⃣class2️⃣> {
492+
struct Weak<T: 1️⃣class> {
493493
weak let value: T
494494
}
495495
""",
496496
diagnostics: [
497-
// TODO: Old parser expected error on line 1: 'class' constraint can only appear on protocol declarations
498-
// TODO: Old parser expected note on line 1: did you mean to write an 'AnyObject' constraint?, Fix-It replacements: 16 - 21 = 'AnyObject'
499-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected '>' to end generic parameter clause"),
500-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected '{' in struct"),
501-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected name and member block in class"),
502-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected '}' to end struct"),
503-
DiagnosticSpec(locationMarker: "2️⃣", message: "extraneous code at top level"),
504-
]
497+
DiagnosticSpec(message: "'class' constraint can only appear on protocol declarations", fixIts: ["replace 'class' by 'AnyObject'"]),
498+
], fixedSource: """
499+
struct Weak<T: AnyObject> {
500+
weak let value: T
501+
}
502+
"""
505503
)
506504
}
507505

0 commit comments

Comments
 (0)