Skip to content

Commit d12d3c1

Browse files
authored
FixCode: add a fixit to insert the missing enum element cases in switch statements. (#7759)
This doesn't work on entirely empty switch block because we reject such statement during parsing.
1 parent ba92028 commit d12d3c1

File tree

3 files changed

+227
-1
lines changed

3 files changed

+227
-1
lines changed

lib/SILOptimizer/Mandatory/DataflowDiagnostics.cpp

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "swift/SILOptimizer/PassManager/Passes.h"
1414
#include "swift/SILOptimizer/PassManager/Transforms.h"
1515
#include "swift/AST/ASTContext.h"
16+
#include "swift/AST/ASTPrinter.h"
1617
#include "swift/AST/DiagnosticEngine.h"
1718
#include "swift/AST/DiagnosticsSIL.h"
1819
#include "swift/AST/Expr.h"
@@ -21,6 +22,8 @@
2122
#include "swift/SIL/SILLocation.h"
2223
#include "swift/SIL/SILModule.h"
2324
#include "swift/SIL/SILVisitor.h"
25+
#include "swift/Syntax/TokenKinds.h"
26+
2427
using namespace swift;
2528

2629
template<typename...T, typename...U>
@@ -57,6 +60,88 @@ static void diagnoseMissingReturn(const UnreachableInst *UI,
5760
}
5861

5962

63+
/// If the pattern handles an enum element, remove the enum element from the
64+
/// given set. If seeing uncertain patterns, e.g. any pattern, return true;
65+
/// otherwise return false.
66+
static bool
67+
removedHandledEnumElements(Pattern *P,
68+
llvm::DenseSet<EnumElementDecl*> &UnhandledElements) {
69+
if (auto *EEP = dyn_cast<EnumElementPattern>(P)) {
70+
UnhandledElements.erase(EEP->getElementDecl());
71+
} else if (auto *VP = dyn_cast<VarPattern>(P)) {
72+
return removedHandledEnumElements(VP->getSubPattern(), UnhandledElements);
73+
} else {
74+
return true;
75+
}
76+
return false;
77+
};
78+
79+
static void diagnoseMissingCases(ASTContext &Context,
80+
const SwitchStmt *SwitchS) {
81+
SourceLoc EndLoc = SwitchS->getEndLoc();
82+
StringRef Placeholder = "<#Code#>";
83+
SmallString<32> Buffer;
84+
llvm::raw_svector_ostream OS(Buffer);
85+
86+
auto DefaultDiag = [&]() {
87+
OS << tok::kw_default << ": " << Placeholder << "\n";
88+
Context.Diags.diagnose(EndLoc, diag::non_exhaustive_switch).
89+
fixItInsert(EndLoc, Buffer.str());
90+
};
91+
// To find the subject enum decl for this switch statement.
92+
EnumDecl *SubjectED = nullptr;
93+
if (auto SubjectTy = SwitchS->getSubjectExpr()->getType()) {
94+
if (auto *ND = SubjectTy->getAnyNominal()) {
95+
SubjectED = ND->getAsEnumOrEnumExtensionContext();
96+
}
97+
}
98+
99+
// The switch is not about an enum decl, add "default:" instead.
100+
if (!SubjectED) {
101+
DefaultDiag();
102+
return;
103+
}
104+
105+
// Assume enum elements are not handled in the switch statement.
106+
llvm::DenseSet<EnumElementDecl*> UnhandledElements;
107+
108+
SubjectED->getAllElements(UnhandledElements);
109+
for (auto Current : SwitchS->getCases()) {
110+
// For each handled enum element, remove it from the bucket.
111+
for (auto Item : Current->getCaseLabelItems()) {
112+
if (removedHandledEnumElements(Item.getPattern(), UnhandledElements)) {
113+
DefaultDiag();
114+
return;
115+
}
116+
}
117+
}
118+
119+
// No unhandled cases, so we are not sure the exact case to add, fall back to
120+
// default instead.
121+
if (UnhandledElements.empty()) {
122+
DefaultDiag();
123+
return;
124+
}
125+
126+
// Sort the missing elements to a vector because set does not guarantee orders.
127+
SmallVector<EnumElementDecl*, 4> SortedElements;
128+
SortedElements.insert(SortedElements.begin(), UnhandledElements.begin(),
129+
UnhandledElements.end());
130+
std::sort(SortedElements.begin(),SortedElements.end(),
131+
[](EnumElementDecl *LHS, EnumElementDecl *RHS) {
132+
return LHS->getNameStr().compare(RHS->getNameStr()) < 0;
133+
});
134+
135+
// Print each enum element name.
136+
std::for_each(SortedElements.begin(), SortedElements.end(),
137+
[&](EnumElementDecl *EE) {
138+
OS << tok::kw_case << " ." << EE->getNameStr() << ": " <<
139+
Placeholder << "\n";
140+
});
141+
Context.Diags.diagnose(EndLoc, diag::non_exhaustive_switch).
142+
fixItInsert(EndLoc, Buffer.str());
143+
}
144+
60145
static void diagnoseUnreachable(const SILInstruction *I,
61146
ASTContext &Context) {
62147
if (auto *UI = dyn_cast<UnreachableInst>(I)) {
@@ -83,7 +168,7 @@ static void diagnoseUnreachable(const SILInstruction *I,
83168

84169
// A non-exhaustive switch would also produce an unreachable instruction.
85170
if (L.isASTNode<SwitchStmt>()) {
86-
diagnose(Context, L.getEndSourceLoc(), diag::non_exhaustive_switch);
171+
diagnoseMissingCases(Context, L.getAsASTNode<SwitchStmt>());
87172
return;
88173
}
89174

test/FixCode/fixits-switch.swift

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// RUN: not %swift -emit-sil -target %target-triple %s -emit-fixits-path %t.remap -I %S/Inputs
2+
// RUN: c-arcmt-test %t.remap | arcmt-test -verify-transformed-files %s.result
3+
4+
enum E1 : Int {
5+
case e1
6+
case e2
7+
case e3
8+
case e4
9+
}
10+
11+
func foo1(_ e : E1) -> Int {
12+
switch(e) {
13+
case .e1:
14+
return 1
15+
}
16+
}
17+
18+
func foo2(_ i : Int) -> Int {
19+
switch i {
20+
case 1:
21+
return 1
22+
}
23+
}
24+
25+
func foo3(_ c : Character) -> Character {
26+
switch c {
27+
case "a":
28+
return "a"
29+
}
30+
}
31+
32+
enum E2 {
33+
case e1(a: Int, s: Int)
34+
case e2(a: Int)
35+
case e3(a: Int)
36+
}
37+
38+
func foo4(_ e : E2) -> Int {
39+
switch e {
40+
case .e2:
41+
return 1
42+
}
43+
}
44+
45+
func foo5(_ e : E1) -> Int {
46+
switch e {
47+
case _ where e.rawValue > 0:
48+
return 1
49+
}
50+
}
51+
52+
func foo6(_ e : E2) -> Int {
53+
switch e {
54+
case let .e1(x, y):
55+
return x + y
56+
}
57+
}
58+
59+
func foo7(_ e : E2) -> Int {
60+
switch e {
61+
case .e2(1): return 0
62+
case .e1: return 0
63+
case .e3: return 0
64+
}
65+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// RUN: not %swift -emit-sil -target %target-triple %s -emit-fixits-path %t.remap -I %S/Inputs
2+
// RUN: c-arcmt-test %t.remap | arcmt-test -verify-transformed-files %s.result
3+
4+
enum E1 : Int {
5+
case e1
6+
case e2
7+
case e3
8+
case e4
9+
}
10+
11+
func foo1(_ e : E1) -> Int {
12+
switch(e) {
13+
case .e1:
14+
return 1
15+
case .e2: <#Code#>
16+
case .e3: <#Code#>
17+
case .e4: <#Code#>
18+
}
19+
}
20+
21+
func foo2(_ i : Int) -> Int {
22+
switch i {
23+
case 1:
24+
return 1
25+
default: <#Code#>
26+
}
27+
}
28+
29+
func foo3(_ c : Character) -> Character {
30+
switch c {
31+
case "a":
32+
return "a"
33+
default: <#Code#>
34+
}
35+
}
36+
37+
enum E2 {
38+
case e1(a: Int, s: Int)
39+
case e2(a: Int)
40+
case e3(a: Int)
41+
}
42+
43+
func foo4(_ e : E2) -> Int {
44+
switch e {
45+
case .e2:
46+
return 1
47+
case .e1: <#Code#>
48+
case .e3: <#Code#>
49+
}
50+
}
51+
52+
func foo5(_ e : E1) -> Int {
53+
switch e {
54+
case _ where e.rawValue > 0:
55+
return 1
56+
default: <#Code#>
57+
}
58+
}
59+
60+
func foo6(_ e : E2) -> Int {
61+
switch e {
62+
case let .e1(x, y):
63+
return x + y
64+
case .e2: <#Code#>
65+
case .e3: <#Code#>
66+
}
67+
}
68+
69+
func foo7(_ e : E2) -> Int {
70+
switch e {
71+
case .e2(1): return 0
72+
case .e1: return 0
73+
case .e3: return 0
74+
default: <#Code#>
75+
}
76+
}

0 commit comments

Comments
 (0)