diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index a99aaf4b497d7..3c92908fae384 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -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", ()) diff --git a/lib/Sema/CSSyntacticElement.cpp b/lib/Sema/CSSyntacticElement.cpp index d523e7ce056ec..8718d31b608c2 100644 --- a/lib/Sema/CSSyntacticElement.cpp +++ b/lib/Sema/CSSyntacticElement.cpp @@ -1733,20 +1733,6 @@ class SyntacticElementSolutionApplication return Type(); } - ASTNode visit(Stmt *S, bool performSyntacticDiagnostics = true) { - auto rewritten = ASTVisitor::visit(S); - if (!rewritten) - return {}; - - if (performSyntacticDiagnostics) { - if (auto *stmt = getAsStmt(rewritten)) { - performStmtDiagnostics(stmt, context.getAsDeclContext()); - } - } - - return rewritten; - } - bool visitPatternBindingDecl(PatternBindingDecl *PBD) { // If this is a placeholder variable with an initializer, we just need to // set the inferred type. @@ -2274,7 +2260,7 @@ class ResultBuilderRewriter : public SyntacticElementSolutionApplication { private: ASTNode visitDoStmt(DoStmt *doStmt) override { if (auto transformed = transformDo(doStmt)) { - return visit(transformed.get(), /*performSyntacticDiagnostics=*/false); + return visit(transformed.get()); } auto newBody = visit(doStmt->getBody()); diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index 7ad1584d5deb4..3d508dee746b2 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -4419,61 +4419,57 @@ 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); - - // 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(); - - // Don't do any work for the vastly most common case. - if (items.size() == 1) continue; - - // Ignore the first item, since it can't have preceding ones. - for (unsigned i = 1, e = items.size(); i != e; ++i) { - // Must have a where clause. - auto where = items[i].getGuardExpr(); - if (!where) - continue; - - // Preceding item must not. - if (items[i-1].getGuardExpr()) - continue; - - // Must be on the same source line. - auto prevLoc = items[i-1].getStartLoc(); - auto thisLoc = items[i].getStartLoc(); - if (prevLoc.isInvalid() || thisLoc.isInvalid()) - continue; - - auto &SM = ctx.SourceMgr; - auto prevLineCol = SM.getLineAndColumnInBuffer(prevLoc); - if (SM.getLineAndColumnInBuffer(thisLoc).first != prevLineCol.first) - continue; +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, 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) + return; - ctx.Diags.diagnose(items[i].getWhereLoc(), diag::where_on_one_item) + // Ignore the first item, since it can't have preceding ones. + for (unsigned i = 1, e = items.size(); i != e; ++i) { + // Must have a where clause. + auto where = items[i].getGuardExpr(); + if (!where) + continue; + + // Preceding item must not. + if (items[i - 1].getGuardExpr()) + continue; + + // Must be on the same source line. + auto prevLoc = items[i - 1].getStartLoc(); + auto thisLoc = items[i].getStartLoc(); + if (prevLoc.isInvalid() || thisLoc.isInvalid()) + continue; + + auto &SM = ctx.SourceMgr; + auto prevLineCol = SM.getLineAndColumnInBuffer(prevLoc); + if (SM.getLineAndColumnInBuffer(thisLoc).first != prevLineCol.first) + continue; + + ctx.Diags + .diagnose(items[i].getWhereLoc(), diag::where_on_one_item, + CS->getParentKind() == CaseParentKind::DoCatch) .highlight(items[i].getPattern()->getSourceRange()) .highlight(where->getSourceRange()); - - // Whitespace it out to the same column as the previous item. - std::string whitespace(prevLineCol.second-1, ' '); - ctx.Diags.diagnose(thisLoc, diag::add_where_newline) - .fixItInsert(thisLoc, "\n"+whitespace); - - 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) - .fixItInsertAfter(items[i-1].getEndLoc(), " " + whereText.str()) - .highlight(items[i-1].getSourceRange()); - } + + // Whitespace it out to the same column as the previous item. + std::string whitespace(prevLineCol.second - 1, ' '); + ctx.Diags.diagnose(thisLoc, diag::add_where_newline) + .fixItInsert(thisLoc, "\n" + whitespace); + + 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) + .fixItInsertAfter(items[i - 1].getEndLoc(), " " + whereText.str()) + .highlight(items[i - 1].getSourceRange()); } } @@ -6194,8 +6190,8 @@ void swift::performStmtDiagnostics(const Stmt *S, DeclContext *DC) { TypeChecker::checkExistentialTypes(ctx, const_cast(S), DC); - if (auto switchStmt = dyn_cast(S)) - checkSwitch(ctx, switchStmt, DC); + if (auto *CS = dyn_cast(S)) + diagnoseCaseStmtAmbiguousWhereClause(CS, ctx); checkStmtConditionTrailingClosure(ctx, S); diff --git a/lib/Sema/MiscDiagnostics.h b/lib/Sema/MiscDiagnostics.h index 8d3608603e1b2..b51ce0e4fc8df 100644 --- a/lib/Sema/MiscDiagnostics.h +++ b/lib/Sema/MiscDiagnostics.h @@ -135,7 +135,11 @@ namespace swift { class BaseDiagnosticWalker : public ASTWalker { PreWalkAction walkToDeclPre(Decl *D) override { - return Action::VisitNodeIf(isa(D)); + // We don't walk into any nested local decls, except PatternBindingDecls, + // which are type-checked along with the parent, and MacroExpansionDecl, + // which needs to be visited to visit the macro arguments. + return Action::VisitNodeIf(isa(D) || + isa(D)); } MacroWalking getMacroWalkingBehavior() const override { diff --git a/lib/Sema/TypeCheckAvailability.cpp b/lib/Sema/TypeCheckAvailability.cpp index 0210559bb44ea..ab0dcf8d1668c 100644 --- a/lib/Sema/TypeCheckAvailability.cpp +++ b/lib/Sema/TypeCheckAvailability.cpp @@ -3561,7 +3561,7 @@ bool diagnoseExplicitUnavailability( } namespace { -class ExprAvailabilityWalker : public ASTWalker { +class ExprAvailabilityWalker : public BaseDiagnosticWalker { /// Models how member references will translate to accessor usage. This is /// used to diagnose the availability of individual accessors that may be /// called by the expression being checked. @@ -3596,11 +3596,6 @@ class ExprAvailabilityWalker : public ASTWalker { explicit ExprAvailabilityWalker(const ExportContext &Where) : Context(Where.getDeclContext()->getASTContext()), Where(Where) {} - MacroWalking getMacroWalkingBehavior() const override { - // Expanded source should be type checked and diagnosed separately. - return MacroWalking::Arguments; - } - PreWalkAction walkToArgumentPre(const Argument &Arg) override { // Arguments should be walked in their own member access context which // starts out read-only by default. @@ -3721,10 +3716,8 @@ class ExprAvailabilityWalker : public ASTWalker { CE->getResultType(), E->getLoc(), Where); } if (AbstractClosureExpr *closure = dyn_cast(E)) { - if (shouldWalkIntoClosure(closure)) { - walkAbstractClosure(closure); - return Action::SkipChildren(E); - } + walkAbstractClosure(closure); + return Action::SkipChildren(E); } if (auto CE = dyn_cast(E)) { @@ -3790,13 +3783,22 @@ class ExprAvailabilityWalker : public ASTWalker { } PreWalkResult walkToStmtPre(Stmt *S) override { - - // We end up here when checking the output of the result builder transform, - // which includes closures that are not "separately typechecked" and yet - // contain statements and declarations. We need to walk them recursively, - // since these availability for these statements is not diagnosed from - // typeCheckStmt() as usual. - diagnoseStmtAvailability(S, Where.getDeclContext(), /*walkRecursively=*/true); + // We need to recursively call diagnoseExprAvailability for any + // sub-expressions in the statement since the availability context may + // differ, e.g for things like `guard #available(...)`. + class StmtRecurseWalker : public BaseDiagnosticWalker { + DeclContext *DC; + + public: + StmtRecurseWalker(DeclContext *DC) : DC(DC) {} + + PreWalkResult walkToExprPre(Expr *E) override { + diagnoseExprAvailability(E, DC); + return Action::SkipNode(E); + } + }; + StmtRecurseWalker W(Where.getDeclContext()); + S->walk(W); return Action::SkipNode(S); } @@ -3943,10 +3945,6 @@ class ExprAvailabilityWalker : public ASTWalker { walkInContext(E->getSubExpr(), accessContext); } - bool shouldWalkIntoClosure(AbstractClosureExpr *closure) const { - return true; - } - /// Walk an abstract closure expression, checking for availability void walkAbstractClosure(AbstractClosureExpr *closure) { // Do the walk with the closure set as the decl context of the 'where' @@ -4396,26 +4394,29 @@ void swift::diagnoseExprAvailability(const Expr *E, DeclContext *DC) { namespace { class StmtAvailabilityWalker : public BaseDiagnosticWalker { + const Stmt *TopLevelStmt; DeclContext *DC; - bool WalkRecursively; public: - explicit StmtAvailabilityWalker(DeclContext *dc, bool walkRecursively) - : DC(dc), WalkRecursively(walkRecursively) {} + explicit StmtAvailabilityWalker(const Stmt *S, DeclContext *dc) + : TopLevelStmt(S), DC(dc) {} PreWalkResult walkToStmtPre(Stmt *S) override { - if (!WalkRecursively && isa(S)) - return Action::SkipNode(S); - - return Action::Continue(S); + // `diagnoseStmtAvailability` is called for every statement, so we don't + // want to walk into any nested statements. + return Action::VisitNodeIf(S == TopLevelStmt, S); } PreWalkResult walkToExprPre(Expr *E) override { - if (WalkRecursively) - diagnoseExprAvailability(E, DC); + // Handled by ExprAvailabilityWalker. return Action::SkipNode(E); } + PreWalkAction walkToDeclPre(Decl *D) override { + // Handled by DeclAvailabilityChecker. + return Action::SkipNode(); + } + PreWalkAction walkToTypeReprPre(TypeRepr *T) override { auto where = ExportContext::forFunctionBody(DC, T->getStartLoc()); diagnoseTypeReprAvailability(T, where); @@ -4434,13 +4435,8 @@ class StmtAvailabilityWalker : public BaseDiagnosticWalker { }; } -void swift::diagnoseStmtAvailability(const Stmt *S, DeclContext *DC, - bool walkRecursively) { - // We'll visit the individual statements when we check them. - if (!walkRecursively && isa(S)) - return; - - StmtAvailabilityWalker walker(DC, walkRecursively); +void swift::diagnoseStmtAvailability(const Stmt *S, DeclContext *DC) { + StmtAvailabilityWalker walker(S, DC); const_cast(S)->walk(walker); } diff --git a/lib/Sema/TypeCheckAvailability.h b/lib/Sema/TypeCheckAvailability.h index 4fdc8fe5343c7..039c148781de4 100644 --- a/lib/Sema/TypeCheckAvailability.h +++ b/lib/Sema/TypeCheckAvailability.h @@ -211,12 +211,8 @@ bool isExported(const Decl *D); void diagnoseExprAvailability(const Expr *E, DeclContext *DC); /// Diagnose uses of unavailable declarations in statements (via patterns, etc) -/// but not expressions, unless \p walkRecursively was specified. -/// -/// \param walkRecursively Whether nested statements and expressions should -/// be visited, too. -void diagnoseStmtAvailability(const Stmt *S, DeclContext *DC, - bool walkRecursively=false); +/// but not expressions. +void diagnoseStmtAvailability(const Stmt *S, DeclContext *DC); /// Checks both a TypeRepr and a Type, but avoids emitting duplicate /// diagnostics by only checking the Type if the TypeRepr succeeded. diff --git a/lib/Sema/TypeCheckConstraints.cpp b/lib/Sema/TypeCheckConstraints.cpp index 6a06bcd0944d7..d39452130d23f 100644 --- a/lib/Sema/TypeCheckConstraints.cpp +++ b/lib/Sema/TypeCheckConstraints.cpp @@ -327,9 +327,10 @@ void ParentConditionalConformance::diagnoseConformanceStack( namespace { /// Produce any additional syntactic diagnostics for a SyntacticElementTarget. -class SyntacticDiagnosticWalker final : public ASTWalker { +class SyntacticDiagnosticWalker final : public BaseDiagnosticWalker { const SyntacticElementTarget &Target; bool IsTopLevelExprStmt; + unsigned ExprDepth = 0; SyntacticDiagnosticWalker(const SyntacticElementTarget &target, bool isExprStmt) @@ -341,16 +342,23 @@ class SyntacticDiagnosticWalker final : public ASTWalker { target.walk(walker); } - MacroWalking getMacroWalkingBehavior() const override { - // We only want to walk macro arguments. Expansions will be walked when - // they're type-checked, not as part of the surrounding code. - return MacroWalking::Arguments; + PreWalkResult walkToExprPre(Expr *E) override { + // We only want to call the diagnostic logic for the top-level expression, + // since the underlying logic will visit each sub-expression. We want to + // continue walking however to diagnose any child statements in e.g + // closures. + if (ExprDepth == 0) { + auto isExprStmt = (E == Target.getAsExpr()) ? IsTopLevelExprStmt : false; + performSyntacticExprDiagnostics(E, Target.getDeclContext(), isExprStmt); + } + ExprDepth += 1; + return Action::Continue(E); } - PreWalkResult walkToExprPre(Expr *expr) override { - auto isExprStmt = (expr == Target.getAsExpr()) ? IsTopLevelExprStmt : false; - performSyntacticExprDiagnostics(expr, Target.getDeclContext(), isExprStmt); - return Action::SkipNode(expr); + PostWalkResult walkToExprPost(Expr *E) override { + assert(ExprDepth > 0); + ExprDepth -= 1; + return Action::Continue(E); } PreWalkResult walkToStmtPre(Stmt *stmt) override { @@ -358,10 +366,6 @@ class SyntacticDiagnosticWalker final : public ASTWalker { return Action::Continue(stmt); } - PreWalkAction walkToDeclPre(Decl *D) override { - return Action::VisitNodeIf(isa(D)); - } - PreWalkAction walkToTypeReprPre(TypeRepr *typeRepr) override { return Action::SkipNode(); } diff --git a/lib/Sema/TypeCheckStmt.cpp b/lib/Sema/TypeCheckStmt.cpp index da10c011dccfe..e31361e01cb6e 100644 --- a/lib/Sema/TypeCheckStmt.cpp +++ b/lib/Sema/TypeCheckStmt.cpp @@ -1607,6 +1607,10 @@ class StmtChecker : public StmtVisitor { BraceStmt *body = caseBlock->getBody(); limitExhaustivityChecks |= typeCheckStmt(body); caseBlock->setBody(body); + + // CaseStmts don't go through typeCheckStmt, so manually call into + // performStmtDiagnostics. + performStmtDiagnostics(caseBlock, DC); } } diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 4540d2606024e..0b9a97622876a 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -6143,13 +6143,16 @@ class ExistentialTypeSyntaxChecker : public ASTWalker { ASTContext &Ctx; bool checkStatements; bool hitTopStmt; + bool warnUntilSwift7; unsigned exprCount = 0; llvm::SmallVector 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 @@ class ExistentialTypeSyntaxChecker : public ASTWalker { 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 @@ class ExistentialTypeSyntaxChecker : public ASTWalker { proto->getDeclaredInterfaceType(), proto->getDeclaredExistentialType(), /*isAlias=*/false); + diag.warnUntilSwiftVersionIf(warnUntilSwift7, 7); emitInsertAnyFixit(diag, T); } } else if (auto *alias = dyn_cast(decl)) { @@ -6410,6 +6415,7 @@ class ExistentialTypeSyntaxChecker : public ASTWalker { 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(stmt)) + downgradeUntilSwift7 = CS->getParentKind() == CaseParentKind::DoCatch; + + ExistentialTypeSyntaxChecker checker(ctx, /*checkStatements=*/true, + downgradeUntilSwift7); stmt->walk(checker); } diff --git a/test/Constraints/result_builder_diags.swift b/test/Constraints/result_builder_diags.swift index 19d2c159b9c16..ec63663e6ff5e 100644 --- a/test/Constraints/result_builder_diags.swift +++ b/test/Constraints/result_builder_diags.swift @@ -1029,3 +1029,31 @@ func testMissingElementInEmptyBuilder() { func test2() -> Int {} // expected-error@-1 {{expected expression of type 'Int' in result builder 'SingleElementBuilder'}} {{24-24=<#T##Int#>}} } + +// https://github.com/swiftlang/swift/issues/77453 +func testNoDuplicateStmtDiags() { + @resultBuilder + struct Builder { + static func buildBlock(_ components: T...) -> T { + components.first! + } + static func buildEither(first component: T) -> T { + component + } + static func buildEither(second component: T) -> T { + component + } + } + + func takesClosure(_ fn: () -> Void) -> Int? { nil } + + @Builder + func foo() -> Int { + if let x = takesClosure {} { + // expected-warning@-1 {{trailing closure in this context is confusable with the body of the statement}} + x + } else { + 1 + } + } +} diff --git a/test/Macros/macro_misc_diags.swift b/test/Macros/macro_misc_diags.swift index 9b88bf1a98d53..4aee3c31d8270 100644 --- a/test/Macros/macro_misc_diags.swift +++ b/test/Macros/macro_misc_diags.swift @@ -77,10 +77,16 @@ _ = #identity(deprecatedFunc()) // CHECK-DIAG: @__swiftmacro_6Client0017Clientswift_yEEFcfMX[[@LINE-2]]{{.*}}identityfMf1_.swift:1:1: warning: 'deprecatedFunc()' is deprecated // CHECK-DIAG: Client.swift:[[@LINE-2]]:15: warning: 'deprecatedFunc()' is deprecated -#makeBinding(deprecatedFunc()) -// CHECK-DIAG: Client.swift:[[@LINE-1]]:14: warning: 'deprecatedFunc()' is deprecated -// CHECK-DIAG: @__swiftmacro_6Client0017Clientswift_yEEFcfMX[[@LINE-3]]{{.*}}makeBindingfMf_.swift:1:9: warning: 'deprecatedFunc()' is deprecated -// CHECK-DIAG: @__swiftmacro_6Client0017Clientswift_yEEFcfMX[[@LINE-4]]{{.*}}makeBindingfMf_.swift:1:5: warning: initialization of immutable value 'x' was never used +#makeBinding((deprecatedFunc(), Int, { + if let _ = takesClosure {} {} +}())) +// CHECK-DIAG: Client.swift:[[@LINE-3]]:33: error: expected member name or initializer call after type name +// CHECK-DIAG: Client.swift:[[@LINE-4]]:15: warning: 'deprecatedFunc()' is deprecated +// CHECK-DIAG: Client.swift:[[@LINE-4]]:27: warning: trailing closure in this context is confusable with the body of the statement +// CHECK-DIAG: @__swiftmacro_6Client0017Clientswift_yEEFcfMX[[@LINE-7]]{{.*}}makeBindingfMf_.swift:1:28: error: expected member name or initializer call after type name +// CHECK-DIAG: @__swiftmacro_6Client0017Clientswift_yEEFcfMX[[@LINE-8]]{{.*}}makeBindingfMf_.swift:1:10: warning: 'deprecatedFunc()' is deprecated +// CHECK-DIAG: @__swiftmacro_6Client0017Clientswift_yEEFcfMX[[@LINE-9]]{{.*}}makeBindingfMf_.swift:2:27: warning: trailing closure in this context is confusable with the body of the statement +// CHECK-DIAG: @__swiftmacro_6Client0017Clientswift_yEEFcfMX[[@LINE-10]]{{.*}}makeBindingfMf_.swift:1:5: warning: initialization of immutable value struct S1 { #makeBinding(deprecatedFunc()) @@ -98,8 +104,8 @@ func takesClosure(_ fn: () -> Void) -> Int? { nil } _ = #trailingClosure { if let _ = takesClosure {} {} - // CHECK-DIAG: Client.swift:[[@LINE-1]]:27: warning: trailing closure in this context is confusable with the body of the statement; pass as a parenthesized argument to silence this warning - // CHECK-DIAG: @__swiftmacro_6Client0017Clientswift_yEEFcfMX[[@LINE-4]]{{.*}}trailingClosurefMf_.swift:2:27: warning: trailing closure in this context is confusable with the body of the statement + // CHECK-DIAG: @__swiftmacro_6Client0017Clientswift_yEEFcfMX[[@LINE-3]]{{.*}}trailingClosurefMf_.swift:2:27: warning: trailing closure in this context is confusable with the body of the statement + // CHECK-DIAG: Client.swift:[[@LINE-2]]:27: warning: trailing closure in this context is confusable with the body of the statement } func testOptionalToAny(_ y: Int?) { diff --git a/test/stmt/statements.swift b/test/stmt/statements.swift index d533d5891d03f..3fde8002744d1 100644 --- a/test/stmt/statements.swift +++ b/test/stmt/statements.swift @@ -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}} diff --git a/test/type/protocol_types.swift b/test/type/protocol_types.swift index da9daac1e3ce3..4309fd0934863 100644 --- a/test/type/protocol_types.swift +++ b/test/type/protocol_types.swift @@ -149,3 +149,99 @@ struct SubscriptWhere { struct OuterGeneric { 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}} + } +} diff --git a/test/type/protocol_types_swift7.swift b/test/type/protocol_types_swift7.swift new file mode 100644 index 0000000000000..5ce2559ddb20e --- /dev/null +++ b/test/type/protocol_types_swift7.swift @@ -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}} + } +}