Skip to content

Commit c5a0860

Browse files
authored
Merge pull request #3365 from benlangmuir/unresolved-member-stmt-condition
[CodeCompletion] Improve StmtCondition recovery for code-completion
2 parents 4bc4f51 + 75619c0 commit c5a0860

File tree

5 files changed

+144
-27
lines changed

5 files changed

+144
-27
lines changed

lib/Parse/ParseStmt.cpp

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,7 +1390,23 @@ ParserResult<Stmt> Parser::parseStmtIf(LabeledStmtInfo LabelInfo) {
13901390
// by a conditional binding. The else branch does *not* see these variables.
13911391
{
13921392
Scope S(this, ScopeKind::IfVars);
1393-
1393+
1394+
auto recoverWithCond = [&](ParserStatus Status,
1395+
StmtCondition Condition) -> ParserResult<Stmt> {
1396+
if (Condition.empty()) {
1397+
SmallVector<StmtConditionElement, 1> ConditionElems;
1398+
ConditionElems.emplace_back(new (Context) ErrorExpr(IfLoc));
1399+
Condition = Context.AllocateCopy(ConditionElems);
1400+
}
1401+
auto EndLoc = Condition.back().getEndLoc();
1402+
return makeParserResult(
1403+
Status,
1404+
new (Context) IfStmt(
1405+
LabelInfo, IfLoc, Condition,
1406+
BraceStmt::create(Context, EndLoc, {}, EndLoc, /*implicit=*/true),
1407+
SourceLoc(), nullptr));
1408+
};
1409+
13941410
if (Tok.is(tok::l_brace)) {
13951411
SourceLoc LBraceLoc = Tok.getLoc();
13961412
diagnose(IfLoc, diag::missing_condition_after_if)
@@ -1401,17 +1417,14 @@ ParserResult<Stmt> Parser::parseStmtIf(LabeledStmtInfo LabelInfo) {
14011417
} else {
14021418
Status |= parseStmtCondition(Condition, diag::expected_condition_if,
14031419
StmtKind::If);
1404-
if (Status.isError() || Status.hasCodeCompletion()) {
1405-
// FIXME: better recovery
1406-
return makeParserResult<Stmt>(Status, nullptr);
1407-
}
1420+
if (Status.isError() || Status.hasCodeCompletion())
1421+
return recoverWithCond(Status, Condition);
14081422
}
14091423

14101424
NormalBody = parseBraceItemList(diag::expected_lbrace_after_if);
1411-
if (NormalBody.isNull())
1412-
return nullptr; // FIXME: better recovery
1413-
14141425
Status |= NormalBody;
1426+
if (NormalBody.isNull())
1427+
return recoverWithCond(Status, Condition);
14151428
}
14161429

14171430
// The else branch, if any, is outside of the scope of the condition.
@@ -1441,7 +1454,22 @@ ParserResult<Stmt> Parser::parseStmtGuard() {
14411454
ParserStatus Status;
14421455
StmtCondition Condition;
14431456
ParserResult<BraceStmt> Body;
1444-
1457+
1458+
auto recoverWithCond = [&](ParserStatus Status,
1459+
StmtCondition Condition) -> ParserResult<Stmt> {
1460+
if (Condition.empty()) {
1461+
SmallVector<StmtConditionElement, 1> ConditionElems;
1462+
ConditionElems.emplace_back(new (Context) ErrorExpr(GuardLoc));
1463+
Condition = Context.AllocateCopy(ConditionElems);
1464+
}
1465+
auto EndLoc = Condition.back().getEndLoc();
1466+
return makeParserResult(
1467+
Status,
1468+
new (Context) GuardStmt(
1469+
GuardLoc, Condition,
1470+
BraceStmt::create(Context, EndLoc, {}, EndLoc, /*implicit=*/true)));
1471+
};
1472+
14451473
if (Tok.is(tok::l_brace)) {
14461474
SourceLoc LBraceLoc = Tok.getLoc();
14471475
diagnose(GuardLoc, diag::missing_condition_after_guard)
@@ -1454,15 +1482,15 @@ ParserResult<Stmt> Parser::parseStmtGuard() {
14541482
StmtKind::Guard);
14551483
if (Status.isError() || Status.hasCodeCompletion()) {
14561484
// FIXME: better recovery
1457-
return makeParserResult<Stmt>(Status, nullptr);
1485+
return recoverWithCond(Status, Condition);
14581486
}
14591487
}
14601488

14611489
// Parse the 'else'. If it is missing, and if the following token isn't a {
14621490
// then the parser is hopelessly lost - just give up instead of spewing.
14631491
if (parseToken(tok::kw_else, diag::expected_else_after_guard) &&
14641492
Tok.isNot(tok::l_brace))
1465-
return makeParserError();
1493+
return recoverWithCond(Status, Condition);
14661494

14671495
// Before parsing the body, disable all of the bound variables so that they
14681496
// cannot be used unbound.
@@ -1479,8 +1507,8 @@ ParserResult<Stmt> Parser::parseStmtGuard() {
14791507

14801508
Body = parseBraceItemList(diag::expected_lbrace_after_guard);
14811509
if (Body.isNull())
1482-
return nullptr; // FIXME: better recovery
1483-
1510+
return recoverWithCond(Status, Condition);
1511+
14841512
Status |= Body;
14851513

14861514
return makeParserResult(Status,
@@ -1810,6 +1838,21 @@ ParserResult<Stmt> Parser::parseStmtWhile(LabeledStmtInfo LabelInfo) {
18101838
ParserStatus Status;
18111839
StmtCondition Condition;
18121840

1841+
auto recoverWithCond = [&](ParserStatus Status,
1842+
StmtCondition Condition) -> ParserResult<Stmt> {
1843+
if (Condition.empty()) {
1844+
SmallVector<StmtConditionElement, 1> ConditionElems;
1845+
ConditionElems.emplace_back(new (Context) ErrorExpr(WhileLoc));
1846+
Condition = Context.AllocateCopy(ConditionElems);
1847+
}
1848+
auto EndLoc = Condition.back().getEndLoc();
1849+
return makeParserResult(
1850+
Status,
1851+
new (Context) WhileStmt(
1852+
LabelInfo, WhileLoc, Condition,
1853+
BraceStmt::create(Context, EndLoc, {}, EndLoc, /*implicit=*/true)));
1854+
};
1855+
18131856
if (Tok.is(tok::l_brace)) {
18141857
SourceLoc LBraceLoc = Tok.getLoc();
18151858
diagnose(WhileLoc, diag::missing_condition_after_while)
@@ -1820,18 +1863,15 @@ ParserResult<Stmt> Parser::parseStmtWhile(LabeledStmtInfo LabelInfo) {
18201863
} else {
18211864
Status |= parseStmtCondition(Condition, diag::expected_condition_while,
18221865
StmtKind::While);
1823-
if (Status.isError() || Status.hasCodeCompletion()) {
1824-
// FIXME: better recovery
1825-
return makeParserResult<Stmt>(Status, nullptr);
1826-
}
1866+
if (Status.isError() || Status.hasCodeCompletion())
1867+
return recoverWithCond(Status, Condition);
18271868
}
18281869

18291870
ParserResult<BraceStmt> Body =
18301871
parseBraceItemList(diag::expected_lbrace_after_while);
1831-
if (Body.isNull())
1832-
return nullptr; // FIXME: better recovery
1833-
18341872
Status |= Body;
1873+
if (Body.isNull())
1874+
return recoverWithCond(Status, Condition);
18351875

18361876
return makeParserResult(
18371877
Status, new (Context) WhileStmt(LabelInfo, WhileLoc, Condition,

test/IDE/complete_stmt_controlling_expr.swift

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,22 @@
116116
// RUN: FileCheck %s -check-prefix=WITH_I_INT_LOCAL < %t.where.txt
117117
// RUN: FileCheck %s -check-prefix=WITH_J_INT < %t.where.txt
118118

119+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_IF_1 | FileCheck %s -check-prefix=UNRESOLVED_B
120+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_IF_2 | FileCheck %s -check-prefix=UNRESOLVED_B
121+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_IF_3 | FileCheck %s -check-prefix=UNRESOLVED_B
122+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_IF_4 | FileCheck %s -check-prefix=UNRESOLVED_B
123+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_WHILE_1 | FileCheck %s -check-prefix=UNRESOLVED_B
124+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_WHILE_2 | FileCheck %s -check-prefix=UNRESOLVED_B
125+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_WHILE_3 | FileCheck %s -check-prefix=UNRESOLVED_B
126+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_WHILE_4 | FileCheck %s -check-prefix=UNRESOLVED_B
127+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_GUARD_1 | FileCheck %s -check-prefix=UNRESOLVED_B
128+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_GUARD_2 | FileCheck %s -check-prefix=UNRESOLVED_B
129+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_GUARD_3 | FileCheck %s -check-prefix=UNRESOLVED_B
130+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_GUARD_4 | FileCheck %s -check-prefix=UNRESOLVED_B
131+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_GUARD_5 | FileCheck %s -check-prefix=UNRESOLVED_B
132+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_GUARD_6 | FileCheck %s -check-prefix=UNRESOLVED_B
133+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_GUARD_7 | FileCheck %s -check-prefix=UNRESOLVED_B
134+
119135

120136
struct FooStruct {
121137
var instanceVar : Int
@@ -496,3 +512,63 @@ func testSwitchCaseWhereExprIJ1(_ fooObject: FooStruct) {
496512

497513
// WITH_I_E_EXPR_SPECIFIC: Decl[LocalVar]/ExprSpecific: i[#Int#]{{; name=.+$}}
498514
// WITH_I_E_EXPR_SPECIFIC: Decl[LocalVar]/Local: e[#Int#]{{; name=.+$}}
515+
516+
enum A { case aaa }
517+
enum B { case bbb }
518+
// UNRESOLVED_B-NOT: aaa
519+
// UNRESOLVED_B: Decl[EnumElement]/ExprSpecific: bbb[#B#]; name=bbb
520+
// UNRESOLVED_B-NOT: aaa
521+
522+
struct AA {
523+
func takeEnum(_: A) {}
524+
}
525+
struct BB {
526+
func takeEnum(_: B) {}
527+
}
528+
func testUnresolvedIF1(x: BB) {
529+
if x.takeEnum(.#^UNRESOLVED_IF_1^#)
530+
}
531+
func testUnresolvedIF2(x: BB) {
532+
if true, x.takeEnum(.#^UNRESOLVED_IF_2^#)
533+
}
534+
func testUnresolvedIF3(x: BB) {
535+
if true, x.takeEnum(.#^UNRESOLVED_IF_3^#) {}
536+
}
537+
func testUnresolvedIF4(x: BB) {
538+
if let x.takeEnum(.#^UNRESOLVED_IF_4^#)
539+
}
540+
541+
func testUnresolvedWhile1(x: BB) {
542+
while x.takeEnum(.#^UNRESOLVED_WHILE_1^#)
543+
}
544+
func testUnresolvedWhile2(x: BB) {
545+
while true, x.takeEnum(.#^UNRESOLVED_WHILE_2^#)
546+
}
547+
func testUnresolvedWhile3(x: BB) {
548+
while let x.takeEnum(.#^UNRESOLVED_WHILE_3^#)
549+
}
550+
func testUnresolvedWhile4(x: BB) {
551+
while true, x.takeEnum(.#^UNRESOLVED_WHILE_4^#) {}
552+
}
553+
554+
func testUnresolvedGuard1(x: BB) {
555+
guard x.takeEnum(.#^UNRESOLVED_GUARD_1^#)
556+
}
557+
func testUnresolvedGuard2(x: BB) {
558+
guard x.takeEnum(.#^UNRESOLVED_GUARD_2^#) {}
559+
}
560+
func testUnresolvedGuard3(x: BB) {
561+
guard x.takeEnum(.#^UNRESOLVED_GUARD_3^#) else
562+
}
563+
func testUnresolvedGuard4(x: BB) {
564+
guard x.takeEnum(.#^UNRESOLVED_GUARD_4^#) else {}
565+
}
566+
func testUnresolvedGuard5(x: BB) {
567+
guard true, x.takeEnum(.#^UNRESOLVED_GUARD_5^#)
568+
}
569+
func testUnresolvedGuard6(x: BB) {
570+
guard let x.takeEnum(.#^UNRESOLVED_GUARD_6^#)
571+
}
572+
func testUnresolvedGuard7(x: BB) {
573+
guard let x.takeEnum(.#^UNRESOLVED_GUARD_7^#) else {}
574+
}

test/stmt/if_while_var.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ class B {} // expected-note * {{did you mean 'B'?}}
2525
class D : B {}// expected-note * {{did you mean 'D'?}}
2626

2727
// TODO poor recovery in these cases
28-
if let {} // expected-error {{expected '{' after 'if' condition}}
29-
if let x = {} // expected-error{{'{' after 'if'}} expected-error {{variable binding in a condition requires an initializer}}
28+
if let {} // expected-error {{expected '{' after 'if' condition}} expected-error {{pattern matching in a condition requires the 'case' keyword}}
29+
if let x = {} // expected-error{{'{' after 'if'}} expected-error {{variable binding in a condition requires an initializer}} expected-error{{initializer for conditional binding must have Optional type, not '() -> ()'}}
30+
31+
/Users/blangmuir/src/s/swift/test/stmt/if_while_var.swift
3032

3133
if let x = foo() {
3234
} else {
@@ -72,13 +74,13 @@ if 1 != 2, let x : Int? = opt {}
7274

7375
// Test error recovery.
7476
// <rdar://problem/19939746> Improve error recovery for malformed if statements
75-
if 1 != 2, {
77+
if 1 != 2, { // expected-error {{type '() -> ()' does not conform to protocol 'Boolean'}}
7678
} // expected-error {{expected '{' after 'if' condition}}
7779
if 1 != 2, 4 == 57 {}
7880
if 1 != 2, 4 == 57, let x = opt {}
7981

8082
// Test that these don't cause the parser to crash.
81-
if true { if a == 0; {} } // expected-error {{expected '{' after 'if' condition}} expected-error 2{{}}
83+
if true { if a == 0; {} } // expected-error {{expected '{' after 'if' condition}} expected-error 3{{}}
8284
if a == 0, where b == 0 {} // expected-error 4{{}} expected-note {{}} {{25-25=_ = }}
8385

8486

validation-test/IDE/crashers/047-swift-typechecker-typechecktoplevelcodedecl.swift

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// RUN: %target-swift-ide-test -code-completion -code-completion-token=A -source-filename=%s
2+
if c={func b:#^A^#

0 commit comments

Comments
 (0)