Skip to content

Commit 8c5ff31

Browse files
authored
Merge pull request #18588 from huonw/fixit-missing-if
[Parse] Fixit for inserting 'if' for 'else ... {' all on one line.
2 parents d929d38 + 75934be commit 8c5ff31

File tree

8 files changed

+133
-17
lines changed

8 files changed

+133
-17
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -951,8 +951,10 @@ ERROR(missing_condition_after_if,none,
951951
"missing condition in an 'if' statement", ())
952952
ERROR(expected_lbrace_after_if,PointsToFirstBadToken,
953953
"expected '{' after 'if' condition", ())
954-
ERROR(expected_lbrace_after_else,PointsToFirstBadToken,
955-
"expected '{' after 'else'", ())
954+
ERROR(expected_lbrace_or_if_after_else,PointsToFirstBadToken,
955+
"expected '{' or 'if' after 'else'", ())
956+
ERROR(expected_lbrace_or_if_after_else_fixit,PointsToFirstBadToken,
957+
"expected '{' or 'if' after 'else'; did you mean to write 'if'?", ())
956958
ERROR(unexpected_else_after_if,none,
957959
"unexpected 'else' immediately following 'if' condition", ())
958960
NOTE(suggest_removing_else,none,

include/swift/Parse/Parser.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,18 @@ class Parser {
536536
/// \brief Skip until the next '#else', '#endif' or until eof.
537537
void skipUntilConditionalBlockClose();
538538

539+
/// \brief Skip until either finding \c T1 or reaching the end of the line.
540+
///
541+
/// This uses \c skipSingle and so matches parens etc. After calling, one or
542+
/// more of the following will be true: Tok.is(T1), Tok.isStartOfLine(),
543+
/// Tok.is(tok::eof). The "or more" case is the first two: if the next line
544+
/// starts with T1.
545+
///
546+
/// \returns true if there is an instance of \c T1 on the current line (this
547+
/// avoids the foot-gun of not considering T1 starting the next line for a
548+
/// plain Tok.is(T1) check).
549+
bool skipUntilTokenOrEndOfLine(tok T1);
550+
539551
/// If the parser is generating only a syntax tree, try loading the current
540552
/// node from a previously generated syntax tree.
541553
/// Returns \c true if the node has been loaded and inserted into the current
@@ -1341,7 +1353,8 @@ class Parser {
13411353
ParserStatus parseStmtCondition(StmtCondition &Result, Diag<> ID,
13421354
StmtKind ParentKind);
13431355
ParserResult<PoundAvailableInfo> parseStmtConditionPoundAvailable();
1344-
ParserResult<Stmt> parseStmtIf(LabeledStmtInfo LabelInfo);
1356+
ParserResult<Stmt> parseStmtIf(LabeledStmtInfo LabelInfo,
1357+
bool IfWasImplicitlyInserted = false);
13451358
ParserResult<Stmt> parseStmtGuard();
13461359
ParserResult<Stmt> parseStmtWhile(LabeledStmtInfo LabelInfo);
13471360
ParserResult<Stmt> parseStmtRepeat(LabeledStmtInfo LabelInfo);

lib/Parse/ParseDecl.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3281,9 +3281,7 @@ ParserResult<PoundDiagnosticDecl> Parser::parseDeclPoundDiagnostic() {
32813281
// Catch #warning(oops, forgot the quotes)
32823282
SourceLoc wordsStartLoc = Tok.getLoc();
32833283

3284-
while (!Tok.isAtStartOfLine() && !Tok.isAny(tok::r_paren, tok::eof)) {
3285-
skipSingle();
3286-
}
3284+
skipUntilTokenOrEndOfLine(tok::r_paren);
32873285

32883286
SourceLoc wordsEndLoc = getEndOfPreviousLoc();
32893287

lib/Parse/ParseStmt.cpp

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,9 @@ bool Parser::isTerminatorForBraceItemListKind(BraceItemListKind Kind,
182182
// for 'case's.
183183
do {
184184
consumeToken();
185-
while (!Tok.isAtStartOfLine() && Tok.isNot(tok::eof))
186-
skipSingle();
185+
186+
// just find the end of the line
187+
skipUntilTokenOrEndOfLine(tok::NUM_TOKENS);
187188
} while (Tok.isAny(tok::pound_if, tok::pound_elseif, tok::pound_else));
188189
return isAtStartOfSwitchCase(*this, /*needsToBacktrack*/false);
189190
}
@@ -640,9 +641,7 @@ ParserResult<BraceStmt> Parser::parseBraceItemList(Diag<> ID) {
640641
diagnose(Tok, ID);
641642

642643
// Attempt to recover by looking for a left brace on the same line
643-
while (Tok.isNot(tok::eof, tok::l_brace) && !Tok.isAtStartOfLine())
644-
skipSingle();
645-
if (Tok.isNot(tok::l_brace))
644+
if (!skipUntilTokenOrEndOfLine(tok::l_brace))
646645
return nullptr;
647646
}
648647
SyntaxParsingContext LocalContext(SyntaxContext, SyntaxKind::CodeBlock);
@@ -1573,9 +1572,17 @@ ParserStatus Parser::parseStmtCondition(StmtCondition &Condition,
15731572
/// stmt-if-else:
15741573
/// 'else' stmt-brace
15751574
/// 'else' stmt-if
1576-
ParserResult<Stmt> Parser::parseStmtIf(LabeledStmtInfo LabelInfo) {
1575+
ParserResult<Stmt> Parser::parseStmtIf(LabeledStmtInfo LabelInfo,
1576+
bool IfWasImplicitlyInserted) {
15771577
SyntaxContext->setCreateSyntax(SyntaxKind::IfStmt);
1578-
SourceLoc IfLoc = consumeToken(tok::kw_if);
1578+
SourceLoc IfLoc;
1579+
if (IfWasImplicitlyInserted) {
1580+
// The code was invalid due to a missing 'if' (e.g. 'else x < y {') and a
1581+
// fixit implicitly inserted it.
1582+
IfLoc = Tok.getLoc();
1583+
} else {
1584+
IfLoc = consumeToken(tok::kw_if);
1585+
}
15791586

15801587
ParserStatus Status;
15811588
StmtCondition Condition;
@@ -1635,16 +1642,30 @@ ParserResult<Stmt> Parser::parseStmtIf(LabeledStmtInfo LabelInfo) {
16351642
ParserResult<Stmt> ElseBody;
16361643
if (Tok.is(tok::kw_else)) {
16371644
ElseLoc = consumeToken(tok::kw_else);
1638-
if (Tok.is(tok::kw_if)) {
1645+
1646+
bool implicitlyInsertIf = false;
1647+
if (Tok.isNot(tok::kw_if, tok::l_brace, tok::code_complete)) {
1648+
// The code looks like 'if ... { ... } else not_if_or_lbrace', so we've
1649+
// got a problem. If the last bit is 'else ... {' on one line, let's
1650+
// assume they've forgotten the 'if'.
1651+
BacktrackingScope backtrack(*this);
1652+
implicitlyInsertIf = skipUntilTokenOrEndOfLine(tok::l_brace);
1653+
}
1654+
1655+
if (Tok.is(tok::kw_if) || implicitlyInsertIf) {
1656+
if (implicitlyInsertIf) {
1657+
diagnose(ElseLoc, diag::expected_lbrace_or_if_after_else_fixit)
1658+
.fixItInsertAfter(ElseLoc, " if");
1659+
}
16391660
SyntaxParsingContext ElseIfCtxt(SyntaxContext, SyntaxKind::IfStmt);
1640-
ElseBody = parseStmtIf(LabeledStmtInfo());
1661+
ElseBody = parseStmtIf(LabeledStmtInfo(), implicitlyInsertIf);
16411662
} else if (Tok.is(tok::code_complete)) {
16421663
if (CodeCompletion)
16431664
CodeCompletion->completeAfterIfStmt(/*hasElse*/true);
16441665
Status.setHasCodeCompletion();
16451666
consumeToken(tok::code_complete);
16461667
} else {
1647-
ElseBody = parseBraceItemList(diag::expected_lbrace_after_else);
1668+
ElseBody = parseBraceItemList(diag::expected_lbrace_or_if_after_else);
16481669
}
16491670
Status |= ElseBody;
16501671
} else if (Tok.is(tok::code_complete)) {

lib/Parse/Parser.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,13 @@ void Parser::skipUntilConditionalBlockClose() {
714714
}
715715
}
716716

717+
bool Parser::skipUntilTokenOrEndOfLine(tok T1) {
718+
while (Tok.isNot(tok::eof, T1) && !Tok.isAtStartOfLine())
719+
skipSingle();
720+
721+
return Tok.is(T1) && !Tok.isAtStartOfLine();
722+
}
723+
717724
bool Parser::loadCurrentSyntaxNodeFromCache() {
718725
// Don't do a cache lookup when not building a syntax tree since otherwise
719726
// the corresponding AST nodes do not get created

test/FixCode/fixits-if-else.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// RUN: not %swift -emit-sil -target %target-triple %s -emit-fixits-path %t.remap -I %S/Inputs -diagnostics-editor-mode
2+
// RUN: c-arcmt-test %t.remap | arcmt-test -verify-transformed-files %s.result
3+
4+
func something() -> Bool { return true }
5+
6+
func foo1() {
7+
if something() {
8+
} else
9+
}
10+
11+
func foo2() {
12+
if something() {
13+
} else
14+
foo1()
15+
}
16+
17+
func foo3() {
18+
if something() {
19+
} else something() { // all on one line, 'if' inserted
20+
}
21+
}
22+
23+
func foo4() {
24+
if something() {
25+
} else something()
26+
{
27+
}
28+
}
29+
30+
func foo5() {
31+
if something() {
32+
} else something()
33+
foo1()
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// RUN: not %swift -emit-sil -target %target-triple %s -emit-fixits-path %t.remap -I %S/Inputs -diagnostics-editor-mode
2+
// RUN: c-arcmt-test %t.remap | arcmt-test -verify-transformed-files %s.result
3+
4+
func something() -> Bool { return true }
5+
6+
func foo1() {
7+
if something() {
8+
} else
9+
}
10+
11+
func foo2() {
12+
if something() {
13+
} else
14+
foo1()
15+
}
16+
17+
func foo3() {
18+
if something() {
19+
} else if something() { // all on one line, 'if' inserted
20+
}
21+
}
22+
23+
func foo4() {
24+
if something() {
25+
} else something()
26+
{
27+
}
28+
}
29+
30+
func foo5() {
31+
if something() {
32+
} else something()
33+
foo1()
34+
}

test/stmt/statements.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,16 @@ func IfStmt1() {
207207

208208
func IfStmt2() {
209209
if 1 > 0 {
210-
} else // expected-error {{expected '{' after 'else'}}
210+
} else // expected-error {{expected '{' or 'if' after 'else'}}
211211
_ = 42
212212
}
213+
func IfStmt3() {
214+
if 1 > 0 {
215+
} else 1 < 0 { // expected-error {{expected '{' or 'if' after 'else'; did you mean to write 'if'?}} {{9-9= if}}
216+
_ = 42
217+
} else {
218+
}
219+
}
213220

214221
//===--- While statement.
215222

0 commit comments

Comments
 (0)