Skip to content

SILGen/Sema: Avoid diagnosing @unknown default switch cases as unreachable #72213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/SILGen/SILGenPattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1136,7 +1136,7 @@ void PatternMatchEmission::emitDispatch(ClauseMatrix &clauses, ArgArray args,
return item.getPattern()->getKind() == PatternKind::Expr;
});
isParentDoCatch = CS->getParentKind() == CaseParentKind::DoCatch;
isDefault = CS->isDefault();
isDefault = CS->isDefault() && !CS->hasUnknownAttr();
}
} else {
Loc = clauses[firstRow].getCasePattern()->getStartLoc();
Expand Down
12 changes: 7 additions & 5 deletions lib/Sema/TypeCheckSwitchStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1058,16 +1058,18 @@ namespace {
unknownCase);
return;
}

auto uncovered = diff.value();
if (unknownCase && uncovered.isEmpty()) {
DE.diagnose(unknownCase->getLoc(), diag::redundant_particular_case)
.highlight(unknownCase->getSourceRange());
}

// Account for unknown cases. If the developer wrote `unknown`, they're
// all handled; otherwise, we ignore the ones that were added for enums
// that are implicitly frozen.
//
// Note that we do not diagnose an unknown case as redundant, even if the
// uncovered space is empty because we trust that if the developer went to
// the trouble of writing @unknown that it was for a good reason, like
// addressing diagnostics in another build configuration where there are
// potentially unknown cases.
uncovered = *uncovered.minus(Space::forUnknown(unknownCase == nullptr),
DC, /*&minusCount*/ nullptr);

Expand Down
12 changes: 6 additions & 6 deletions test/Compatibility/exhaustive_switch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -872,7 +872,7 @@ public func testNonExhaustive(_ value: NonExhaustive, _ payload: NonExhaustivePa

switch value {
case _: break
@unknown case _: break // expected-warning {{case is already handled by previous patterns; consider removing it}}
@unknown case _: break
}

// Test being part of other spaces.
Expand Down Expand Up @@ -973,25 +973,25 @@ public func testNonExhaustive(_ value: NonExhaustive, _ payload: NonExhaustivePa
switch interval {
case .seconds, .milliseconds, .microseconds, .nanoseconds: break
case .never: break
@unknown case _: break // expected-warning {{case is already handled by previous patterns; consider removing it}}
@unknown case _: break
}

switch flag {
case true: break
case false: break
@unknown case _: break // expected-warning {{case is already handled by previous patterns; consider removing it}}
@unknown case _: break
}

switch flag as Optional {
case _?: break
case nil: break
@unknown case _: break // expected-warning {{case is already handled by previous patterns; consider removing it}}
@unknown case _: break
}

switch (flag, value) {
case (true, _): break
case (false, _): break
@unknown case _: break // expected-warning {{case is already handled by previous patterns; consider removing it}}
@unknown case _: break
}
}

Expand Down Expand Up @@ -1028,7 +1028,7 @@ public func testNonExhaustiveWithinModule(_ value: NonExhaustive, _ payload: Non

switch value {
case _: break
@unknown case _: break // expected-warning {{case is already handled by previous patterns; consider removing it}}
@unknown case _: break
}

// Test being part of other spaces.
Expand Down
152 changes: 152 additions & 0 deletions test/SILOptimizer/diagnose_unreachable_enum_resilience.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// RUN: %target-swift-frontend -emit-sil -swift-version 5 -enable-library-evolution -primary-file %s -o /dev/null -verify

// Re-run with optimizations to check if -O does not make any difference
// RUN: %target-swift-frontend -O -emit-sil -swift-version 5 -enable-library-evolution -primary-file %s -o /dev/null -verify

public enum NonExhaustive {
case a
}

@frozen public enum Exhaustive {
case a
}

public var optional: Void? = nil

public func testNonExhaustive(_ e: NonExhaustive) {
switch e {
case .a: break
}

switch e {
case .a: break
default: break
// expected-warning@-1 {{default will never be executed}}
}

switch e {
case .a: break
@unknown default: break
// Ok, @unknown suppresses "default will never be executed"
}

switch (e, optional) {
case (.a, _): break
}

switch (e, optional) {
case (.a, _): break
default: break
// expected-warning@-1 {{default will never be executed}}
}

switch (e, optional) {
case (.a, _): break
@unknown default: break
// Ok, @unknown suppresses "default will never be executed"
}
}

@inlinable
public func testNonExhaustiveInlinable(_ e: NonExhaustive) {
switch e {
// expected-warning@-1 {{switch covers known cases, but 'NonExhaustive' may have additional unknown values}}
// expected-note@-2 {{handle unknown values using "@unknown default"}}
case .a: break
}

switch e {
case .a: break
default: break
}

switch e {
case .a: break
@unknown default: break
}

switch (e, optional) {
// expected-warning@-1 {{switch covers known cases, but '(NonExhaustive, Void?)' may have additional unknown values}}
// expected-note@-2 {{add missing case: '(_, _)'}}
case (.a, _): break
}

switch (e, optional) {
case (.a, _): break
default: break
}

switch (e, optional) {
case (.a, _): break
@unknown default: break
}
}

public func testExhaustive(_ e: Exhaustive) {
switch e {
case .a: break
}

switch e {
case .a: break
default: break
// expected-warning@-1 {{default will never be executed}}
}

switch e {
case .a: break
@unknown default: break
// Ok, @unknown suppresses "default will never be executed"
}

switch (e, optional) {
case (.a, _): break
}

switch (e, optional) {
case (.a, _): break
default: break
// expected-warning@-1 {{default will never be executed}}
}

switch (e, optional) {
case (.a, _): break
@unknown default: break
// Ok, @unknown suppresses "default will never be executed"
}
}

@inlinable
public func testExhaustiveInlinable(_ e: Exhaustive) {
switch e {
case .a: break
}

switch e {
case .a: break
default: break
// expected-warning@-1 {{default will never be executed}}
}

switch e {
case .a: break
@unknown default: break
// Ok, @unknown suppresses "default will never be executed"
}

switch (e, optional) {
case (.a, _): break
}

switch (e, optional) {
case (.a, _): break
default: break
// expected-warning@-1 {{default will never be executed}}
}

switch (e, optional) {
case (.a, _): break
@unknown default: break
// Ok, @unknown suppresses "default will never be executed"
}
}
12 changes: 6 additions & 6 deletions test/Sema/exhaustive_switch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,7 @@ public func testNonExhaustive(_ value: NonExhaustive, _ payload: NonExhaustivePa

switch value {
case _: break
@unknown case _: break // expected-warning {{case is already handled by previous patterns; consider removing it}}
@unknown case _: break
}

// Test being part of other spaces.
Expand Down Expand Up @@ -939,25 +939,25 @@ public func testNonExhaustive(_ value: NonExhaustive, _ payload: NonExhaustivePa
switch interval {
case .seconds, .milliseconds, .microseconds, .nanoseconds: break
case .never: break
@unknown case _: break // expected-warning {{case is already handled by previous patterns; consider removing it}}
@unknown case _: break
}

switch flag {
case true: break
case false: break
@unknown case _: break // expected-warning {{case is already handled by previous patterns; consider removing it}}
@unknown case _: break
}

switch flag as Optional {
case _?: break
case nil: break
@unknown case _: break // expected-warning {{case is already handled by previous patterns; consider removing it}}
@unknown case _: break
}

switch (flag, value) {
case (true, _): break
case (false, _): break
@unknown case _: break // expected-warning {{case is already handled by previous patterns; consider removing it}}
@unknown case _: break
}
}

Expand Down Expand Up @@ -994,7 +994,7 @@ public func testNonExhaustiveWithinModule(_ value: NonExhaustive, _ payload: Non

switch value {
case _: break
@unknown case _: break // expected-warning {{case is already handled by previous patterns; consider removing it}}
@unknown case _: break
}

// Test being part of other spaces.
Expand Down
4 changes: 2 additions & 2 deletions test/Sema/exhaustive_switch_debugger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public func testNonExhaustive(_ value: NonExhaustive, _ payload: NonExhaustivePa

switch value {
case _: break
@unknown case _: break // expected-warning {{case is already handled by previous patterns; consider removing it}}
@unknown case _: break
}

// Test being part of other spaces.
Expand Down Expand Up @@ -144,7 +144,7 @@ public func testNonExhaustiveWithinModule(_ value: NonExhaustive, _ payload: Non

switch value {
case _: break
@unknown case _: break // expected-warning {{case is already handled by previous patterns; consider removing it}}
@unknown case _: break
}

// Test being part of other spaces.
Expand Down