Skip to content

[Parser] When recovering from expression parsing don't stop at '{' #42214

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 2 commits into from
Apr 8, 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
6 changes: 3 additions & 3 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -1093,7 +1093,7 @@ ERROR(conditional_var_valid_identifiers_only,none,
ERROR(expected_condition_if,PointsToFirstBadToken,
"expected expression, var, or let in 'if' condition", ())
ERROR(missing_condition_after_if,none,
"missing condition in an 'if' statement", ())
"missing condition in 'if' statement", ())
ERROR(expected_lbrace_after_if,PointsToFirstBadToken,
"expected '{' after 'if' condition", ())
ERROR(expected_lbrace_or_if_after_else,PointsToFirstBadToken,
Expand All @@ -1109,7 +1109,7 @@ NOTE(suggest_removing_else,none,
ERROR(expected_condition_guard,PointsToFirstBadToken,
"expected expression, var, let or case in 'guard' condition", ())
ERROR(missing_condition_after_guard,none,
"missing condition in an 'guard' statement", ())
"missing condition in 'guard' statement", ())
ERROR(expected_else_after_guard,PointsToFirstBadToken,
"expected 'else' after 'guard' condition", ())
ERROR(expected_lbrace_after_guard,PointsToFirstBadToken,
Expand All @@ -1119,7 +1119,7 @@ ERROR(expected_lbrace_after_guard,PointsToFirstBadToken,
ERROR(expected_condition_while,PointsToFirstBadToken,
"expected expression, var, or let in 'while' condition", ())
ERROR(missing_condition_after_while,none,
"missing condition in a 'while' statement", ())
"missing condition in 'while' statement", ())
ERROR(expected_lbrace_after_while,PointsToFirstBadToken,
"expected '{' after 'while' condition", ())

Expand Down
11 changes: 9 additions & 2 deletions include/swift/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -671,8 +671,15 @@ class Parser {
/// skipUntilDeclStmtRBrace - Skip to the next decl or '}'.
void skipUntilDeclRBrace();

void skipUntilDeclStmtRBrace(tok T1);
void skipUntilDeclStmtRBrace(tok T1, tok T2);
template <typename ...T>
void skipUntilDeclStmtRBrace(T... K) {
while (Tok.isNot(K..., tok::eof, tok::r_brace, tok::pound_endif,
tok::pound_else, tok::pound_elseif,
tok::code_complete) &&
!isStartOfStmt() && !isStartOfSwiftDecl()) {
skipSingle();
}
}

void skipUntilDeclRBrace(tok T1, tok T2);

Expand Down
7 changes: 3 additions & 4 deletions lib/Parse/ParseStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -494,13 +494,12 @@ ParserStatus Parser::parseBraceItems(SmallVectorImpl<ASTNode> &Entries,
if (NeedParseErrorRecovery) {
SyntaxParsingContext TokenListCtxt(SyntaxContext,
SyntaxKind::NonEmptyTokenList);
// If we had a parse error, skip to the start of the next stmt, decl or
// '{'.
// If we had a parse error, skip to the start of the next stmt or decl.
//
// It would be ideal to stop at the start of the next expression (e.g.
// "X = 4"), but distinguishing the start of an expression from the middle
// of one is "hard".
skipUntilDeclStmtRBrace(tok::l_brace);
skipUntilDeclStmtRBrace();

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

if (Tok.is(tok::l_brace)) {
if (Tok.isAny(tok::l_brace, tok::kw_else)) {
SourceLoc LBraceLoc = Tok.getLoc();
diagnose(GuardLoc, diag::missing_condition_after_guard)
.highlight(SourceRange(GuardLoc, LBraceLoc));
Expand Down
18 changes: 0 additions & 18 deletions lib/Parse/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -767,24 +767,6 @@ void Parser::skipUntilDeclRBrace() {
skipSingle();
}

void Parser::skipUntilDeclStmtRBrace(tok T1) {
while (Tok.isNot(T1, tok::eof, tok::r_brace, tok::pound_endif,
tok::pound_else, tok::pound_elseif,
tok::code_complete) &&
!isStartOfStmt() && !isStartOfSwiftDecl()) {
skipSingle();
}
}

void Parser::skipUntilDeclStmtRBrace(tok T1, tok T2) {
while (Tok.isNot(T1, T2, tok::eof, tok::r_brace, tok::pound_endif,
tok::pound_else, tok::pound_elseif,
tok::code_complete) &&
!isStartOfStmt() && !isStartOfSwiftDecl()) {
skipSingle();
}
}

void Parser::skipListUntilDeclRBrace(SourceLoc startLoc, tok T1, tok T2) {
while (Tok.isNot(T1, T2, tok::eof, tok::r_brace, tok::pound_endif,
tok::pound_else, tok::pound_elseif)) {
Expand Down
2 changes: 2 additions & 0 deletions test/IDE/complete_cross_import.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ func foo() {

import #^IMPORT^#

func sync() {}

// IMPORT-DAG: Decl[Module]/None/NotRecommended: A[#Module#]; name=A
// IMPORT-DAG: Decl[Module]/None/NotRecommended: B[#Module#]; name=B
// IMPORT-DAG: Decl[Module]/None: C[#Module#]; name=C
Expand Down
46 changes: 46 additions & 0 deletions test/IDE/complete_in_result_builder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,49 @@ func testCompleteErrorTypeInCatch() {
// CATHC2-DAG: Decl[EnumElement]/CurrNominal/TypeRelation[Convertible]: E2({#Int32#})[#Error4#]; name=E2(Int32)
// CATCH2: End completions
}

func testCompleteInStringLiteral() {
struct Island {
var turnipPrice: String
}

func takeTrailingClosure(_ x: () -> Void) -> Text { fatalError() }

struct BStack<Content> {
init(@ViewBuilder content: () -> Content) {}
}

protocol View {}

struct Text: View {
init(_ x: String) {}

var body: Never { fatalError() }
}

@resultBuilder struct ViewBuilder {
static func buildBlock() -> Text { fatalError() }
static func buildBlock<C: View>(_ c: C) -> C { return c }
static func buildBlock<C1: View, C2: View>(_ c: C1, _ d: C2) -> C1 { return c }
}


func foo(island: Island) {
BStack {
let b = "\(island.#^STRING_LITERAL_VAR^#turnipPrice)"
takeTrailingClosure {}
}
// STRING_LITERAL_VAR: Begin completions, 2 items
// STRING_LITERAL_VAR-DAG: Keyword[self]/CurrNominal: self[#Island#]; name=self
// STRING_LITERAL_VAR-DAG: Decl[InstanceVar]/CurrNominal/TypeRelation[Convertible]: turnipPrice[#String#]; name=turnipPrice
// STRING_LITERAL_VAR: End completions


func bar(island: Island) {
BStack {
Text("\(island.#^STRING_LITERAL_AS_ARGUMENT?check=STRING_LITERAL_VAR^#turnipPrice)")
takeTrailingClosure {}
}
}

}
2 changes: 1 addition & 1 deletion test/Parse/availability_query.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ if #available(OSX 10.51, *) && #available(OSX 10.52, *) { // expected-error {{ex
}


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 }}
if #available { // expected-error {{expected availability condition}}
}

if #available( { // expected-error {{expected platform name}} expected-error {{expected ')'}} expected-note {{to match this opening '('}}
Expand Down
4 changes: 2 additions & 2 deletions test/Parse/availability_query_unavailability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ if #unavailable(OSX 10.51) && #unavailable(OSX 10.52) { // expected-error {{expe
}


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 }}
if #unavailable { // expected-error {{expected availability condition}}
}

if #unavailable( { // expected-error {{expected platform name}} expected-error {{expected ')'}} expected-note {{to match this opening '('}}
Expand Down Expand Up @@ -124,4 +124,4 @@ if #available(macOS 10, *) {
if #available(*) == false { // expected-error {{#available cannot be used as an expression, did you mean to use '#unavailable'?}} {{4-14=#unavailable}} {{18-27=}}
}
if !#available(*) { // expected-error {{#available cannot be used as an expression, did you mean to use '#unavailable'?}} {{4-15=#unavailable}}
}
}
8 changes: 8 additions & 0 deletions test/Parse/guard.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// RUN: %target-typecheck-verify-swift

func noConditionNoElse() {
guard {} // expected-error {{missing condition in 'guard' statement}} expected-error {{expected 'else' after 'guard' condition}}
}
func noCondition() {
guard else {} // expected-error {{missing condition in 'guard' statement}}
}
29 changes: 13 additions & 16 deletions test/Parse/recovery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,60 +79,60 @@ class ClassWithStaticDecls {
func missingControllingExprInIf() {
if // expected-error {{expected expression, var, or let in 'if' condition}}

if { // expected-error {{missing condition in an 'if' statement}}
if { // expected-error {{missing condition in 'if' statement}}
}

if // expected-error {{missing condition in an 'if' statement}}
if // expected-error {{missing condition in 'if' statement}}
{
}

if true {
} else if { // expected-error {{missing condition in an 'if' statement}}
} else if { // expected-error {{missing condition in 'if' statement}}
}

// It is debatable if we should do recovery here and parse { true } as the
// body, but the error message should be sensible.
if { true } { // expected-error {{missing condition in an 'if' statement}} expected-error{{consecutive statements on a line must be separated by ';'}} {{14-14=;}} expected-error {{closure expression is unused}} expected-note {{did you mean to use a 'do' statement?}} {{15-15=do }} expected-warning {{boolean literal is unused}}
if { true } { // expected-error {{missing condition in 'if' statement}} expected-error{{consecutive statements on a line must be separated by ';'}} {{14-14=;}} expected-error {{closure expression is unused}} expected-note {{did you mean to use a 'do' statement?}} {{15-15=do }} expected-warning {{boolean literal is unused}}
}

if { true }() { // expected-error {{missing condition in an 'if' statement}} expected-error 2 {{consecutive statements on a line must be separated by ';'}} expected-error {{closure expression is unused}} expected-note {{did you mean to use a 'do' statement?}} {{17-17=do }} expected-warning {{boolean literal is unused}}
if { true }() { // expected-error {{missing condition in 'if' statement}} expected-error 2 {{consecutive statements on a line must be separated by ';'}} expected-error {{closure expression is unused}} expected-note {{did you mean to use a 'do' statement?}} {{17-17=do }} expected-warning {{boolean literal is unused}}
}

// <rdar://problem/18940198>
if { { } } // expected-error{{missing condition in an 'if' statement}} expected-error{{closure expression is unused}} expected-note {{did you mean to use a 'do' statement?}} {{8-8=do }}
if { { } } // expected-error{{missing condition in 'if' statement}} expected-error{{closure expression is unused}} expected-note {{did you mean to use a 'do' statement?}} {{8-8=do }}
}

func missingControllingExprInWhile() {
while // expected-error {{expected expression, var, or let in 'while' condition}}

while { // expected-error {{missing condition in a 'while' statement}}
while { // expected-error {{missing condition in 'while' statement}}
}

while // expected-error {{missing condition in a 'while' statement}}
while // expected-error {{missing condition in 'while' statement}}
{
}

// It is debatable if we should do recovery here and parse { true } as the
// body, but the error message should be sensible.
while { true } { // expected-error {{missing condition in a 'while' statement}} expected-error{{consecutive statements on a line must be separated by ';'}} {{17-17=;}} expected-error {{closure expression is unused}} expected-note {{did you mean to use a 'do' statement?}} {{18-18=do }} expected-warning {{boolean literal is unused}}
while { true } { // expected-error {{missing condition in 'while' statement}} expected-error{{consecutive statements on a line must be separated by ';'}} {{17-17=;}} expected-error {{closure expression is unused}} expected-note {{did you mean to use a 'do' statement?}} {{18-18=do }} expected-warning {{boolean literal is unused}}
}

while { true }() { // expected-error {{missing condition in a 'while' statement}} expected-error 2 {{consecutive statements on a line must be separated by ';'}} expected-error {{closure expression is unused}} expected-note {{did you mean to use a 'do' statement?}} {{20-20=do }} expected-warning {{boolean literal is unused}}
while { true }() { // expected-error {{missing condition in 'while' statement}} expected-error 2 {{consecutive statements on a line must be separated by ';'}} expected-error {{closure expression is unused}} expected-note {{did you mean to use a 'do' statement?}} {{20-20=do }} expected-warning {{boolean literal is unused}}
}

// <rdar://problem/18940198>
while { { } } // expected-error{{missing condition in a 'while' statement}} expected-error{{closure expression is unused}} expected-note {{did you mean to use a 'do' statement?}} {{11-11=do }}
while { { } } // expected-error{{missing condition in 'while' statement}} expected-error{{closure expression is unused}} expected-note {{did you mean to use a 'do' statement?}} {{11-11=do }}
}

func missingControllingExprInRepeatWhile() {
repeat {
} while // expected-error {{missing condition in a 'while' statement}}
} while // expected-error {{missing condition in 'while' statement}}
{ // expected-error{{closure expression is unused}} expected-note {{did you mean to use a 'do' statement?}} {{3-3=do }}
missingControllingExprInRepeatWhile();
}

repeat {
} while { true }() // expected-error{{missing condition in a 'while' statement}} expected-error{{consecutive statements on a line must be separated by ';'}} {{10-10=;}} expected-warning {{result of call to closure returning 'Bool' is unused}}
} while { true }() // expected-error{{missing condition in 'while' statement}} expected-error{{consecutive statements on a line must be separated by ';'}} {{10-10=;}} expected-warning {{result of call to closure returning 'Bool' is unused}}
}

// SR-165
Expand Down Expand Up @@ -660,9 +660,6 @@ func foo2(bar! = baz) {}// expected-note {{did you mean 'foo2'?}}
// expected-error@+1{{cannot find 'esp' in scope; did you mean 'test'?}}
switch esp {
case let (jeb):
// expected-error@+5{{top-level statement cannot begin with a closure expression}}
// expected-error@+4{{closure expression is unused}}
// expected-note@+3{{did you mean to use a 'do' statement?}}
// expected-error@+2{{expected an identifier to name generic parameter}}
// expected-error@+1{{expected '{' in class}}
class Ceac<}> {}
Expand Down
2 changes: 1 addition & 1 deletion test/Parse/switch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ func ~= (x: (Int,Int), y: (Int,Int)) -> Bool {
}

func parseError1(x: Int) {
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 }}
switch func {} // expected-error {{expected expression in 'switch' statement}} expected-error {{expected identifier in function declaration}}
}

func parseError2(x: Int) {
Expand Down
4 changes: 2 additions & 2 deletions test/Parse/trailing_closures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ func produce(fn: () -> Int?, default d: () -> Int) -> Int { // expected-note {{d
return fn() ?? d()
}
// TODO: The diagnostics here are perhaps a little overboard.
_ = 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?}}
_ = produce { 2 } `default`: { 3 }
_ = 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}}
_ = produce { 2 } `default`: { 3 } // expected-error {{labeled block needs 'do'}} expected-warning {{integer literal is unused}}

func f() -> Int { 42 }

Expand Down
3 changes: 0 additions & 3 deletions test/Syntax/diagnostics_verify.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ if false {

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

}

Expand Down
2 changes: 1 addition & 1 deletion test/decl/func/operator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ prefix func +// this should be a comment, not an operator
prefix func -/* this also should be a comment, not an operator */
(arg: Int) -> Int { return arg }

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 }}
func +*/ () {} // expected-error {{expected identifier in function declaration}} expected-error {{unexpected end of block comment}}
func errors() {
*/ // expected-error {{unexpected end of block comment}}

Expand Down
4 changes: 2 additions & 2 deletions test/decl/func/static_func.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ static override func gf5() {} // expected-error {{static methods may only be dec
class override func gf6() {} // expected-error {{class methods may only be declared on a type}}{{1-7=}}
// expected-error@-1 {{'override' can only be specified on class members}}{{7-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 }}
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 }}
static gf7() {} // expected-error {{expected declaration}}
class gf8() {} // expected-error {{expected '{' in class}}

func inGlobalFunc() {
static func gf1() {} // expected-error {{static methods may only be declared on a type}}{{3-10=}}
Expand Down
2 changes: 1 addition & 1 deletion test/stmt/if_while_var.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ if 1 != 2, 4 == 57, let x = opt {} // expected-warning {{immutable value 'x' was

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



Expand Down