diff --git a/Sources/SwiftParser/Declarations.swift b/Sources/SwiftParser/Declarations.swift index 524bc803129..3d6e859799e 100644 --- a/Sources/SwiftParser/Declarations.swift +++ b/Sources/SwiftParser/Declarations.swift @@ -1059,7 +1059,7 @@ extension Parser { } // Parse the signature. - let signature = self.parseFunctionSignature() + let signature = self.parseFunctionSignature(allowOutput: false) let whereClause: RawGenericWhereClauseSyntax? if self.at(.keyword(.where)) { @@ -1389,12 +1389,12 @@ extension Parser { } @_spi(RawSyntax) - public mutating func parseFunctionSignature() -> RawFunctionSignatureSyntax { + public mutating func parseFunctionSignature(allowOutput: Bool = true) -> RawFunctionSignatureSyntax { let input = self.parseParameterClause(for: .functionParameters) var effectSpecifiers = self.parseDeclEffectSpecifiers() - let output: RawReturnClauseSyntax? + var output: RawReturnClauseSyntax? /// Only allow recovery to the arrow with exprKeyword precedence so we only /// skip over misplaced identifiers and don't e.g. recover to an arrow in a 'where' clause. @@ -1404,10 +1404,19 @@ extension Parser { output = nil } + var unexpectedAfterOutput: RawUnexpectedNodesSyntax? + if !allowOutput, + let unexpectedOutput = output + { + output = nil + unexpectedAfterOutput = RawUnexpectedNodesSyntax([unexpectedOutput], arena: self.arena) + } + return RawFunctionSignatureSyntax( input: input, effectSpecifiers: effectSpecifiers, output: output, + unexpectedAfterOutput, arena: self.arena ) } diff --git a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift index f477a29434e..21f0e7ef28b 100644 --- a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift +++ b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift @@ -748,6 +748,41 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor { return .visitChildren } + public override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { + if shouldSkip(node) { + return .skipChildren + } + + if let unexpectedName = node.signature.input.unexpectedBeforeLeftParen, + let previous = unexpectedName.previousToken(viewMode: .sourceAccurate) + { + addDiagnostic( + unexpectedName, + .initializerCannotHaveName, + fixIts: [ + FixIt( + message: RemoveNodesFixIt(unexpectedName), + changes: [ + .makeMissing(unexpectedName), + FixIt.Changes(changes: [.replaceTrailingTrivia(token: previous, newTrivia: .zero)]), + ] + ) + ], + handledNodes: [unexpectedName.id] + ) + } + + if let unexpectedOutput = node.signature.unexpectedAfterOutput { + addDiagnostic( + unexpectedOutput, + .initializerCannotHaveResultType, + handledNodes: [unexpectedOutput.id] + ) + } + + return .visitChildren + } + public override func visit(_ node: MemberDeclListItemSyntax) -> SyntaxVisitorContinueKind { if shouldSkip(node) { return .skipChildren diff --git a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift index d603c1e5b4a..d0907ca85da 100644 --- a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift +++ b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift @@ -131,6 +131,12 @@ extension DiagnosticMessage where Self == StaticParserError { public static var initializerInPattern: Self { .init("unexpected initializer in pattern; did you mean to use '='?") } + public static var initializerCannotHaveName: Self { + .init("initializers cannot have a name") + } + public static var initializerCannotHaveResultType: Self { + .init("initializers cannot have a result type") + } public static var invalidFlagAfterPrecedenceGroupAssignment: Self { .init("expected 'true' or 'false' after 'assignment'") } diff --git a/Tests/SwiftParserTest/translated/InitDeinitTests.swift b/Tests/SwiftParserTest/translated/InitDeinitTests.swift index 93a072ba194..5d23185bc3c 100644 --- a/Tests/SwiftParserTest/translated/InitDeinitTests.swift +++ b/Tests/SwiftParserTest/translated/InitDeinitTests.swift @@ -83,11 +83,11 @@ final class InitDeinitTests: XCTestCase { AssertParse( """ struct FooStructConstructorD { - init() -> FooStructConstructorD { } + init() 1️⃣-> FooStructConstructorD { } } """, diagnostics: [ - // TODO: Old parser expected error on line 2: initializers cannot have a result type + DiagnosticSpec(message: "initializers cannot have a result type") ] ) } @@ -391,6 +391,17 @@ final class InitDeinitTests: XCTestCase { ) } + func testInitDeinit28() { + AssertParse( + """ + init(_ foo: T) 1️⃣-> Int where T: Comparable {} + """, + diagnostics: [ + DiagnosticSpec(message: "initializers cannot have a result type") + ] + ) + } + func testDeinitInSwiftinterfaceIsFollowedByFinalFunc() { AssertParse( """ diff --git a/Tests/SwiftParserTest/translated/RecoveryTests.swift b/Tests/SwiftParserTest/translated/RecoveryTests.swift index 97980e42cbc..b251ac9bbc0 100644 --- a/Tests/SwiftParserTest/translated/RecoveryTests.swift +++ b/Tests/SwiftParserTest/translated/RecoveryTests.swift @@ -1741,7 +1741,7 @@ final class RecoveryTests: XCTestCase { DiagnosticSpec(locationMarker: "1️⃣", message: "expected '>' to end generic parameter clause"), DiagnosticSpec(locationMarker: "2️⃣", message: "expected type and ')' to end parameter clause"), DiagnosticSpec(locationMarker: "3️⃣", message: "unexpected code ')}' before struct"), - DiagnosticSpec(locationMarker: "4️⃣", message: "unexpected code 'x' before parameter clause"), + DiagnosticSpec(locationMarker: "4️⃣", message: "initializers cannot have a name", fixIts: ["remove 'x'"]), ] ) } @@ -1752,9 +1752,11 @@ final class RecoveryTests: XCTestCase { init 1️⃣a(b: Int) {} """, diagnostics: [ - // TODO: (good first issue) Old parser expected error on line 3: initializers cannot have a name, Fix-It replacements: 8 - 9 = '' - DiagnosticSpec(message: "unexpected code 'a' before parameter clause") - ] + DiagnosticSpec(message: "initializers cannot have a name", fixIts: ["remove 'a'"]) + ], + fixedSource: """ + init(b: Int) {} + """ ) } @@ -1764,9 +1766,11 @@ final class RecoveryTests: XCTestCase { init? 1️⃣c(_ d: Int) {} """, diagnostics: [ - // TODO: (good first issue) Old parser expected error on line 1: initializers cannot have a name, Fix-It replacements: 9 - 10 = '' - DiagnosticSpec(message: "unexpected code 'c' before parameter clause") - ] + DiagnosticSpec(message: "initializers cannot have a name", fixIts: ["remove 'c'"]) + ], + fixedSource: """ + init?(_ d: Int) {} + """ ) } @@ -1776,9 +1780,11 @@ final class RecoveryTests: XCTestCase { init 1️⃣e(f: T) {} """, diagnostics: [ - // TODO: (good first issue) Old parser expected error on line 1: initializers cannot have a name, Fix-It replacements: 8 - 9 = '' - DiagnosticSpec(message: "unexpected code 'e' before parameter clause") - ] + DiagnosticSpec(message: "initializers cannot have a name", fixIts: ["remove 'e'"]) + ], + fixedSource: """ + init(f: T) {} + """ ) } @@ -1788,9 +1794,11 @@ final class RecoveryTests: XCTestCase { init? 1️⃣g(_: T) {} """, diagnostics: [ - // TODO: (good first issue) Old parser expected error on line 1: initializers cannot have a name, Fix-It replacements: 9 - 10 = '' - DiagnosticSpec(message: "unexpected code 'g' before parameter clause") - ] + DiagnosticSpec(message: "initializers cannot have a name", fixIts: ["remove 'g'"]) + ], + fixedSource: """ + init?(_: T) {} + """ ) }