Skip to content

Commit 6862809

Browse files
committed
Reimplement break statement target determination using ASTScope.
1 parent 97a8919 commit 6862809

File tree

1 file changed

+109
-92
lines changed

1 file changed

+109
-92
lines changed

lib/Sema/TypeCheckStmt.cpp

Lines changed: 109 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,14 @@ static void tryDiagnoseUnnecessaryCastOverOptionSet(ASTContext &Ctx,
290290
.fixItRemove(SourceRange(ME->getDotLoc(), E->getEndLoc()));
291291
}
292292

293+
/// Whether the given enclosing function is a "defer" body.
294+
static bool isDefer(Optional<AnyFunctionRef> enclosingFunc) {
295+
if (!enclosingFunc.hasValue()) return false;
296+
auto *FD = dyn_cast_or_null<FuncDecl>
297+
(enclosingFunc.getValue().getAbstractFunctionDecl());
298+
return FD && FD->isDeferBody();
299+
}
300+
293301
/// Check that a labeled statement doesn't shadow another statement with the
294302
/// same label.
295303
static void checkLabeledStmtShadowing(
@@ -311,6 +319,104 @@ static void checkLabeledStmtShadowing(
311319
}
312320
}
313321

322+
static void
323+
emitUnresolvedLabelDiagnostics(DiagnosticEngine &DE,
324+
SourceLoc targetLoc, Identifier targetName,
325+
TopCollection<unsigned, LabeledStmt *> corrections) {
326+
// If an unresolved label was used, but we have a single correction,
327+
// produce the specific diagnostic and fixit.
328+
if (corrections.size() == 1) {
329+
DE.diagnose(targetLoc, diag::unresolved_label_corrected,
330+
targetName, corrections.begin()->Value->getLabelInfo().Name)
331+
.highlight(SourceRange(targetLoc))
332+
.fixItReplace(SourceRange(targetLoc),
333+
corrections.begin()->Value->getLabelInfo().Name.str());
334+
DE.diagnose(corrections.begin()->Value->getLabelInfo().Loc,
335+
diag::decl_declared_here,
336+
corrections.begin()->Value->getLabelInfo().Name);
337+
} else {
338+
// If we have multiple corrections or none, produce a generic diagnostic
339+
// and all corrections available.
340+
DE.diagnose(targetLoc, diag::unresolved_label, targetName)
341+
.highlight(SourceRange(targetLoc));
342+
for (auto &entry : corrections)
343+
DE.diagnose(entry.Value->getLabelInfo().Loc, diag::note_typo_candidate,
344+
entry.Value->getLabelInfo().Name.str())
345+
.fixItReplace(SourceRange(targetLoc),
346+
entry.Value->getLabelInfo().Name.str());
347+
}
348+
}
349+
350+
/// Find the target of a break statement.
351+
///
352+
/// \returns the target, if one was found, or \c nullptr if no such target
353+
/// exists.
354+
static LabeledStmt *findBreakStmtTarget(
355+
ASTContext &ctx, SourceFile *sourceFile, BreakStmt *breakStmt,
356+
Optional<AnyFunctionRef> enclosingFunc) {
357+
TopCollection<unsigned, LabeledStmt *> labelCorrections(3);
358+
359+
// Pick the nearest break target that matches the specified name.
360+
auto activeLabeledStmts = ASTScope::lookupLabeledStmts(
361+
sourceFile, breakStmt->getStartLoc());
362+
if (breakStmt->getTargetName().empty()) {
363+
for (auto labeledStmt : activeLabeledStmts) {
364+
// 'break' with no label looks through non-loop structures
365+
// except 'switch'.
366+
if (!labeledStmt->requiresLabelOnJump()) {
367+
return labeledStmt;
368+
}
369+
}
370+
} else {
371+
// Scan inside out until we find something with the right label.
372+
for (auto labeledStmt : activeLabeledStmts) {
373+
if (breakStmt->getTargetName() == labeledStmt->getLabelInfo().Name) {
374+
return labeledStmt;
375+
}
376+
377+
unsigned distance =
378+
TypeChecker::getCallEditDistance(
379+
DeclNameRef(breakStmt->getTargetName()),
380+
labeledStmt->getLabelInfo().Name,
381+
TypeChecker::UnreasonableCallEditDistance);
382+
if (distance < TypeChecker::UnreasonableCallEditDistance)
383+
labelCorrections.insert(distance, std::move(labeledStmt));
384+
}
385+
labelCorrections.filterMaxScoreRange(
386+
TypeChecker::MaxCallEditDistanceFromBestCandidate);
387+
}
388+
389+
// If we're in a defer, produce a tailored diagnostic.
390+
if (isDefer(enclosingFunc)) {
391+
ctx.Diags.diagnose(
392+
breakStmt->getLoc(), diag::jump_out_of_defer, "break");
393+
return nullptr;
394+
}
395+
396+
if (breakStmt->getTargetName().empty()) {
397+
// If we're dealing with an unlabeled break inside of an 'if' or 'do'
398+
// statement, produce a more specific error.
399+
if (llvm::any_of(activeLabeledStmts,
400+
[&](Stmt *S) -> bool {
401+
return isa<IfStmt>(S) || isa<DoStmt>(S);
402+
})) {
403+
ctx.Diags.diagnose(
404+
breakStmt->getLoc(), diag::unlabeled_break_outside_loop);
405+
return nullptr;
406+
}
407+
408+
// Otherwise produce a generic error.
409+
ctx.Diags.diagnose(breakStmt->getLoc(), diag::break_outside_loop);
410+
return nullptr;
411+
}
412+
413+
// Provide potential corrections for an incorrect label.
414+
emitUnresolvedLabelDiagnostics(
415+
ctx.Diags, breakStmt->getTargetLoc(), breakStmt->getTargetName(),
416+
labelCorrections);
417+
return nullptr;
418+
}
419+
314420
namespace {
315421
class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
316422
public:
@@ -423,10 +529,7 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
423529
//===--------------------------------------------------------------------===//
424530

425531
bool isInDefer() const {
426-
if (!TheFunc.hasValue()) return false;
427-
auto *FD = dyn_cast_or_null<FuncDecl>
428-
(TheFunc.getValue().getAbstractFunctionDecl());
429-
return FD && FD->isDeferBody();
532+
return isDefer(TheFunc);
430533
}
431534

432535
template<typename StmtTy>
@@ -739,67 +842,9 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
739842
}
740843

741844
Stmt *visitBreakStmt(BreakStmt *S) {
742-
LabeledStmt *Target = nullptr;
743-
TopCollection<unsigned, LabeledStmt *> labelCorrections(3);
744-
// Pick the nearest break target that matches the specified name.
745-
if (S->getTargetName().empty()) {
746-
for (auto I = ActiveLabeledStmts.rbegin(), E = ActiveLabeledStmts.rend();
747-
I != E; ++I) {
748-
// 'break' with no label looks through non-loop structures
749-
// except 'switch'.
750-
if (!(*I)->requiresLabelOnJump()) {
751-
Target = *I;
752-
break;
753-
}
754-
}
845+
if (auto target = findBreakStmtTarget(getASTContext(), DC->getParentSourceFile(), S, TheFunc))
846+
S->setTarget(target);
755847

756-
} else {
757-
// Scan inside out until we find something with the right label.
758-
for (auto I = ActiveLabeledStmts.rbegin(), E = ActiveLabeledStmts.rend();
759-
I != E; ++I) {
760-
if (S->getTargetName() == (*I)->getLabelInfo().Name) {
761-
Target = *I;
762-
break;
763-
} else {
764-
unsigned distance =
765-
TypeChecker::getCallEditDistance(
766-
DeclNameRef(S->getTargetName()), (*I)->getLabelInfo().Name,
767-
TypeChecker::UnreasonableCallEditDistance);
768-
if (distance < TypeChecker::UnreasonableCallEditDistance)
769-
labelCorrections.insert(distance, std::move(*I));
770-
}
771-
}
772-
labelCorrections.filterMaxScoreRange(
773-
TypeChecker::MaxCallEditDistanceFromBestCandidate);
774-
}
775-
776-
if (!Target) {
777-
// If we're in a defer, produce a tailored diagnostic.
778-
if (isInDefer()) {
779-
getASTContext().Diags.diagnose(S->getLoc(),
780-
diag::jump_out_of_defer, "break");
781-
} else if (S->getTargetName().empty()) {
782-
// If we're dealing with an unlabeled break inside of an 'if' or 'do'
783-
// statement, produce a more specific error.
784-
if (std::any_of(ActiveLabeledStmts.rbegin(),
785-
ActiveLabeledStmts.rend(),
786-
[&](Stmt *S) -> bool {
787-
return isa<IfStmt>(S) || isa<DoStmt>(S);
788-
})) {
789-
getASTContext().Diags.diagnose(S->getLoc(),
790-
diag::unlabeled_break_outside_loop);
791-
} else {
792-
// Otherwise produce a generic error.
793-
getASTContext().Diags.diagnose(S->getLoc(), diag::break_outside_loop);
794-
}
795-
} else {
796-
emitUnresolvedLabelDiagnostics(getASTContext().Diags,
797-
S->getTargetLoc(), S->getTargetName(),
798-
labelCorrections);
799-
}
800-
return nullptr;
801-
}
802-
S->setTarget(Target);
803848
return S;
804849
}
805850

@@ -866,34 +911,6 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
866911
return S;
867912
}
868913

869-
static void
870-
emitUnresolvedLabelDiagnostics(DiagnosticEngine &DE,
871-
SourceLoc targetLoc, Identifier targetName,
872-
TopCollection<unsigned, LabeledStmt *> corrections) {
873-
// If an unresolved label was used, but we have a single correction,
874-
// produce the specific diagnostic and fixit.
875-
if (corrections.size() == 1) {
876-
DE.diagnose(targetLoc, diag::unresolved_label_corrected,
877-
targetName, corrections.begin()->Value->getLabelInfo().Name)
878-
.highlight(SourceRange(targetLoc))
879-
.fixItReplace(SourceRange(targetLoc),
880-
corrections.begin()->Value->getLabelInfo().Name.str());
881-
DE.diagnose(corrections.begin()->Value->getLabelInfo().Loc,
882-
diag::decl_declared_here,
883-
corrections.begin()->Value->getLabelInfo().Name);
884-
} else {
885-
// If we have multiple corrections or none, produce a generic diagnostic
886-
// and all corrections available.
887-
DE.diagnose(targetLoc, diag::unresolved_label, targetName)
888-
.highlight(SourceRange(targetLoc));
889-
for (auto &entry : corrections)
890-
DE.diagnose(entry.Value->getLabelInfo().Loc, diag::note_typo_candidate,
891-
entry.Value->getLabelInfo().Name.str())
892-
.fixItReplace(SourceRange(targetLoc),
893-
entry.Value->getLabelInfo().Name.str());
894-
}
895-
}
896-
897914
Stmt *visitFallthroughStmt(FallthroughStmt *S) {
898915
if (!SwitchLevel) {
899916
getASTContext().Diags.diagnose(S->getLoc(),

0 commit comments

Comments
 (0)