Skip to content

Commit 0f1cffd

Browse files
committed
[CodeCompletion] Completion for control trasfer target
Completion after 'break' and 'continue' rdar://problem/57016218
1 parent 31f715d commit 0f1cffd

File tree

5 files changed

+176
-20
lines changed

5 files changed

+176
-20
lines changed

include/swift/IDE/CodeCompletion.h

+1
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,7 @@ enum class CompletionKind {
534534
AfterIfStmtElse,
535535
GenericRequirement,
536536
PrecedenceGroup,
537+
StmtLabel,
537538
};
538539

539540
/// A single code completion result.

include/swift/Parse/CodeCompletionCallbacks.h

+2
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ class CodeCompletionCallbacks {
219219

220220
virtual void completeGenericRequirement() {};
221221

222+
virtual void completeStmtLabel(StmtKind ParentKind) {};
223+
222224
/// Signals that the AST for the all the delayed-parsed code was
223225
/// constructed. No \c complete*() callbacks will be done after this.
224226
virtual void doneParsing() = 0;

lib/IDE/CodeCompletion.cpp

+59
Original file line numberDiff line numberDiff line change
@@ -1580,6 +1580,7 @@ class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks {
15801580
void completePlatformCondition() override;
15811581
void completeGenericRequirement() override;
15821582
void completeAfterIfStmt(bool hasElse) override;
1583+
void completeStmtLabel(StmtKind ParentKind) override;
15831584

15841585
void doneParsing() override;
15851586

@@ -4350,6 +4351,52 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
43504351
for (auto PGD: precedenceGroups)
43514352
addPrecedenceGroupRef(PGD);
43524353
}
4354+
4355+
void getStmtLabelCompletions(SourceLoc Loc, bool isContinue) {
4356+
class LabelFinder : public ASTWalker {
4357+
SourceManager &SM;
4358+
SourceLoc TargetLoc;
4359+
bool IsContinue;
4360+
4361+
public:
4362+
SmallVector<Identifier, 2> Result;
4363+
4364+
LabelFinder(SourceManager &SM, SourceLoc TargetLoc, bool IsContinue)
4365+
: SM(SM), TargetLoc(TargetLoc), IsContinue(IsContinue) {}
4366+
4367+
std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override {
4368+
if (SM.isBeforeInBuffer(S->getEndLoc(), TargetLoc))
4369+
return {false, S};
4370+
4371+
if (LabeledStmt *LS = dyn_cast<LabeledStmt>(S)) {
4372+
if (LS->getLabelInfo()) {
4373+
if (!IsContinue || LS->isPossibleContinueTarget()) {
4374+
auto label = LS->getLabelInfo().Name;
4375+
if (!llvm::is_contained(Result, label))
4376+
Result.push_back(label);
4377+
}
4378+
}
4379+
}
4380+
4381+
return {true, S};
4382+
}
4383+
4384+
Stmt *walkToStmtPost(Stmt *S) override { return nullptr; }
4385+
4386+
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
4387+
if (SM.isBeforeInBuffer(E->getEndLoc(), TargetLoc))
4388+
return {false, E};
4389+
return {true, E};
4390+
}
4391+
} Finder(CurrDeclContext->getASTContext().SourceMgr, Loc, isContinue);
4392+
const_cast<DeclContext *>(CurrDeclContext)->walkContext(Finder);
4393+
for (auto name : Finder.Result) {
4394+
CodeCompletionResultBuilder Builder(
4395+
Sink, CodeCompletionResult::ResultKind::Pattern,
4396+
SemanticContextKind::Local, {});
4397+
Builder.addTextChunk(name.str());
4398+
}
4399+
}
43534400
};
43544401

43554402
class CompletionOverrideLookup : public swift::VisibleDeclConsumer {
@@ -5133,6 +5180,12 @@ void CodeCompletionCallbacksImpl::completeAccessorBeginning(
51335180
CodeCompleteTokenExpr = E;
51345181
}
51355182

5183+
void CodeCompletionCallbacksImpl::completeStmtLabel(StmtKind ParentKind) {
5184+
CurDeclContext = P.CurDeclContext;
5185+
Kind = CompletionKind::StmtLabel;
5186+
ParentStmtKind = ParentKind;
5187+
}
5188+
51365189
static bool isDynamicLookup(Type T) {
51375190
return T->getRValueType()->isAnyObject();
51385191
}
@@ -5252,6 +5305,7 @@ void CodeCompletionCallbacksImpl::addKeywords(CodeCompletionResultSink &Sink,
52525305
case CompletionKind::KeyPathExprObjC:
52535306
case CompletionKind::KeyPathExprSwift:
52545307
case CompletionKind::PrecedenceGroup:
5308+
case CompletionKind::StmtLabel:
52555309
break;
52565310

52575311
case CompletionKind::AccessorBeginning: {
@@ -5850,6 +5904,11 @@ void CodeCompletionCallbacksImpl::doneParsing() {
58505904
case CompletionKind::PrecedenceGroup:
58515905
Lookup.getPrecedenceGroupCompletions(SyntxKind);
58525906
break;
5907+
case CompletionKind::StmtLabel: {
5908+
SourceLoc Loc = P.Context.SourceMgr.getCodeCompletionLoc();
5909+
Lookup.getStmtLabelCompletions(Loc, ParentStmtKind == StmtKind::Continue);
5910+
break;
5911+
}
58535912
case CompletionKind::AfterIfStmtElse:
58545913
case CompletionKind::CaseStmtKeyword:
58555914
// Handled earlier by keyword completions.

lib/Parse/ParseStmt.cpp

+34-20
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,30 @@ ParserResult<BraceStmt> Parser::parseBraceItemList(Diag<> ID) {
679679
BraceStmt::create(Context, LBLoc, Entries, RBLoc));
680680
}
681681

682+
static ParserStatus parseOptionalControlTransferTarget(Parser &P,
683+
Identifier &Target,
684+
SourceLoc &TargetLoc,
685+
StmtKind Kind) {
686+
// If we have an identifier after 'break' or 'continue', which is not the
687+
// start of another stmt or decl, we assume it is the label to break to,
688+
// unless there is a line break. There is ambiguity with expressions (e.g.
689+
// "break x+y") but since the expression after the them is dead, we don't feel
690+
// bad eagerly parsing this.
691+
if (!P.Tok.isAtStartOfLine()) {
692+
if (P.Tok.is(tok::identifier) && !P.isStartOfStmt() &&
693+
!P.isStartOfSwiftDecl()) {
694+
TargetLoc = P.consumeIdentifier(&Target);
695+
return makeParserSuccess();
696+
} else if (P.Tok.is(tok::code_complete)) {
697+
if (P.CodeCompletion)
698+
P.CodeCompletion->completeStmtLabel(Kind);
699+
TargetLoc = P.consumeToken(tok::code_complete);
700+
return makeParserCodeCompletionStatus();
701+
}
702+
}
703+
return makeParserSuccess();
704+
}
705+
682706
/// parseStmtBreak
683707
///
684708
/// stmt-break:
@@ -689,17 +713,12 @@ ParserResult<Stmt> Parser::parseStmtBreak() {
689713
SourceLoc Loc = consumeToken(tok::kw_break);
690714
SourceLoc TargetLoc;
691715
Identifier Target;
716+
ParserStatus Status;
717+
Status |= parseOptionalControlTransferTarget(*this, Target, TargetLoc,
718+
StmtKind::Break);
692719

693-
// If we have an identifier after this, which is not the start of another
694-
// stmt or decl, we assume it is the label to break to, unless there is a
695-
// line break. There is ambiguity with expressions (e.g. "break x+y") but
696-
// since the expression after the break is dead, we don't feel bad eagerly
697-
// parsing this.
698-
if (Tok.is(tok::identifier) && !Tok.isAtStartOfLine() &&
699-
!isStartOfStmt() && !isStartOfSwiftDecl())
700-
TargetLoc = consumeIdentifier(&Target);
701-
702-
return makeParserResult(new (Context) BreakStmt(Loc, Target, TargetLoc));
720+
return makeParserResult(Status,
721+
new (Context) BreakStmt(Loc, Target, TargetLoc));
703722
}
704723

705724
/// parseStmtContinue
@@ -712,17 +731,12 @@ ParserResult<Stmt> Parser::parseStmtContinue() {
712731
SourceLoc Loc = consumeToken(tok::kw_continue);
713732
SourceLoc TargetLoc;
714733
Identifier Target;
734+
ParserStatus Status;
735+
Status |= parseOptionalControlTransferTarget(*this, Target, TargetLoc,
736+
StmtKind::Continue);
715737

716-
// If we have an identifier after this, which is not the start of another
717-
// stmt or decl, we assume it is the label to continue to, unless there is a
718-
// line break. There is ambiguity with expressions (e.g. "continue x+y") but
719-
// since the expression after the continue is dead, we don't feel bad eagerly
720-
// parsing this.
721-
if (Tok.is(tok::identifier) && !Tok.isAtStartOfLine() &&
722-
!isStartOfStmt() && !isStartOfSwiftDecl())
723-
TargetLoc = consumeIdentifier(&Target);
724-
725-
return makeParserResult(new (Context) ContinueStmt(Loc, Target, TargetLoc));
738+
return makeParserResult(Status,
739+
new (Context) ContinueStmt(Loc, Target, TargetLoc));
726740
}
727741

728742

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=LABEL_1 | %FileCheck %s -check-prefix=LABEL_1
2+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=LABEL_2 | %FileCheck %s -check-prefix=LABEL_2
3+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=LABEL_3 | %FileCheck %s -check-prefix=LABEL_3
4+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=LABEL_4 | %FileCheck %s -check-prefix=LABEL_4
5+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=LABEL_5 | %FileCheck %s -check-prefix=LABEL_5
6+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=LABEL_6 | %FileCheck %s -check-prefix=LABEL_6
7+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=LABEL_7 | %FileCheck %s -check-prefix=LABEL_7
8+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=TOPLEVEL_1 | %FileCheck %s -check-prefix=TOPLEVEL_1
9+
10+
func test(subject: Int) {
11+
OUTER_IF_1:
12+
if subject == 1 {
13+
break #^LABEL_1^#
14+
// LABEL_1: Begin completions, 1 items
15+
// LABEL_1-DAG: Pattern/Local: OUTER_IF_1;
16+
// LABEL_1: End completions
17+
}
18+
19+
OUTER_SWITCH_1:
20+
switch subject {
21+
case var x where x < 2:
22+
break #^LABEL_2^#
23+
// LABEL_2: Begin completions, 1 items
24+
// LABEL_2-DAG: Pattern/Local: OUTER_SWITCH_1;
25+
// LABEL_2: End completions
26+
27+
INNER_IF_1: if subject == 1 {
28+
INNER_FOR_1: for 0 ..< 1 {
29+
break #^LABEL_3^#
30+
// LABEL_3: Begin completions, 3 items
31+
// LABEL_3-DAG: Pattern/Local: INNER_FOR_1;
32+
// LABEL_3-DAG: Pattern/Local: INNER_IF_1;
33+
// LABEL_3-DAG: Pattern/Local: OUTER_SWITCH_1;
34+
// LABEL_3: End completions
35+
}
36+
break #^LABEL_4^#
37+
// LABEL_4: Begin completions, 2 items
38+
// LABEL_4-DAG: Pattern/Local: INNER_IF_1;
39+
// LABEL_4-DAG: Pattern/Local: OUTER_SWITCH_1;
40+
// LABEL_4: End completions
41+
}
42+
43+
INNER_IF_2: if subject == 1 {
44+
INNER_WHILE_1: while i == 1 {
45+
break #^LABEL_5^#
46+
// LABEL_5: Begin completions, 3 items
47+
// LABEL_5-DAG: Pattern/Local: INNER_WHILE_1;
48+
// LABEL_5-DAG: Pattern/Local: INNER_IF_2;
49+
// LABEL_5-DAG: Pattern/Local: OUTER_SWITCH_1;
50+
// LABEL_5: End completions
51+
}
52+
}
53+
54+
INNER_GUARD_1: guard subject == 1 else {
55+
INNER_DOCATCH_1: do {
56+
}
57+
catch let err {
58+
continue #^LABEL_6^#
59+
// LABEL_6: Begin completions, 1 items
60+
// LABEL_6-DAG: Pattern/Local: INNER_DOCATCH_1;
61+
// LABEL_6: End completions
62+
}
63+
}
64+
65+
}
66+
67+
OUTER_FOR_1: for subject == 1 {
68+
break #^LABEL_7^#
69+
// LABEL_7: Begin completions, 1 items
70+
// LABEL_7-DAG: Pattern/Local: OUTER_FOR_1;
71+
// LABEL_7: End completions
72+
}
73+
}
74+
75+
TOP_IF_1: if true {}
76+
TOP_IF_2: if true { break #^TOPLEVEL_1^# }
77+
TOP_IF_3: if true {}
78+
// TOPLEVEL_1: Begin completions, 1 items
79+
// TOPLEVEL_1-DAG: Pattern/Local: TOP_IF_2;
80+
// TOPLEVEL_1: End completions

0 commit comments

Comments
 (0)