Skip to content

Commit bfc68f4

Browse files
committed
[Parser] When recovering from expression parsing don't stop at '{'
When recovering from a parser error in an expression, we resumed parsing at a '{'. I assume this was because we wanted to continue inside e.g. an if-body if parsing the condition failed, but it's actually causing more issue because when parsing e.g. ```swift expr + has - error + functionTakesClosure { } ``` we continue parsing at the `{` of the trailing closure, which is a completely garbage location to continue parsing. The motivating example for this change was (in a result builder) ```swift Text("\(island.#^COMPLETE^#)") takeTrailingClosure {} ``` Here `Text(…)` has an error (because it contains a code completion token) and thus we skip `takeTrailingClosure`, effectively parsing ```swift Text(….) {} ``` which the type checker wasn’t very happy with and thus refused to provide code completion. With this change, we completely drop `takeTrailingClosure {}`. The type checker is a lot happier with that.
1 parent 1e1d4e3 commit bfc68f4

14 files changed

+70
-40
lines changed

include/swift/Parse/Parser.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -671,8 +671,15 @@ class Parser {
671671
/// skipUntilDeclStmtRBrace - Skip to the next decl or '}'.
672672
void skipUntilDeclRBrace();
673673

674-
void skipUntilDeclStmtRBrace(tok T1);
675-
void skipUntilDeclStmtRBrace(tok T1, tok T2);
674+
template <typename ...T>
675+
void skipUntilDeclStmtRBrace(T... K) {
676+
while (Tok.isNot(K..., tok::eof, tok::r_brace, tok::pound_endif,
677+
tok::pound_else, tok::pound_elseif,
678+
tok::code_complete) &&
679+
!isStartOfStmt() && !isStartOfSwiftDecl()) {
680+
skipSingle();
681+
}
682+
}
676683

677684
void skipUntilDeclRBrace(tok T1, tok T2);
678685

lib/Parse/ParseStmt.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -494,13 +494,12 @@ ParserStatus Parser::parseBraceItems(SmallVectorImpl<ASTNode> &Entries,
494494
if (NeedParseErrorRecovery) {
495495
SyntaxParsingContext TokenListCtxt(SyntaxContext,
496496
SyntaxKind::NonEmptyTokenList);
497-
// If we had a parse error, skip to the start of the next stmt, decl or
498-
// '{'.
497+
// If we had a parse error, skip to the start of the next stmt or decl.
499498
//
500499
// It would be ideal to stop at the start of the next expression (e.g.
501500
// "X = 4"), but distinguishing the start of an expression from the middle
502501
// of one is "hard".
503-
skipUntilDeclStmtRBrace(tok::l_brace);
502+
skipUntilDeclStmtRBrace();
504503

505504
// If we have to recover, pretend that we had a semicolon; it's less
506505
// noisy that way.
@@ -1875,7 +1874,7 @@ ParserResult<Stmt> Parser::parseStmtGuard() {
18751874
BraceStmt::create(Context, EndLoc, {}, EndLoc, /*implicit=*/true)));
18761875
};
18771876

1878-
if (Tok.is(tok::l_brace)) {
1877+
if (Tok.isAny(tok::l_brace, tok::kw_else)) {
18791878
SourceLoc LBraceLoc = Tok.getLoc();
18801879
diagnose(GuardLoc, diag::missing_condition_after_guard)
18811880
.highlight(SourceRange(GuardLoc, LBraceLoc));

lib/Parse/Parser.cpp

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -767,24 +767,6 @@ void Parser::skipUntilDeclRBrace() {
767767
skipSingle();
768768
}
769769

770-
void Parser::skipUntilDeclStmtRBrace(tok T1) {
771-
while (Tok.isNot(T1, tok::eof, tok::r_brace, tok::pound_endif,
772-
tok::pound_else, tok::pound_elseif,
773-
tok::code_complete) &&
774-
!isStartOfStmt() && !isStartOfSwiftDecl()) {
775-
skipSingle();
776-
}
777-
}
778-
779-
void Parser::skipUntilDeclStmtRBrace(tok T1, tok T2) {
780-
while (Tok.isNot(T1, T2, tok::eof, tok::r_brace, tok::pound_endif,
781-
tok::pound_else, tok::pound_elseif,
782-
tok::code_complete) &&
783-
!isStartOfStmt() && !isStartOfSwiftDecl()) {
784-
skipSingle();
785-
}
786-
}
787-
788770
void Parser::skipListUntilDeclRBrace(SourceLoc startLoc, tok T1, tok T2) {
789771
while (Tok.isNot(T1, T2, tok::eof, tok::r_brace, tok::pound_endif,
790772
tok::pound_else, tok::pound_elseif)) {

test/IDE/complete_cross_import.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ func foo() {
3434

3535
import #^IMPORT^#
3636

37+
func sync() {}
38+
3739
// IMPORT-DAG: Decl[Module]/None/NotRecommended: A[#Module#]; name=A
3840
// IMPORT-DAG: Decl[Module]/None/NotRecommended: B[#Module#]; name=B
3941
// IMPORT-DAG: Decl[Module]/None: C[#Module#]; name=C

test/IDE/complete_in_result_builder.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,49 @@ func testCompleteErrorTypeInCatch() {
172172
// CATHC2-DAG: Decl[EnumElement]/CurrNominal/TypeRelation[Convertible]: E2({#Int32#})[#Error4#]; name=E2(Int32)
173173
// CATCH2: End completions
174174
}
175+
176+
func testCompleteInStringLiteral() {
177+
struct Island {
178+
var turnipPrice: String
179+
}
180+
181+
func takeTrailingClosure(_ x: () -> Void) -> Text { fatalError() }
182+
183+
struct BStack<Content> {
184+
init(@ViewBuilder content: () -> Content) {}
185+
}
186+
187+
protocol View {}
188+
189+
struct Text: View {
190+
init(_ x: String) {}
191+
192+
var body: Never { fatalError() }
193+
}
194+
195+
@resultBuilder struct ViewBuilder {
196+
static func buildBlock() -> Text { fatalError() }
197+
static func buildBlock<C: View>(_ c: C) -> C { return c }
198+
static func buildBlock<C1: View, C2: View>(_ c: C1, _ d: C2) -> C1 { return c }
199+
}
200+
201+
202+
func foo(island: Island) {
203+
BStack {
204+
let b = "\(island.#^STRING_LITERAL_VAR^#turnipPrice)"
205+
takeTrailingClosure {}
206+
}
207+
// STRING_LITERAL_VAR: Begin completions, 2 items
208+
// STRING_LITERAL_VAR-DAG: Keyword[self]/CurrNominal: self[#Island#]; name=self
209+
// STRING_LITERAL_VAR-DAG: Decl[InstanceVar]/CurrNominal/TypeRelation[Convertible]: turnipPrice[#String#]; name=turnipPrice
210+
// STRING_LITERAL_VAR: End completions
211+
212+
213+
func bar(island: Island) {
214+
BStack {
215+
Text("\(island.#^STRING_LITERAL_AS_ARGUMENT?check=STRING_LITERAL_VAR^#turnipPrice)")
216+
takeTrailingClosure {}
217+
}
218+
}
219+
220+
}

test/Parse/availability_query.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ if #available(OSX 10.51, *) && #available(OSX 10.52, *) { // expected-error {{ex
2121
}
2222

2323

24-
if #available { // expected-error {{expected availability condition}} expected-error {{closure expression is unused}} expected-error {{top-level statement cannot begin with a closure expression}} expected-note {{did you mean to use a 'do' statement?}} {{15-15=do }}
24+
if #available { // expected-error {{expected availability condition}}
2525
}
2626

2727
if #available( { // expected-error {{expected platform name}} expected-error {{expected ')'}} expected-note {{to match this opening '('}}

test/Parse/availability_query_unavailability.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ if #unavailable(OSX 10.51) && #unavailable(OSX 10.52) { // expected-error {{expe
1919
}
2020

2121

22-
if #unavailable { // expected-error {{expected availability condition}} expected-error {{closure expression is unused}} expected-error {{top-level statement cannot begin with a closure expression}} expected-note {{did you mean to use a 'do' statement?}} {{17-17=do }}
22+
if #unavailable { // expected-error {{expected availability condition}}
2323
}
2424

2525
if #unavailable( { // expected-error {{expected platform name}} expected-error {{expected ')'}} expected-note {{to match this opening '('}}
@@ -124,4 +124,4 @@ if #available(macOS 10, *) {
124124
if #available(*) == false { // expected-error {{#available cannot be used as an expression, did you mean to use '#unavailable'?}} {{4-14=#unavailable}} {{18-27=}}
125125
}
126126
if !#available(*) { // expected-error {{#available cannot be used as an expression, did you mean to use '#unavailable'?}} {{4-15=#unavailable}}
127-
}
127+
}

test/Parse/recovery.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -660,9 +660,6 @@ func foo2(bar! = baz) {}// expected-note {{did you mean 'foo2'?}}
660660
// expected-error@+1{{cannot find 'esp' in scope; did you mean 'test'?}}
661661
switch esp {
662662
case let (jeb):
663-
// expected-error@+5{{top-level statement cannot begin with a closure expression}}
664-
// expected-error@+4{{closure expression is unused}}
665-
// expected-note@+3{{did you mean to use a 'do' statement?}}
666663
// expected-error@+2{{expected an identifier to name generic parameter}}
667664
// expected-error@+1{{expected '{' in class}}
668665
class Ceac<}> {}

test/Parse/switch.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ func ~= (x: (Int,Int), y: (Int,Int)) -> Bool {
77
}
88

99
func parseError1(x: Int) {
10-
switch func {} // expected-error {{expected expression in 'switch' statement}} expected-error {{expected identifier in function declaration}} expected-error {{closure expression is unused}} expected-note{{did you mean to use a 'do' statement?}} {{15-15=do }}
10+
switch func {} // expected-error {{expected expression in 'switch' statement}} expected-error {{expected identifier in function declaration}}
1111
}
1212

1313
func parseError2(x: Int) {

test/Parse/trailing_closures.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ func produce(fn: () -> Int?, default d: () -> Int) -> Int { // expected-note {{d
108108
return fn() ?? d()
109109
}
110110
// TODO: The diagnostics here are perhaps a little overboard.
111-
_ = produce { 0 } default: { 1 } // expected-error {{missing argument for parameter 'default' in call}} expected-error {{consecutive statements}} expected-error {{'default' label can only appear inside a 'switch' statement}} expected-error {{top-level statement cannot begin with a closure expression}} expected-error {{closure expression is unused}} expected-note {{did you mean to use a 'do' statement?}}
112-
_ = produce { 2 } `default`: { 3 }
111+
_ = produce { 0 } default: { 1 } // expected-error {{missing argument for parameter 'default' in call}} expected-error {{consecutive statements}} expected-error {{'default' label can only appear inside a 'switch' statement}}
112+
_ = produce { 2 } `default`: { 3 } // expected-error {{labeled block needs 'do'}} expected-warning {{integer literal is unused}}
113113

114114
func f() -> Int { 42 }
115115

test/Syntax/diagnostics_verify.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ if false {
1414

1515
class { // expected-error {{unknown declaration syntax exists in the source}}
1616
// expected-error@-1 {{expected identifier in class declaration}}
17-
// expected-note@-2 {{did you mean to use a 'do' statement?}}
18-
// expected-error@-3 {{closure expression is unused}}
19-
// expected-error@-4 {{top-level statement cannot begin with a closure expression}}
2017

2118
}
2219

test/decl/func/operator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ prefix func +// this should be a comment, not an operator
8282
prefix func -/* this also should be a comment, not an operator */
8383
(arg: Int) -> Int { return arg }
8484

85-
func +*/ () {} // expected-error {{expected identifier in function declaration}} expected-error {{unexpected end of block comment}} expected-error {{closure expression is unused}} expected-error{{top-level statement cannot begin with a closure expression}} expected-note{{did you mean to use a 'do' statement?}} {{13-13=do }}
85+
func +*/ () {} // expected-error {{expected identifier in function declaration}} expected-error {{unexpected end of block comment}}
8686
func errors() {
8787
*/ // expected-error {{unexpected end of block comment}}
8888

test/decl/func/static_func.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ static override func gf5() {} // expected-error {{static methods may only be dec
1313
class override func gf6() {} // expected-error {{class methods may only be declared on a type}}{{1-7=}}
1414
// expected-error@-1 {{'override' can only be specified on class members}}{{7-16=}}
1515

16-
static gf7() {} // expected-error {{expected declaration}} expected-error {{closure expression is unused}} expected-error{{begin with a closure}} expected-note{{did you mean to use a 'do' statement?}} {{14-14=do }}
17-
class gf8() {} // expected-error {{expected '{' in class}} expected-error {{closure expression is unused}} expected-error{{begin with a closure}} expected-note{{did you mean to use a 'do' statement?}} {{13-13=do }}
16+
static gf7() {} // expected-error {{expected declaration}}
17+
class gf8() {} // expected-error {{expected '{' in class}}
1818

1919
func inGlobalFunc() {
2020
static func gf1() {} // expected-error {{static methods may only be declared on a type}}{{3-10=}}

test/stmt/if_while_var.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ if 1 != 2, 4 == 57, let x = opt {} // expected-warning {{immutable value 'x' was
136136

137137
// Test that these don't cause the parser to crash.
138138
if true { if a == 0; {} } // expected-error {{cannot find 'a' in scope}} expected-error {{expected '{' after 'if' condition}}
139-
if a == 0, where b == 0 {} // expected-error 4{{}} expected-note {{}} {{25-25=do }}
139+
if a == 0, where b == 0 {} // expected-error {{cannot find 'a' in scope}} expected-error {{expected expression in conditional}}
140140

141141

142142

0 commit comments

Comments
 (0)