Skip to content

Commit b644cd8

Browse files
committed
[Sema] Ensure performStmtDiagnostics is called for CaseStmts
Previously we would check if we have a SwitchStmt, and apply diagnostics such as `checkExistentialTypes` to the CaseStmts individually. This however would have been missed for `catch` statements. The change to consistently call `performStmtDiagnostics` in closures fixed this for `do-catch`'s in closures, this commit fixes it for those outside of closures. Because this is source breaking, the existential diagnostic is downgraded to a warning until Swift 7 for catch statements specifically. While here, also apply the ambiguous where clause diagnostic to `catch` statements.
1 parent 964ba73 commit b644cd8

File tree

7 files changed

+246
-59
lines changed

7 files changed

+246
-59
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5105,7 +5105,8 @@ ERROR(unknown_case_must_be_last,none,
51055105
"'@unknown' can only be applied to the last case in a switch", ())
51065106

51075107
WARNING(where_on_one_item, none,
5108-
"'where' only applies to the second pattern match in this case", ())
5108+
"'where' only applies to the second pattern match in this "
5109+
"'%select{case|catch}0'", (bool))
51095110

51105111
NOTE(add_where_newline, none,
51115112
"disambiguate by adding a line break between them if this is desired", ())

lib/Sema/MiscDiagnostics.cpp

Lines changed: 50 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4419,61 +4419,57 @@ void swift::performAbstractFuncDeclDiagnostics(AbstractFunctionDecl *AFD) {
44194419
}
44204420
}
44214421

4422-
// Perform MiscDiagnostics on Switch Statements.
4423-
static void checkSwitch(ASTContext &ctx, const SwitchStmt *stmt,
4424-
DeclContext *DC) {
4425-
// We want to warn about "case .Foo, .Bar where 1 != 100:" since the where
4426-
// clause only applies to the second case, and this is surprising.
4427-
for (auto cs : stmt->getCases()) {
4428-
TypeChecker::checkExistentialTypes(ctx, cs, DC);
4429-
4430-
// The case statement can have multiple case items, each can have a where.
4431-
// If we find a "where", and there is a preceding item without a where, and
4432-
// if they are on the same source line, then warn.
4433-
auto items = cs->getCaseLabelItems();
4434-
4435-
// Don't do any work for the vastly most common case.
4436-
if (items.size() == 1) continue;
4437-
4438-
// Ignore the first item, since it can't have preceding ones.
4439-
for (unsigned i = 1, e = items.size(); i != e; ++i) {
4440-
// Must have a where clause.
4441-
auto where = items[i].getGuardExpr();
4442-
if (!where)
4443-
continue;
4444-
4445-
// Preceding item must not.
4446-
if (items[i-1].getGuardExpr())
4447-
continue;
4448-
4449-
// Must be on the same source line.
4450-
auto prevLoc = items[i-1].getStartLoc();
4451-
auto thisLoc = items[i].getStartLoc();
4452-
if (prevLoc.isInvalid() || thisLoc.isInvalid())
4453-
continue;
4454-
4455-
auto &SM = ctx.SourceMgr;
4456-
auto prevLineCol = SM.getLineAndColumnInBuffer(prevLoc);
4457-
if (SM.getLineAndColumnInBuffer(thisLoc).first != prevLineCol.first)
4458-
continue;
4422+
static void diagnoseCaseStmtAmbiguousWhereClause(const CaseStmt *CS,
4423+
ASTContext &ctx) {
4424+
// The case statement can have multiple case items, each can have a where.
4425+
// If we find a "where", and there is a preceding item without a where, and
4426+
// if they are on the same source line, e.g
4427+
// "case .Foo, .Bar where 1 != 100:" then warn since it may be unexpected.
4428+
auto items = CS->getCaseLabelItems();
4429+
4430+
// Don't do any work for the vastly most common case.
4431+
if (items.size() == 1)
4432+
return;
44594433

4460-
ctx.Diags.diagnose(items[i].getWhereLoc(), diag::where_on_one_item)
4434+
// Ignore the first item, since it can't have preceding ones.
4435+
for (unsigned i = 1, e = items.size(); i != e; ++i) {
4436+
// Must have a where clause.
4437+
auto where = items[i].getGuardExpr();
4438+
if (!where)
4439+
continue;
4440+
4441+
// Preceding item must not.
4442+
if (items[i - 1].getGuardExpr())
4443+
continue;
4444+
4445+
// Must be on the same source line.
4446+
auto prevLoc = items[i - 1].getStartLoc();
4447+
auto thisLoc = items[i].getStartLoc();
4448+
if (prevLoc.isInvalid() || thisLoc.isInvalid())
4449+
continue;
4450+
4451+
auto &SM = ctx.SourceMgr;
4452+
auto prevLineCol = SM.getLineAndColumnInBuffer(prevLoc);
4453+
if (SM.getLineAndColumnInBuffer(thisLoc).first != prevLineCol.first)
4454+
continue;
4455+
4456+
ctx.Diags
4457+
.diagnose(items[i].getWhereLoc(), diag::where_on_one_item,
4458+
CS->getParentKind() == CaseParentKind::DoCatch)
44614459
.highlight(items[i].getPattern()->getSourceRange())
44624460
.highlight(where->getSourceRange());
4463-
4464-
// Whitespace it out to the same column as the previous item.
4465-
std::string whitespace(prevLineCol.second-1, ' ');
4466-
ctx.Diags.diagnose(thisLoc, diag::add_where_newline)
4467-
.fixItInsert(thisLoc, "\n"+whitespace);
4468-
4469-
auto whereRange = SourceRange(items[i].getWhereLoc(),
4470-
where->getEndLoc());
4471-
auto charRange = Lexer::getCharSourceRangeFromSourceRange(SM, whereRange);
4472-
auto whereText = SM.extractText(charRange);
4473-
ctx.Diags.diagnose(prevLoc, diag::duplicate_where)
4474-
.fixItInsertAfter(items[i-1].getEndLoc(), " " + whereText.str())
4475-
.highlight(items[i-1].getSourceRange());
4476-
}
4461+
4462+
// Whitespace it out to the same column as the previous item.
4463+
std::string whitespace(prevLineCol.second - 1, ' ');
4464+
ctx.Diags.diagnose(thisLoc, diag::add_where_newline)
4465+
.fixItInsert(thisLoc, "\n" + whitespace);
4466+
4467+
auto whereRange = SourceRange(items[i].getWhereLoc(), where->getEndLoc());
4468+
auto charRange = Lexer::getCharSourceRangeFromSourceRange(SM, whereRange);
4469+
auto whereText = SM.extractText(charRange);
4470+
ctx.Diags.diagnose(prevLoc, diag::duplicate_where)
4471+
.fixItInsertAfter(items[i - 1].getEndLoc(), " " + whereText.str())
4472+
.highlight(items[i - 1].getSourceRange());
44774473
}
44784474
}
44794475

@@ -6194,8 +6190,8 @@ void swift::performStmtDiagnostics(const Stmt *S, DeclContext *DC) {
61946190

61956191
TypeChecker::checkExistentialTypes(ctx, const_cast<Stmt *>(S), DC);
61966192

6197-
if (auto switchStmt = dyn_cast<SwitchStmt>(S))
6198-
checkSwitch(ctx, switchStmt, DC);
6193+
if (auto *CS = dyn_cast<CaseStmt>(S))
6194+
diagnoseCaseStmtAmbiguousWhereClause(CS, ctx);
61996195

62006196
checkStmtConditionTrailingClosure(ctx, S);
62016197

lib/Sema/TypeCheckStmt.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1607,6 +1607,10 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
16071607
BraceStmt *body = caseBlock->getBody();
16081608
limitExhaustivityChecks |= typeCheckStmt(body);
16091609
caseBlock->setBody(body);
1610+
1611+
// CaseStmts don't go through typeCheckStmt, so manually call into
1612+
// performStmtDiagnostics.
1613+
performStmtDiagnostics(caseBlock, DC);
16101614
}
16111615
}
16121616

lib/Sema/TypeCheckType.cpp

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6143,13 +6143,16 @@ class ExistentialTypeSyntaxChecker : public ASTWalker {
61436143
ASTContext &Ctx;
61446144
bool checkStatements;
61456145
bool hitTopStmt;
6146+
bool warnUntilSwift7;
61466147

61476148
unsigned exprCount = 0;
61486149
llvm::SmallVector<TypeRepr *, 4> reprStack;
61496150

61506151
public:
6151-
ExistentialTypeSyntaxChecker(ASTContext &ctx, bool checkStatements)
6152-
: Ctx(ctx), checkStatements(checkStatements), hitTopStmt(false) {}
6152+
ExistentialTypeSyntaxChecker(ASTContext &ctx, bool checkStatements,
6153+
bool warnUntilSwift7 = false)
6154+
: Ctx(ctx), checkStatements(checkStatements), hitTopStmt(false),
6155+
warnUntilSwift7(warnUntilSwift7) {}
61536156

61546157
MacroWalking getMacroWalkingBehavior() const override {
61556158
return MacroWalking::ArgumentsAndExpansion;
@@ -6363,6 +6366,7 @@ class ExistentialTypeSyntaxChecker : public ASTWalker {
63636366
inverse && isAnyOrSomeMissing()) {
63646367
auto diag = Ctx.Diags.diagnose(inverse->getTildeLoc(),
63656368
diag::inverse_requires_any);
6369+
diag.warnUntilSwiftVersionIf(warnUntilSwift7, 7);
63666370
emitInsertAnyFixit(diag, T);
63676371
return;
63686372
}
@@ -6380,6 +6384,7 @@ class ExistentialTypeSyntaxChecker : public ASTWalker {
63806384
proto->getDeclaredInterfaceType(),
63816385
proto->getDeclaredExistentialType(),
63826386
/*isAlias=*/false);
6387+
diag.warnUntilSwiftVersionIf(warnUntilSwift7, 7);
63836388
emitInsertAnyFixit(diag, T);
63846389
}
63856390
} else if (auto *alias = dyn_cast<TypeAliasDecl>(decl)) {
@@ -6410,6 +6415,7 @@ class ExistentialTypeSyntaxChecker : public ASTWalker {
64106415
alias->getDeclaredInterfaceType(),
64116416
ExistentialType::get(alias->getDeclaredInterfaceType()),
64126417
/*isAlias=*/true);
6418+
diag.warnUntilSwiftVersionIf(warnUntilSwift7, 7);
64136419
emitInsertAnyFixit(diag, T);
64146420
}
64156421
}
@@ -6489,7 +6495,14 @@ void TypeChecker::checkExistentialTypes(ASTContext &ctx, Stmt *stmt,
64896495
if (sourceFile && sourceFile->Kind == SourceFileKind::Interface)
64906496
return;
64916497

6492-
ExistentialTypeSyntaxChecker checker(ctx, /*checkStatements=*/true);
6498+
// Previously we missed this diagnostic on 'catch' statements, downgrade
6499+
// to a warning until Swift 7.
6500+
auto downgradeUntilSwift7 = false;
6501+
if (auto *CS = dyn_cast<CaseStmt>(stmt))
6502+
downgradeUntilSwift7 = CS->getParentKind() == CaseParentKind::DoCatch;
6503+
6504+
ExistentialTypeSyntaxChecker checker(ctx, /*checkStatements=*/true,
6505+
downgradeUntilSwift7);
64936506
stmt->walk(checker);
64946507
}
64956508

test/stmt/statements.swift

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ func r25178926(_ a : Type) {
476476
switch a { // expected-error {{switch must be exhaustive}}
477477
// expected-note@-1 {{missing case: '.Bar'}}
478478
case .Foo, .Bar where 1 != 100:
479-
// expected-warning @-1 {{'where' only applies to the second pattern match in this case}}
479+
// expected-warning @-1 {{'where' only applies to the second pattern match in this 'case'}}
480480
// expected-note @-2 {{disambiguate by adding a line break between them if this is desired}} {{14-14=\n }}
481481
// expected-note @-3 {{duplicate the 'where' on both patterns to check both patterns}} {{12-12= where 1 != 100}}
482482
break
@@ -504,6 +504,34 @@ func r25178926(_ a : Type) {
504504
}
505505
}
506506

507+
func testAmbiguousWhereInCatch() {
508+
protocol P1 {}
509+
protocol P2 {}
510+
func throwingFn() throws {}
511+
do {
512+
try throwingFn()
513+
} catch is P1, is P2 where .random() {
514+
// expected-warning @-1 {{'where' only applies to the second pattern match in this 'catch'}}
515+
// expected-note @-2 {{disambiguate by adding a line break between them if this is desired}} {{18-18=\n }}
516+
// expected-note @-3 {{duplicate the 'where' on both patterns to check both patterns}} {{16-16= where .random()}}
517+
} catch {
518+
519+
}
520+
do {
521+
try throwingFn()
522+
} catch is P1,
523+
is P2 where .random() {
524+
} catch {
525+
526+
}
527+
do {
528+
try throwingFn()
529+
} catch is P1 where .random(), is P2 where .random() {
530+
} catch {
531+
532+
}
533+
}
534+
507535
do {
508536
guard 1 == 2 else {
509537
break // expected-error {{unlabeled 'break' is only allowed inside a loop or switch, a labeled break is required to exit an if or do}}

test/type/protocol_types.swift

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,99 @@ struct SubscriptWhere {
149149
struct OuterGeneric<T> {
150150
func contextuallyGenericMethod() where T == any HasAssoc {}
151151
}
152+
153+
typealias HasAssocAlias = HasAssoc
154+
155+
func testExistentialInCase(_ x: Any) {
156+
switch x {
157+
case is HasAssoc:
158+
// expected-error@-1 {{use of protocol 'HasAssoc' as a type must be written 'any HasAssoc'}}
159+
break
160+
default:
161+
break
162+
}
163+
_ = {
164+
switch x {
165+
case is HasAssoc:
166+
// expected-error@-1 {{use of protocol 'HasAssoc' as a type must be written 'any HasAssoc'}}
167+
break
168+
default:
169+
break
170+
}
171+
}
172+
switch x {
173+
case is HasAssocAlias:
174+
// expected-error@-1 {{use of 'HasAssocAlias' (aka 'HasAssoc') as a type must be written 'any HasAssocAlias' (aka 'any HasAssoc')}}
175+
break
176+
default:
177+
break
178+
}
179+
_ = {
180+
switch x {
181+
case is HasAssocAlias:
182+
// expected-error@-1 {{use of 'HasAssocAlias' (aka 'HasAssoc') as a type must be written 'any HasAssocAlias' (aka 'any HasAssoc')}}
183+
break
184+
default:
185+
break
186+
}
187+
}
188+
switch x {
189+
case is ~Copyable:
190+
// expected-error@-1 {{constraint that suppresses conformance requires 'any'}}
191+
// expected-warning@-2 {{'is' test is always true}}
192+
break
193+
default:
194+
break
195+
}
196+
_ = {
197+
switch x {
198+
case is ~Copyable:
199+
// expected-error@-1 {{constraint that suppresses conformance requires 'any'}}
200+
// expected-warning@-2 {{'is' test is always true}}
201+
break
202+
default:
203+
break
204+
}
205+
}
206+
}
207+
208+
func throwingFn() throws {}
209+
210+
// These are downgraded to warnings until Swift 7, see protocol_types_swift7.swift.
211+
// https://github.com/swiftlang/swift/issues/77553
212+
func testExistentialInCatch() throws {
213+
do {
214+
try throwingFn()
215+
} catch is HasAssoc {}
216+
// expected-warning@-1 {{use of protocol 'HasAssoc' as a type must be written 'any HasAssoc'}}
217+
_ = {
218+
do {
219+
try throwingFn()
220+
} catch is HasAssoc {}
221+
// expected-warning@-1 {{use of protocol 'HasAssoc' as a type must be written 'any HasAssoc'}}
222+
}
223+
do {
224+
try throwingFn()
225+
} catch is HasAssocAlias {}
226+
// expected-warning@-1 {{use of 'HasAssocAlias' (aka 'HasAssoc') as a type must be written 'any HasAssocAlias' (aka 'any HasAssoc')}}
227+
_ = {
228+
do {
229+
try throwingFn()
230+
} catch is HasAssocAlias {}
231+
// expected-warning@-1 {{use of 'HasAssocAlias' (aka 'HasAssoc') as a type must be written 'any HasAssocAlias' (aka 'any HasAssoc')}}
232+
}
233+
do {
234+
try throwingFn()
235+
} catch is ~Copyable {}
236+
// expected-warning@-1 {{constraint that suppresses conformance requires 'any'}}
237+
// expected-warning@-2 {{'is' test is always true}}
238+
239+
// FIXME: We shouldn't emit a duplicate 'always true' warning here.
240+
_ = {
241+
do {
242+
try throwingFn()
243+
} catch is ~Copyable {}
244+
// expected-warning@-1 {{constraint that suppresses conformance requires 'any'}}
245+
// expected-warning@-2 2{{'is' test is always true}}
246+
}
247+
}

test/type/protocol_types_swift7.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// RUN: %target-typecheck-verify-swift -swift-version 7
2+
// REQUIRES: swift7
3+
4+
protocol HasAssoc {
5+
associatedtype Assoc
6+
}
7+
8+
typealias HasAssocAlias = HasAssoc
9+
10+
func throwingFn() throws {}
11+
12+
// In Swift 6 we previously missed this diagnostic.
13+
// https://github.com/swiftlang/swift/issues/77553
14+
func testExistentialInCatch() throws {
15+
do {
16+
try throwingFn()
17+
} catch is HasAssoc {}
18+
// expected-error@-1 {{use of protocol 'HasAssoc' as a type must be written 'any HasAssoc'}}
19+
_ = {
20+
do {
21+
try throwingFn()
22+
} catch is HasAssoc {}
23+
// expected-error@-1 {{use of protocol 'HasAssoc' as a type must be written 'any HasAssoc'}}
24+
}
25+
do {
26+
try throwingFn()
27+
} catch is HasAssocAlias {}
28+
// expected-error@-1 {{use of 'HasAssocAlias' (aka 'HasAssoc') as a type must be written 'any HasAssocAlias' (aka 'any HasAssoc')}}
29+
_ = {
30+
do {
31+
try throwingFn()
32+
} catch is HasAssocAlias {}
33+
// expected-error@-1 {{use of 'HasAssocAlias' (aka 'HasAssoc') as a type must be written 'any HasAssocAlias' (aka 'any HasAssoc')}}
34+
}
35+
do {
36+
try throwingFn()
37+
} catch is ~Copyable {}
38+
// expected-error@-1 {{constraint that suppresses conformance requires 'any'}}
39+
// expected-warning@-2 {{'is' test is always true}}
40+
41+
// FIXME: We shouldn't emit a duplicate 'always true' warning here.
42+
_ = {
43+
do {
44+
try throwingFn()
45+
} catch is ~Copyable {}
46+
// expected-error@-1 {{constraint that suppresses conformance requires 'any'}}
47+
// expected-warning@-2 2{{'is' test is always true}}
48+
}
49+
}

0 commit comments

Comments
 (0)