Skip to content

Commit 1f14d03

Browse files
committed
Enforce restrictions on '@unknown'
- Must be a single pattern - Must be last in the switch - Must not have a 'where' clause - Must specifically be the "any" pattern if using 'case' We may lift some of these restrictions in the future, but that would need a new proposal.
1 parent ceb51ee commit 1f14d03

File tree

3 files changed

+102
-2
lines changed

3 files changed

+102
-2
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2944,6 +2944,15 @@ ERROR(type_mismatch_multiple_pattern_list,none,
29442944
ERROR(type_mismatch_fallthrough_pattern_list,none,
29452945
"pattern variable bound to type %0, fallthrough case bound to type %1", (Type, Type))
29462946

2947+
ERROR(unknown_case_must_be_catchall,none,
2948+
"'@unknown' is only supported for catch-all cases (\"case _\")", ())
2949+
ERROR(unknown_case_where_clause,none,
2950+
"'where' cannot be used with '@unknown'", ())
2951+
ERROR(unknown_case_multiple_patterns,none,
2952+
"'@unknown' cannot be applied to multiple patterns", ())
2953+
ERROR(unknown_case_must_be_last,none,
2954+
"'@unknown' can only be applied to the last case in a switch", ())
2955+
29472956
WARNING(where_on_one_item, none,
29482957
"'where' only applies to the second pattern match in this case", ())
29492958

lib/Sema/TypeCheckStmt.cpp

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -913,7 +913,38 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
913913
labelItem.setGuardExpr(guard);
914914
}
915915
}
916-
916+
917+
// Check restrictions on '@unknown'.
918+
if (caseBlock->hasUnknownAttr()) {
919+
if (caseBlock->getCaseLabelItems().size() != 1) {
920+
assert(!caseBlock->getCaseLabelItems().empty() &&
921+
"parser should not produce case blocks with no items");
922+
TC.diagnose(caseBlock->getLoc(),
923+
diag::unknown_case_multiple_patterns)
924+
.highlight(caseBlock->getCaseLabelItems()[1].getSourceRange());
925+
}
926+
927+
if (!caseBlock->isDefault() && FallthroughDest != nullptr) {
928+
TC.diagnose(caseBlock->getLoc(),
929+
diag::unknown_case_must_be_last);
930+
}
931+
932+
const CaseLabelItem &labelItem = caseBlock->getCaseLabelItems().front();
933+
if (labelItem.getGuardExpr() && !labelItem.isDefault()) {
934+
TC.diagnose(labelItem.getStartLoc(),
935+
diag::unknown_case_where_clause)
936+
.highlight(labelItem.getGuardExpr()->getSourceRange());
937+
}
938+
939+
const Pattern *pattern =
940+
labelItem.getPattern()->getSemanticsProvidingPattern();
941+
if (!isa<AnyPattern>(pattern)) {
942+
TC.diagnose(labelItem.getStartLoc(),
943+
diag::unknown_case_must_be_catchall)
944+
.highlight(pattern->getSourceRange());
945+
}
946+
}
947+
917948
// If the previous case fellthrough, similarly check that that case's bindings
918949
// includes our first label item's pattern bindings and types.
919950
if (PreviousFallthrough) {

test/Parse/switch.swift

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ case .Thing:
388388
switch Whatever.Thing {
389389
default:
390390
x = 0
391-
@unknown case _: // expected-error{{additional 'case' blocks cannot appear after the 'default' block of a 'switch'}}
391+
@unknown case _: // expected-error{{additional 'case' blocks cannot appear after the 'default' block of a 'switch'}} expected-error {{'@unknown' can only be applied to the last case in a switch}}
392392
x = 0
393393
case .Thing:
394394
x = 0
@@ -413,6 +413,66 @@ switch Whatever.Thing { // expected-warning {{switch must be exhaustive}} expect
413413
fallthrough // expected-error{{'fallthrough' without a following 'case' or 'default' block}}
414414
}
415415

416+
switch Whatever.Thing {
417+
@unknown case _: // expected-error {{'@unknown' can only be applied to the last case in a switch}}
418+
fallthrough
419+
case .Thing:
420+
break
421+
}
422+
423+
switch Whatever.Thing {
424+
@unknown default:
425+
fallthrough
426+
case .Thing: // expected-error{{additional 'case' blocks cannot appear after the 'default' block of a 'switch'}}
427+
break
428+
}
429+
430+
switch Whatever.Thing { // expected-warning {{switch must be exhaustive}} expected-note{{add missing case: '.Thing'}}
431+
@unknown case _, _: // expected-error {{'@unknown' cannot be applied to multiple patterns}}
432+
break
433+
}
434+
435+
switch Whatever.Thing { // expected-warning {{switch must be exhaustive}} expected-note{{add missing case: '.Thing'}}
436+
@unknown case _, _, _: // expected-error {{'@unknown' cannot be applied to multiple patterns}}
437+
break
438+
}
439+
440+
switch Whatever.Thing { // expected-warning {{switch must be exhaustive}} expected-note{{add missing case: '.Thing'}}
441+
@unknown case let value: // expected-error {{'@unknown' is only supported for catch-all cases ("case _")}}
442+
_ = value
443+
}
444+
445+
switch (Whatever.Thing, Whatever.Thing) { // expected-warning {{switch must be exhaustive}} expected-note{{add missing case: '(_, _)'}}
446+
@unknown case (_, _): // expected-error {{'@unknown' is only supported for catch-all cases ("case _")}}
447+
break
448+
}
449+
450+
switch Whatever.Thing { // expected-warning {{switch must be exhaustive}} expected-note{{add missing case: '.Thing'}}
451+
@unknown case is Whatever: // expected-error {{'@unknown' is only supported for catch-all cases ("case _")}}
452+
// expected-warning@-1 {{'is' test is always true}}
453+
break
454+
}
455+
456+
switch Whatever.Thing { // expected-warning {{switch must be exhaustive}} expected-note{{add missing case: '.Thing'}}
457+
@unknown case .Thing: // expected-error {{'@unknown' is only supported for catch-all cases ("case _")}}
458+
break
459+
}
460+
461+
switch Whatever.Thing { // expected-warning {{switch must be exhaustive}} expected-note{{add missing case: '.Thing'}}
462+
@unknown case (_): // okay
463+
break
464+
}
465+
466+
switch Whatever.Thing { // expected-warning {{switch must be exhaustive}} expected-note{{add missing case: '.Thing'}}
467+
@unknown case _ where x == 0: // expected-error {{'where' cannot be used with '@unknown'}}
468+
break
469+
}
470+
471+
switch Whatever.Thing { // expected-warning {{switch must be exhaustive}} expected-note{{add missing case: '.Thing'}}
472+
@unknown default where x == 0: // expected-error {{'default' cannot be used with a 'where' guard expression}}
473+
break
474+
}
475+
416476
switch Whatever.Thing {
417477
case .Thing:
418478
x = 0

0 commit comments

Comments
 (0)