From b2c6854fe169da794928b6de24cd88f4cca1e93b Mon Sep 17 00:00:00 2001 From: Mateus Rodrigues Date: Mon, 19 Aug 2024 16:55:31 -0300 Subject: [PATCH 1/2] Use `continue` instead of `break` after failing to match a closing delimiter in `canRecoverTo` --- Sources/SwiftParser/Recovery.swift | 2 +- .../BasicFormatTests.swift | 9 + Tests/SwiftParserTest/DeclarationTests.swift | 44 +---- .../translated/RecoveryTests.swift | 162 ++++-------------- 4 files changed, 50 insertions(+), 167 deletions(-) diff --git a/Sources/SwiftParser/Recovery.swift b/Sources/SwiftParser/Recovery.swift index df0c32ae130..388f93492ec 100644 --- a/Sources/SwiftParser/Recovery.swift +++ b/Sources/SwiftParser/Recovery.swift @@ -127,7 +127,7 @@ extension Parser.Lookahead { if let closingDelimiter = currentTokenPrecedence.closingTokenKind { let closingDelimiterSpec = TokenSpec(closingDelimiter) guard self.canRecoverTo(closingDelimiterSpec) != nil else { - break + continue } self.eat(closingDelimiterSpec) } diff --git a/Tests/SwiftBasicFormatTest/BasicFormatTests.swift b/Tests/SwiftBasicFormatTest/BasicFormatTests.swift index 801c5e5e7ff..742cac2ef6f 100644 --- a/Tests/SwiftBasicFormatTest/BasicFormatTests.swift +++ b/Tests/SwiftBasicFormatTest/BasicFormatTests.swift @@ -53,6 +53,15 @@ fileprivate func assertFormattingRoundTrips( } final class BasicFormatTest: XCTestCase { + func testClosureParameter() { + let source = """ + myFunc({ + return true + }) + """ + assertFormatted(source: source, expected: source) + } + func testNotIndented() { assertFormatted( source: """ diff --git a/Tests/SwiftParserTest/DeclarationTests.swift b/Tests/SwiftParserTest/DeclarationTests.swift index 7d726174b58..4d8c453768f 100644 --- a/Tests/SwiftParserTest/DeclarationTests.swift +++ b/Tests/SwiftParserTest/DeclarationTests.swift @@ -1301,47 +1301,13 @@ final class DeclarationTests: ParserTestCase { func testExpressionMember() { assertParse( """ - struct S 1️⃣{2️⃣ - 3️⃣/4️⃣ ###line 25 "line-directive.swift"5️⃣ - 6️⃣} + struct S { + 1️⃣/ ###line 25 "line-directive.swift" + } """, diagnostics: [ - DiagnosticSpec( - locationMarker: "2️⃣", - message: "expected '}' to end struct", - notes: [NoteSpec(locationMarker: "1️⃣", message: "to match this opening '{'")], - fixIts: ["insert '}'"] - ), - DiagnosticSpec( - locationMarker: "4️⃣", - message: "bare slash regex literal may not start with space", - fixIts: [ - "convert to extended regex literal with '#'", - #"insert '\'"#, - ] - ), - DiagnosticSpec( - locationMarker: "5️⃣", - message: "expected '/' to end regex literal", - notes: [NoteSpec(locationMarker: "3️⃣", message: "to match this opening '/'")], - fixIts: ["insert '/'"] - ), - DiagnosticSpec( - locationMarker: "6️⃣", - message: "extraneous brace at top level" - ), - ], - applyFixIts: [ - "insert '}'", - #"insert '\'"#, - "insert '/'", - ], - fixedSource: #""" - struct S { - } - /\ ###line 25 "line-directive.swift"/ - } - """# + DiagnosticSpec(message: #"unexpected code '/ ###line 25 "line-directive.swift"' in struct"#) + ] ) } diff --git a/Tests/SwiftParserTest/translated/RecoveryTests.swift b/Tests/SwiftParserTest/translated/RecoveryTests.swift index 9a04d223636..ce4a6908fcd 100644 --- a/Tests/SwiftParserTest/translated/RecoveryTests.swift +++ b/Tests/SwiftParserTest/translated/RecoveryTests.swift @@ -1838,44 +1838,31 @@ final class RecoveryTests: ParserTestCase { func testRecovery100() { assertParse( """ - struct ErrorInFunctionSignatureResultArrayType1 1️⃣{ - func foo() -> Int2️⃣[ { + struct ErrorInFunctionSignatureResultArrayType1 { + func foo() -> Int1️⃣[ { return [0] - }3️⃣ - func bar() -> Int4️⃣] { + } + func bar() -> Int2️⃣] { return [0] } - 5️⃣} + } """, diagnostics: [ DiagnosticSpec( - locationMarker: "2️⃣", - message: "expected '}' to end struct", - notes: [NoteSpec(locationMarker: "1️⃣", message: "to match this opening '{'")], - fixIts: ["insert '}'"] - ), - DiagnosticSpec( - locationMarker: "3️⃣", - message: "expected ']' to end array", - notes: [NoteSpec(locationMarker: "2️⃣", message: "to match this opening '['")], - fixIts: ["insert ']'"] + locationMarker: "1️⃣", + message: "unexpected code '[' in function" ), DiagnosticSpec( - locationMarker: "4️⃣", + locationMarker: "2️⃣", message: "unexpected ']' in type; did you mean to write an array type?", fixIts: ["insert '['"] ), - DiagnosticSpec( - locationMarker: "5️⃣", - message: "extraneous brace at top level" - ), ], fixedSource: """ struct ErrorInFunctionSignatureResultArrayType1 { - func foo() -> Int - }[ { + func foo() -> Int[ { return [0] - }] + } func bar() -> [Int] { return [0] } @@ -1887,45 +1874,15 @@ final class RecoveryTests: ParserTestCase { func testRecovery101() { assertParse( """ - struct ErrorInFunctionSignatureResultArrayType2 1️⃣{ - func foo() -> Int2️⃣[03️⃣ { + struct ErrorInFunctionSignatureResultArrayType2 { + func foo() -> Int1️⃣[0 { return [0] - }4️⃣ - 5️⃣} + } + } """, diagnostics: [ - // TODO: Old parser expected error to add `]` on line 2, but we should just recover to - // `{` with `[0` becoming unexpected. - DiagnosticSpec( - locationMarker: "2️⃣", - message: "expected '}' to end struct", - notes: [NoteSpec(locationMarker: "1️⃣", message: "to match this opening '{'")], - fixIts: ["insert '}'"] - ), - DiagnosticSpec( - locationMarker: "3️⃣", - message: "expected ',' in array element", - fixIts: ["insert ','"] - ), - DiagnosticSpec( - locationMarker: "4️⃣", - message: "expected ']' to end array", - notes: [NoteSpec(locationMarker: "2️⃣", message: "to match this opening '['")], - fixIts: ["insert ']'"] - ), - DiagnosticSpec( - locationMarker: "5️⃣", - message: "extraneous brace at top level" - ), - ], - fixedSource: """ - struct ErrorInFunctionSignatureResultArrayType2 { - func foo() -> Int - }[0, { - return [0] - }] - } - """ + DiagnosticSpec(message: "unexpected code '[0' in function") + ] ) } @@ -1977,37 +1934,21 @@ final class RecoveryTests: ParserTestCase { func testRecovery105() { assertParse( """ - struct ErrorInFunctionSignatureResultArrayType11 ℹ️{ - func foo() -> Int1️⃣[(a){a++}]2️⃣ { + struct ErrorInFunctionSignatureResultArrayType11 { + func foo() -> Int1️⃣[(a){a++}2️⃣] { } - 3️⃣} + } """, diagnostics: [ - // TODO: We should just recover to `{` with `[(a){a++}]` becoming unexpected. DiagnosticSpec( locationMarker: "1️⃣", - message: "expected '}' to end struct", - notes: [NoteSpec(message: "to match this opening '{'")], - fixIts: ["insert '}'"] + message: "unexpected code '[(a)' in function" ), DiagnosticSpec( locationMarker: "2️⃣", - message: "consecutive statements on a line must be separated by newline or ';'", - fixIts: ["insert newline", "insert ';'"] - ), - DiagnosticSpec( - locationMarker: "3️⃣", - message: "extraneous brace at top level" - ), - ], - fixedSource: """ - struct ErrorInFunctionSignatureResultArrayType11 { - func foo() -> Int - }[(a){a++}] - { - } - } - """ + message: "unexpected code in struct" + ) + ] ) } @@ -2513,50 +2454,32 @@ final class RecoveryTests: ParserTestCase { assertParse( """ #if true - struct Foo19605164 1️⃣{ - func a2️⃣(s: S3️⃣[{{g4️⃣) -> Int {} - }}5️⃣} + struct Foo19605164 { + func a1️⃣(s: S2️⃣3️⃣[{{g4️⃣) -> Int {} + }}} #endif """, diagnostics: [ DiagnosticSpec( - locationMarker: "3️⃣", + locationMarker: "2️⃣", message: "expected ')' to end parameter clause", - notes: [NoteSpec(locationMarker: "2️⃣", message: "to match this opening '('")], + notes: [NoteSpec(locationMarker: "1️⃣", message: "to match this opening '('")], fixIts: ["insert ')'"] ), DiagnosticSpec( locationMarker: "3️⃣", - message: "expected '}' to end struct", - notes: [NoteSpec(locationMarker: "1️⃣", message: "to match this opening '{'")], - fixIts: ["insert '}'"] + message: "unexpected code '[' in function" ), DiagnosticSpec( locationMarker: "4️⃣", message: "unexpected code ') -> Int {}' in closure" ), - DiagnosticSpec( - locationMarker: "5️⃣", - message: "expected ',' in array element", - fixIts: ["insert ','"] - ), - DiagnosticSpec( - locationMarker: "5️⃣", - message: "expected ']' to end array", - notes: [NoteSpec(locationMarker: "3️⃣", message: "to match this opening '['")], - fixIts: ["insert ']'"] - ), - DiagnosticSpec( - locationMarker: "5️⃣", - message: "unexpected brace in conditional compilation block" - ), ], fixedSource: """ #if true struct Foo19605164 { - func a(s: S) - }[{{g) -> Int {} - }},]} + func a(s: S) [{{g) -> Int {} + }}} #endif """ ) @@ -3211,28 +3134,13 @@ final class RecoveryTests: ParserTestCase { func testRecovery179() { assertParse( """ - func testSkipUnbalancedParen() ℹ️{1️⃣ - 2️⃣?( + func testSkipUnbalancedParen() { + 1️⃣?( } """, diagnostics: [ - DiagnosticSpec( - locationMarker: "1️⃣", - message: "expected '}' to end function", - notes: [NoteSpec(message: "to match this opening '{'")], - fixIts: ["insert '}'"] - ), - DiagnosticSpec( - locationMarker: "2️⃣", - message: "extraneous code at top level" - ), - ], - fixedSource: """ - func testSkipUnbalancedParen() { - } - ?( - } - """ + DiagnosticSpec(message: "unexpected code '?(' in function") + ] ) } From b02a7f1d4a7acdaf8faa20fae0cc0ee87b3f738f Mon Sep 17 00:00:00 2001 From: Mateus Rodrigues Date: Mon, 19 Aug 2024 21:48:47 -0300 Subject: [PATCH 2/2] Skip tokens in `canRecoverTo` when a closing delimiter is found at the same line --- Sources/SwiftParser/Recovery.swift | 22 ++++++++++++++++++- .../BasicFormatTests.swift | 9 -------- .../translated/RecoveryTests.swift | 11 ++-------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/Sources/SwiftParser/Recovery.swift b/Sources/SwiftParser/Recovery.swift index 388f93492ec..a17112813d2 100644 --- a/Sources/SwiftParser/Recovery.swift +++ b/Sources/SwiftParser/Recovery.swift @@ -123,13 +123,33 @@ extension Parser.Lookahead { if currentTokenPrecedence >= recoveryPrecedence { break } - self.consumeAnyToken() if let closingDelimiter = currentTokenPrecedence.closingTokenKind { let closingDelimiterSpec = TokenSpec(closingDelimiter) + let canCloseAtSameLine: Int? = self.withLookahead { lookahead in + var tokensToSkip = 0 + while !lookahead.at(.endOfFile), !lookahead.currentToken.isAtStartOfLine { + tokensToSkip += 1 + if lookahead.at(closingDelimiterSpec) { + return tokensToSkip + } else { + lookahead.consumeAnyToken() + } + } + return nil + } + if let tokensToSkip = canCloseAtSameLine { + for _ in 0.. Int1️⃣[(a){a++}2️⃣] { + func foo() -> Int1️⃣[(a){a++}] { } } """, diagnostics: [ - DiagnosticSpec( - locationMarker: "1️⃣", - message: "unexpected code '[(a)' in function" - ), - DiagnosticSpec( - locationMarker: "2️⃣", - message: "unexpected code in struct" - ) + DiagnosticSpec(message: "unexpected code '[(a){a++}]' in function") ] ) }