Skip to content

Diagnose if class is being used as an inherited type in a generic parameter clause #957

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
merged 1 commit into from
Oct 17, 2022
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
14 changes: 13 additions & 1 deletion Sources/SwiftParser/Declarations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -349,14 +349,25 @@ extension Parser {

// Parse the ':' followed by a type.
let colon = self.consume(if: .colon)
let unexpectedBeforeInherited: RawUnexpectedNodesSyntax?
let inherited: RawTypeSyntax?
if colon != nil {
if self.at(any: [.identifier, .protocolKeyword, .anyKeyword]) {
unexpectedBeforeInherited = nil
inherited = self.parseType()
} else if let classKeyword = self.consume(if: .classKeyword) {
unexpectedBeforeInherited = RawUnexpectedNodesSyntax([classKeyword], arena: self.arena)
inherited = RawTypeSyntax(RawSimpleTypeIdentifierSyntax(
name: missingToken(.identifier, text: "AnyObject"),
genericArgumentClause: nil,
arena: self.arena
))
} else {
inherited = nil
unexpectedBeforeInherited = nil
inherited = RawTypeSyntax(RawMissingTypeSyntax(arena: self.arena))
}
} else {
unexpectedBeforeInherited = nil
inherited = nil
}
keepGoing = self.consume(if: .comma)
Expand All @@ -366,6 +377,7 @@ extension Parser {
name: name,
ellipsis: ellipsis,
colon: colon,
unexpectedBeforeInherited,
inheritedType: inherited,
trailingComma: keepGoing,
arena: self.arena))
Expand Down
16 changes: 16 additions & 0 deletions Sources/SwiftParser/Diagnostics/ParseDiagnosticsGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,22 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
return .visitChildren
}

public override func visit(_ node: GenericParameterSyntax) -> SyntaxVisitorContinueKind {
if shouldSkip(node) {
return .skipChildren
}
if let inheritedTypeName = node.inheritedType?.as(SimpleTypeIdentifierSyntax.self)?.name {
exchangeTokens(
unexpected: node.unexpectedBetweenColonAndInheritedType,
unexpectedTokenCondition: { $0.tokenKind == .classKeyword },
correctTokens: [inheritedTypeName],
message: { _ in StaticParserError.classConstraintCanOnlyBeUsedInProtocol },
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacement: inheritedTypeName) }
)
}
return .visitChildren
}

public override func visit(_ node: IdentifierExprSyntax) -> SyntaxVisitorContinueKind {
if shouldSkip(node) {
return .skipChildren
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public extension ParserFixIt {
public enum StaticParserError: String, DiagnosticMessage {
case allStatmentsInSwitchMustBeCoveredByCase = "all statements inside a switch must be covered by a 'case' or 'default' label"
case caseOutsideOfSwitchOrEnum = "'case' can only appear inside a 'switch' statement or 'enum' declaration"
case classConstraintCanOnlyBeUsedInProtocol = "'class' constraint can only appear on protocol declarations"
case consecutiveDeclarationsOnSameLine = "consecutive declarations on a line must be separated by ';'"
case consecutiveStatementsOnSameLine = "consecutive statements on a line must be separated by ';'"
case cStyleForLoop = "C-style for statement has been removed in Swift 3"
Expand Down
16 changes: 7 additions & 9 deletions Tests/SwiftParserTest/translated/InvalidTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -489,19 +489,17 @@ final class InvalidTests: XCTestCase {
func testInvalid29() {
AssertParse(
"""
struct Weak<T: 1️⃣class2️⃣> {
struct Weak<T: 1️⃣class> {
weak let value: T
}
""",
diagnostics: [
// TODO: Old parser expected error on line 1: 'class' constraint can only appear on protocol declarations
// TODO: Old parser expected note on line 1: did you mean to write an 'AnyObject' constraint?, Fix-It replacements: 16 - 21 = 'AnyObject'
DiagnosticSpec(locationMarker: "1️⃣", message: "expected '>' to end generic parameter clause"),
DiagnosticSpec(locationMarker: "1️⃣", message: "expected '{' in struct"),
DiagnosticSpec(locationMarker: "2️⃣", message: "expected name and member block in class"),
DiagnosticSpec(locationMarker: "2️⃣", message: "expected '}' to end struct"),
DiagnosticSpec(locationMarker: "2️⃣", message: "extraneous code at top level"),
]
DiagnosticSpec(message: "'class' constraint can only appear on protocol declarations", fixIts: ["replace 'class' by 'AnyObject'"]),
], fixedSource: """
struct Weak<T: AnyObject> {
weak let value: T
}
"""
)
}

Expand Down