mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[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.
This commit is contained in:
@@ -5105,7 +5105,8 @@ ERROR(unknown_case_must_be_last,none,
|
||||
"'@unknown' can only be applied to the last case in a switch", ())
|
||||
|
||||
WARNING(where_on_one_item, none,
|
||||
"'where' only applies to the second pattern match in this case", ())
|
||||
"'where' only applies to the second pattern match in this "
|
||||
"'%select{case|catch}0'", (bool))
|
||||
|
||||
NOTE(add_where_newline, none,
|
||||
"disambiguate by adding a line break between them if this is desired", ())
|
||||
|
||||
@@ -4419,21 +4419,17 @@ void swift::performAbstractFuncDeclDiagnostics(AbstractFunctionDecl *AFD) {
|
||||
}
|
||||
}
|
||||
|
||||
// Perform MiscDiagnostics on Switch Statements.
|
||||
static void checkSwitch(ASTContext &ctx, const SwitchStmt *stmt,
|
||||
DeclContext *DC) {
|
||||
// We want to warn about "case .Foo, .Bar where 1 != 100:" since the where
|
||||
// clause only applies to the second case, and this is surprising.
|
||||
for (auto cs : stmt->getCases()) {
|
||||
TypeChecker::checkExistentialTypes(ctx, cs, DC);
|
||||
|
||||
static void diagnoseCaseStmtAmbiguousWhereClause(const CaseStmt *CS,
|
||||
ASTContext &ctx) {
|
||||
// The case statement can have multiple case items, each can have a where.
|
||||
// If we find a "where", and there is a preceding item without a where, and
|
||||
// if they are on the same source line, then warn.
|
||||
auto items = cs->getCaseLabelItems();
|
||||
// if they are on the same source line, e.g
|
||||
// "case .Foo, .Bar where 1 != 100:" then warn since it may be unexpected.
|
||||
auto items = CS->getCaseLabelItems();
|
||||
|
||||
// Don't do any work for the vastly most common case.
|
||||
if (items.size() == 1) continue;
|
||||
if (items.size() == 1)
|
||||
return;
|
||||
|
||||
// Ignore the first item, since it can't have preceding ones.
|
||||
for (unsigned i = 1, e = items.size(); i != e; ++i) {
|
||||
@@ -4457,7 +4453,9 @@ static void checkSwitch(ASTContext &ctx, const SwitchStmt *stmt,
|
||||
if (SM.getLineAndColumnInBuffer(thisLoc).first != prevLineCol.first)
|
||||
continue;
|
||||
|
||||
ctx.Diags.diagnose(items[i].getWhereLoc(), diag::where_on_one_item)
|
||||
ctx.Diags
|
||||
.diagnose(items[i].getWhereLoc(), diag::where_on_one_item,
|
||||
CS->getParentKind() == CaseParentKind::DoCatch)
|
||||
.highlight(items[i].getPattern()->getSourceRange())
|
||||
.highlight(where->getSourceRange());
|
||||
|
||||
@@ -4466,8 +4464,7 @@ static void checkSwitch(ASTContext &ctx, const SwitchStmt *stmt,
|
||||
ctx.Diags.diagnose(thisLoc, diag::add_where_newline)
|
||||
.fixItInsert(thisLoc, "\n" + whitespace);
|
||||
|
||||
auto whereRange = SourceRange(items[i].getWhereLoc(),
|
||||
where->getEndLoc());
|
||||
auto whereRange = SourceRange(items[i].getWhereLoc(), where->getEndLoc());
|
||||
auto charRange = Lexer::getCharSourceRangeFromSourceRange(SM, whereRange);
|
||||
auto whereText = SM.extractText(charRange);
|
||||
ctx.Diags.diagnose(prevLoc, diag::duplicate_where)
|
||||
@@ -4475,7 +4472,6 @@ static void checkSwitch(ASTContext &ctx, const SwitchStmt *stmt,
|
||||
.highlight(items[i - 1].getSourceRange());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void swift::fixItEncloseTrailingClosure(ASTContext &ctx,
|
||||
InFlightDiagnostic &diag,
|
||||
@@ -6194,8 +6190,8 @@ void swift::performStmtDiagnostics(const Stmt *S, DeclContext *DC) {
|
||||
|
||||
TypeChecker::checkExistentialTypes(ctx, const_cast<Stmt *>(S), DC);
|
||||
|
||||
if (auto switchStmt = dyn_cast<SwitchStmt>(S))
|
||||
checkSwitch(ctx, switchStmt, DC);
|
||||
if (auto *CS = dyn_cast<CaseStmt>(S))
|
||||
diagnoseCaseStmtAmbiguousWhereClause(CS, ctx);
|
||||
|
||||
checkStmtConditionTrailingClosure(ctx, S);
|
||||
|
||||
|
||||
@@ -1607,6 +1607,10 @@ public:
|
||||
BraceStmt *body = caseBlock->getBody();
|
||||
limitExhaustivityChecks |= typeCheckStmt(body);
|
||||
caseBlock->setBody(body);
|
||||
|
||||
// CaseStmts don't go through typeCheckStmt, so manually call into
|
||||
// performStmtDiagnostics.
|
||||
performStmtDiagnostics(caseBlock, DC);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6143,13 +6143,16 @@ class ExistentialTypeSyntaxChecker : public ASTWalker {
|
||||
ASTContext &Ctx;
|
||||
bool checkStatements;
|
||||
bool hitTopStmt;
|
||||
bool warnUntilSwift7;
|
||||
|
||||
unsigned exprCount = 0;
|
||||
llvm::SmallVector<TypeRepr *, 4> reprStack;
|
||||
|
||||
public:
|
||||
ExistentialTypeSyntaxChecker(ASTContext &ctx, bool checkStatements)
|
||||
: Ctx(ctx), checkStatements(checkStatements), hitTopStmt(false) {}
|
||||
ExistentialTypeSyntaxChecker(ASTContext &ctx, bool checkStatements,
|
||||
bool warnUntilSwift7 = false)
|
||||
: Ctx(ctx), checkStatements(checkStatements), hitTopStmt(false),
|
||||
warnUntilSwift7(warnUntilSwift7) {}
|
||||
|
||||
MacroWalking getMacroWalkingBehavior() const override {
|
||||
return MacroWalking::ArgumentsAndExpansion;
|
||||
@@ -6363,6 +6366,7 @@ private:
|
||||
inverse && isAnyOrSomeMissing()) {
|
||||
auto diag = Ctx.Diags.diagnose(inverse->getTildeLoc(),
|
||||
diag::inverse_requires_any);
|
||||
diag.warnUntilSwiftVersionIf(warnUntilSwift7, 7);
|
||||
emitInsertAnyFixit(diag, T);
|
||||
return;
|
||||
}
|
||||
@@ -6380,6 +6384,7 @@ private:
|
||||
proto->getDeclaredInterfaceType(),
|
||||
proto->getDeclaredExistentialType(),
|
||||
/*isAlias=*/false);
|
||||
diag.warnUntilSwiftVersionIf(warnUntilSwift7, 7);
|
||||
emitInsertAnyFixit(diag, T);
|
||||
}
|
||||
} else if (auto *alias = dyn_cast<TypeAliasDecl>(decl)) {
|
||||
@@ -6410,6 +6415,7 @@ private:
|
||||
alias->getDeclaredInterfaceType(),
|
||||
ExistentialType::get(alias->getDeclaredInterfaceType()),
|
||||
/*isAlias=*/true);
|
||||
diag.warnUntilSwiftVersionIf(warnUntilSwift7, 7);
|
||||
emitInsertAnyFixit(diag, T);
|
||||
}
|
||||
}
|
||||
@@ -6489,7 +6495,14 @@ void TypeChecker::checkExistentialTypes(ASTContext &ctx, Stmt *stmt,
|
||||
if (sourceFile && sourceFile->Kind == SourceFileKind::Interface)
|
||||
return;
|
||||
|
||||
ExistentialTypeSyntaxChecker checker(ctx, /*checkStatements=*/true);
|
||||
// Previously we missed this diagnostic on 'catch' statements, downgrade
|
||||
// to a warning until Swift 7.
|
||||
auto downgradeUntilSwift7 = false;
|
||||
if (auto *CS = dyn_cast<CaseStmt>(stmt))
|
||||
downgradeUntilSwift7 = CS->getParentKind() == CaseParentKind::DoCatch;
|
||||
|
||||
ExistentialTypeSyntaxChecker checker(ctx, /*checkStatements=*/true,
|
||||
downgradeUntilSwift7);
|
||||
stmt->walk(checker);
|
||||
}
|
||||
|
||||
|
||||
@@ -476,7 +476,7 @@ func r25178926(_ a : Type) {
|
||||
switch a { // expected-error {{switch must be exhaustive}}
|
||||
// expected-note@-1 {{missing case: '.Bar'}}
|
||||
case .Foo, .Bar where 1 != 100:
|
||||
// expected-warning @-1 {{'where' only applies to the second pattern match in this case}}
|
||||
// expected-warning @-1 {{'where' only applies to the second pattern match in this 'case'}}
|
||||
// expected-note @-2 {{disambiguate by adding a line break between them if this is desired}} {{14-14=\n }}
|
||||
// expected-note @-3 {{duplicate the 'where' on both patterns to check both patterns}} {{12-12= where 1 != 100}}
|
||||
break
|
||||
@@ -504,6 +504,34 @@ func r25178926(_ a : Type) {
|
||||
}
|
||||
}
|
||||
|
||||
func testAmbiguousWhereInCatch() {
|
||||
protocol P1 {}
|
||||
protocol P2 {}
|
||||
func throwingFn() throws {}
|
||||
do {
|
||||
try throwingFn()
|
||||
} catch is P1, is P2 where .random() {
|
||||
// expected-warning @-1 {{'where' only applies to the second pattern match in this 'catch'}}
|
||||
// expected-note @-2 {{disambiguate by adding a line break between them if this is desired}} {{18-18=\n }}
|
||||
// expected-note @-3 {{duplicate the 'where' on both patterns to check both patterns}} {{16-16= where .random()}}
|
||||
} catch {
|
||||
|
||||
}
|
||||
do {
|
||||
try throwingFn()
|
||||
} catch is P1,
|
||||
is P2 where .random() {
|
||||
} catch {
|
||||
|
||||
}
|
||||
do {
|
||||
try throwingFn()
|
||||
} catch is P1 where .random(), is P2 where .random() {
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
guard 1 == 2 else {
|
||||
break // expected-error {{unlabeled 'break' is only allowed inside a loop or switch, a labeled break is required to exit an if or do}}
|
||||
|
||||
@@ -149,3 +149,99 @@ struct SubscriptWhere {
|
||||
struct OuterGeneric<T> {
|
||||
func contextuallyGenericMethod() where T == any HasAssoc {}
|
||||
}
|
||||
|
||||
typealias HasAssocAlias = HasAssoc
|
||||
|
||||
func testExistentialInCase(_ x: Any) {
|
||||
switch x {
|
||||
case is HasAssoc:
|
||||
// expected-error@-1 {{use of protocol 'HasAssoc' as a type must be written 'any HasAssoc'}}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
_ = {
|
||||
switch x {
|
||||
case is HasAssoc:
|
||||
// expected-error@-1 {{use of protocol 'HasAssoc' as a type must be written 'any HasAssoc'}}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
switch x {
|
||||
case is HasAssocAlias:
|
||||
// expected-error@-1 {{use of 'HasAssocAlias' (aka 'HasAssoc') as a type must be written 'any HasAssocAlias' (aka 'any HasAssoc')}}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
_ = {
|
||||
switch x {
|
||||
case is HasAssocAlias:
|
||||
// expected-error@-1 {{use of 'HasAssocAlias' (aka 'HasAssoc') as a type must be written 'any HasAssocAlias' (aka 'any HasAssoc')}}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
switch x {
|
||||
case is ~Copyable:
|
||||
// expected-error@-1 {{constraint that suppresses conformance requires 'any'}}
|
||||
// expected-warning@-2 {{'is' test is always true}}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
_ = {
|
||||
switch x {
|
||||
case is ~Copyable:
|
||||
// expected-error@-1 {{constraint that suppresses conformance requires 'any'}}
|
||||
// expected-warning@-2 {{'is' test is always true}}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func throwingFn() throws {}
|
||||
|
||||
// These are downgraded to warnings until Swift 7, see protocol_types_swift7.swift.
|
||||
// https://github.com/swiftlang/swift/issues/77553
|
||||
func testExistentialInCatch() throws {
|
||||
do {
|
||||
try throwingFn()
|
||||
} catch is HasAssoc {}
|
||||
// expected-warning@-1 {{use of protocol 'HasAssoc' as a type must be written 'any HasAssoc'}}
|
||||
_ = {
|
||||
do {
|
||||
try throwingFn()
|
||||
} catch is HasAssoc {}
|
||||
// expected-warning@-1 {{use of protocol 'HasAssoc' as a type must be written 'any HasAssoc'}}
|
||||
}
|
||||
do {
|
||||
try throwingFn()
|
||||
} catch is HasAssocAlias {}
|
||||
// expected-warning@-1 {{use of 'HasAssocAlias' (aka 'HasAssoc') as a type must be written 'any HasAssocAlias' (aka 'any HasAssoc')}}
|
||||
_ = {
|
||||
do {
|
||||
try throwingFn()
|
||||
} catch is HasAssocAlias {}
|
||||
// expected-warning@-1 {{use of 'HasAssocAlias' (aka 'HasAssoc') as a type must be written 'any HasAssocAlias' (aka 'any HasAssoc')}}
|
||||
}
|
||||
do {
|
||||
try throwingFn()
|
||||
} catch is ~Copyable {}
|
||||
// expected-warning@-1 {{constraint that suppresses conformance requires 'any'}}
|
||||
// expected-warning@-2 {{'is' test is always true}}
|
||||
|
||||
// FIXME: We shouldn't emit a duplicate 'always true' warning here.
|
||||
_ = {
|
||||
do {
|
||||
try throwingFn()
|
||||
} catch is ~Copyable {}
|
||||
// expected-warning@-1 {{constraint that suppresses conformance requires 'any'}}
|
||||
// expected-warning@-2 2{{'is' test is always true}}
|
||||
}
|
||||
}
|
||||
|
||||
49
test/type/protocol_types_swift7.swift
Normal file
49
test/type/protocol_types_swift7.swift
Normal file
@@ -0,0 +1,49 @@
|
||||
// RUN: %target-typecheck-verify-swift -swift-version 7
|
||||
// REQUIRES: swift7
|
||||
|
||||
protocol HasAssoc {
|
||||
associatedtype Assoc
|
||||
}
|
||||
|
||||
typealias HasAssocAlias = HasAssoc
|
||||
|
||||
func throwingFn() throws {}
|
||||
|
||||
// In Swift 6 we previously missed this diagnostic.
|
||||
// https://github.com/swiftlang/swift/issues/77553
|
||||
func testExistentialInCatch() throws {
|
||||
do {
|
||||
try throwingFn()
|
||||
} catch is HasAssoc {}
|
||||
// expected-error@-1 {{use of protocol 'HasAssoc' as a type must be written 'any HasAssoc'}}
|
||||
_ = {
|
||||
do {
|
||||
try throwingFn()
|
||||
} catch is HasAssoc {}
|
||||
// expected-error@-1 {{use of protocol 'HasAssoc' as a type must be written 'any HasAssoc'}}
|
||||
}
|
||||
do {
|
||||
try throwingFn()
|
||||
} catch is HasAssocAlias {}
|
||||
// expected-error@-1 {{use of 'HasAssocAlias' (aka 'HasAssoc') as a type must be written 'any HasAssocAlias' (aka 'any HasAssoc')}}
|
||||
_ = {
|
||||
do {
|
||||
try throwingFn()
|
||||
} catch is HasAssocAlias {}
|
||||
// expected-error@-1 {{use of 'HasAssocAlias' (aka 'HasAssoc') as a type must be written 'any HasAssocAlias' (aka 'any HasAssoc')}}
|
||||
}
|
||||
do {
|
||||
try throwingFn()
|
||||
} catch is ~Copyable {}
|
||||
// expected-error@-1 {{constraint that suppresses conformance requires 'any'}}
|
||||
// expected-warning@-2 {{'is' test is always true}}
|
||||
|
||||
// FIXME: We shouldn't emit a duplicate 'always true' warning here.
|
||||
_ = {
|
||||
do {
|
||||
try throwingFn()
|
||||
} catch is ~Copyable {}
|
||||
// expected-error@-1 {{constraint that suppresses conformance requires 'any'}}
|
||||
// expected-warning@-2 2{{'is' test is always true}}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user