From 157e488f3bfdc1375e7309c431f56d1a1d41443f Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Wed, 30 Aug 2023 12:57:29 +0100 Subject: [PATCH 1/3] [Sema] Allow implicit nodes to wrap if/switch expressions Previously we would only look through a handful of AST node types when determining if an if/switch expression is in a valid position. However this doesn't handle cases where we synthesize code around an if/switch expression, such as `init(erasing:)` calls. As such, relax the logic such that it can look through any implicit expression. rdar://113435870 --- lib/AST/Expr.cpp | 57 ++++++++++++++++++++----------- test/expr/unary/if_expr.swift | 13 +++++++ test/expr/unary/switch_expr.swift | 13 +++++++ 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/lib/AST/Expr.cpp b/lib/AST/Expr.cpp index 24e6856ddb2d5..5ab0c1347824f 100644 --- a/lib/AST/Expr.cpp +++ b/lib/AST/Expr.cpp @@ -2519,30 +2519,47 @@ SingleValueStmtExpr *SingleValueStmtExpr::createWithWrappedBranches( SingleValueStmtExpr * SingleValueStmtExpr::tryDigOutSingleValueStmtExpr(Expr *E) { - while (true) { - // Look through implicit conversions. - if (auto *ICE = dyn_cast(E)) { - E = ICE->getSubExpr(); - continue; + class SVEFinder final : public ASTWalker { + public: + SingleValueStmtExpr *FoundSVE = nullptr; + + PreWalkResult walkToExprPre(Expr *E) override { + if (auto *SVE = dyn_cast(E)) { + FoundSVE = SVE; + return Action::Stop(); + } + + // Look through implicit exprs. + if (E->isImplicit()) + return Action::Continue(E); + + // Look through coercions. + if (isa(E)) + return Action::Continue(E); + + // Look through try/await (this is invalid, but we'll error on it in + // effect checking). + if (isa(E) || isa(E)) + return Action::Continue(E); + + return Action::Stop(); } - // Look through coercions. - if (auto *CE = dyn_cast(E)) { - E = CE->getSubExpr(); - continue; + PreWalkResult walkToStmtPre(Stmt *S) override { + return Action::Stop(); } - // Look through try/await (this is invalid, but we'll error on it in - // effect checking). - if (auto *TE = dyn_cast(E)) { - E = TE->getSubExpr(); - continue; + PreWalkAction walkToDeclPre(Decl *D) override { + return Action::Stop(); } - if (auto *AE = dyn_cast(E)) { - E = AE->getSubExpr(); - continue; + PreWalkResult walkToPatternPre(Pattern *P) override { + return Action::Stop(); } - break; - } - return dyn_cast(E); + PreWalkAction walkToTypeReprPre(TypeRepr *T) override { + return Action::Stop(); + } + }; + SVEFinder finder; + E->walk(finder); + return finder.FoundSVE; } SourceRange SingleValueStmtExpr::getSourceRange() const { diff --git a/test/expr/unary/if_expr.swift b/test/expr/unary/if_expr.swift index ab88e23a0642a..7f52006e4d859 100644 --- a/test/expr/unary/if_expr.swift +++ b/test/expr/unary/if_expr.swift @@ -1016,3 +1016,16 @@ func tryAwaitIf2() async throws -> Int { // expected-error@-1 {{'try' may not be used on 'if' expression}} // expected-error@-2 {{'await' may not be used on 'if' expression}} } + +struct AnyEraserP: EraserP { + init(erasing: T) {} +} + +@_typeEraser(AnyEraserP) +protocol EraserP {} +struct SomeEraserP: EraserP {} + +// rdar://113435870 - Make sure we allow an implicit init(erasing:) call. +dynamic func testDynamicOpaqueErase() -> some EraserP { + if .random() { SomeEraserP() } else { SomeEraserP() } +} diff --git a/test/expr/unary/switch_expr.swift b/test/expr/unary/switch_expr.swift index b498598375dfb..a164624a9ad0b 100644 --- a/test/expr/unary/switch_expr.swift +++ b/test/expr/unary/switch_expr.swift @@ -1288,3 +1288,16 @@ func tryAwaitSwitch2() async throws -> Int { // expected-error@-1 {{'try' may not be used on 'switch' expression}} // expected-error@-2 {{'await' may not be used on 'switch' expression}} } + +struct AnyEraserP: EraserP { + init(erasing: T) {} +} + +@_typeEraser(AnyEraserP) +protocol EraserP {} +struct SomeEraserP: EraserP {} + +// rdar://113435870 - Make sure we allow an implicit init(erasing:) call. +dynamic func testDynamicOpaqueErase() -> some EraserP { + switch Bool.random() { default: SomeEraserP() } +} From e8465707205cc22ddf66526e3ca4b8ddeb0f2de8 Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Wed, 30 Aug 2023 12:57:29 +0100 Subject: [PATCH 2/3] [Sema] Catch invalid if/switch exprs in more places Move out-of-place SingleValueStmtExpr checking into `performSyntacticExprDiagnostics`, to ensure we catch all expressions. Previously we did the walk as a part of Decl-based MiscDiagnostics, but it turns out that can miss expressions in property initializers, subscript default arguments, and custom attrs. This does mean that we'll now no longer diagnose out-of-place if/switch exprs if the expression didn't type-check, but that's consistent with the rest of MiscDiagnostics, and I don't think it will be a major issue in practice. We probably ought to consider moving this checking into PreCheckExpr, but that would require first separating out SequenceExpr folding, which has other consequences, and so I'm leaving as future work for now. --- lib/Sema/MiscDiagnostics.cpp | 71 ++++++++++---- lib/Sema/MiscDiagnostics.h | 15 ++- lib/Sema/TypeCheckConstraints.cpp | 25 +++-- test/Constraints/closures.swift | 8 +- test/Constraints/if_expr.swift | 42 +++++++- test/Constraints/switch_expr.swift | 46 ++++++++- .../Inputs/syntax_macro_definitions.swift | 14 +++ test/Macros/if_expr.swift | 18 ++++ test/Parse/recovery.swift | 6 +- test/SILGen/if_expr.swift | 5 + test/SILGen/switch_expr.swift | 5 + test/expr/unary/if_expr.swift | 81 ++++++++++++++-- test/expr/unary/switch_expr.swift | 97 +++++++++++++++---- 13 files changed, 363 insertions(+), 70 deletions(-) create mode 100644 test/Macros/if_expr.swift diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index e89448efa89a7..a6c5cd843c4e1 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -3850,7 +3850,20 @@ class SingleValueStmtUsageChecker final : public ASTWalker { llvm::DenseSet ValidSingleValueStmtExprs; public: - SingleValueStmtUsageChecker(ASTContext &ctx) : Ctx(ctx), Diags(ctx.Diags) {} + SingleValueStmtUsageChecker( + ASTContext &ctx, ASTNode root, + llvm::Optional contextualPurpose) + : Ctx(ctx), Diags(ctx.Diags) { + assert(!root.is() || contextualPurpose && + "Must provide contextual purpose for expr"); + + // If we have a contextual purpose, this is for an expression. Check if it's + // an expression in a valid position. + if (contextualPurpose) { + markAnyValidTopLevelSingleValueStmt(root.get(), + *contextualPurpose); + } + } private: /// Mark a given expression as a valid position for a SingleValueStmtExpr. @@ -3862,8 +3875,23 @@ class SingleValueStmtUsageChecker final : public ASTWalker { ValidSingleValueStmtExprs.insert(SVE); } + /// Mark a valid top-level expression with a given contextual purpose. + void markAnyValidTopLevelSingleValueStmt(Expr *E, ContextualTypePurpose ctp) { + // Allowed in returns, throws, and bindings. + switch (ctp) { + case CTP_ReturnStmt: + case CTP_ReturnSingleExpr: + case CTP_ThrowStmt: + case CTP_Initialization: + markValidSingleValueStmt(E); + break; + default: + break; + } + } + MacroWalking getMacroWalkingBehavior() const override { - return MacroWalking::Expansion; + return MacroWalking::ArgumentsAndExpansion; } AssignExpr *findAssignment(Expr *E) const { @@ -3989,19 +4017,26 @@ class SingleValueStmtUsageChecker final : public ASTWalker { if (auto *PBD = dyn_cast(D)) { for (auto idx : range(PBD->getNumPatternEntries())) markValidSingleValueStmt(PBD->getInit(idx)); + + return Action::Continue(); } - // Valid as a single expression body of a function. This is needed in - // addition to ReturnStmt checking, as we will remove the return if the - // expression is inferred to be Never. - if (auto *AFD = dyn_cast(D)) { - if (AFD->hasSingleExpressionBody()) - markValidSingleValueStmt(AFD->getSingleExpressionBody()); - } - return Action::Continue(); + // We don't want to walk into any other decl, we will visit them as part of + // typeCheckDecl. + return Action::SkipChildren(); } }; } // end anonymous namespace +void swift::diagnoseOutOfPlaceExprs( + ASTContext &ctx, ASTNode root, + llvm::Optional contextualPurpose) { + // TODO: We ought to consider moving this into pre-checking such that we can + // still diagnose on invalid code, and don't have to traverse over implicit + // exprs. We need to first separate out SequenceExpr folding though. + SingleValueStmtUsageChecker sveChecker(ctx, root, contextualPurpose); + root.walk(sveChecker); +} + /// Apply the warnings managed by VarDeclUsageChecker to the top level /// code declarations that haven't been checked yet. void swift:: @@ -4009,8 +4044,6 @@ performTopLevelDeclDiagnostics(TopLevelCodeDecl *TLCD) { auto &ctx = TLCD->getDeclContext()->getASTContext(); VarDeclUsageChecker checker(TLCD, ctx.Diags); TLCD->walk(checker); - SingleValueStmtUsageChecker sveChecker(ctx); - TLCD->walk(sveChecker); } /// Perform diagnostics for func/init/deinit declarations. @@ -4026,10 +4059,6 @@ void swift::performAbstractFuncDeclDiagnostics(AbstractFunctionDecl *AFD) { auto &ctx = AFD->getDeclContext()->getASTContext(); VarDeclUsageChecker checker(AFD, ctx.Diags); AFD->walk(checker); - - // Do a similar walk to check for out of place SingleValueStmtExprs. - SingleValueStmtUsageChecker sveChecker(ctx); - AFD->walk(sveChecker); } auto *body = AFD->getBody(); @@ -5864,10 +5893,10 @@ diagnoseDictionaryLiteralDuplicateKeyEntries(const Expr *E, //===----------------------------------------------------------------------===// /// Emit diagnostics for syntactic restrictions on a given expression. -void swift::performSyntacticExprDiagnostics(const Expr *E, - const DeclContext *DC, - bool isExprStmt, - bool disableExprAvailabilityChecking) { +void swift::performSyntacticExprDiagnostics( + const Expr *E, const DeclContext *DC, + llvm::Optional contextualPurpose, bool isExprStmt, + bool disableExprAvailabilityChecking, bool disableOutOfPlaceExprChecking) { auto &ctx = DC->getASTContext(); TypeChecker::diagnoseSelfAssignment(E); diagSyntacticUseRestrictions(E, DC, isExprStmt); @@ -5886,6 +5915,8 @@ void swift::performSyntacticExprDiagnostics(const Expr *E, diagnoseConstantArgumentRequirement(E, DC); diagUnqualifiedAccessToMethodNamedSelf(E, DC); diagnoseDictionaryLiteralDuplicateKeyEntries(E, DC); + if (!disableOutOfPlaceExprChecking) + diagnoseOutOfPlaceExprs(ctx, const_cast(E), contextualPurpose); } void swift::performStmtDiagnostics(const Stmt *S, DeclContext *DC) { diff --git a/lib/Sema/MiscDiagnostics.h b/lib/Sema/MiscDiagnostics.h index 217062eda1ac1..d58a2e09b08a6 100644 --- a/lib/Sema/MiscDiagnostics.h +++ b/lib/Sema/MiscDiagnostics.h @@ -28,6 +28,7 @@ namespace swift { class ApplyExpr; class CallExpr; class ClosureExpr; + enum ContextualTypePurpose : uint8_t; class DeclContext; class Decl; class Expr; @@ -37,10 +38,22 @@ namespace swift { class ValueDecl; class ForEachStmt; +/// Diagnose any expressions that appear in an unsupported position. If visiting +/// an expression directly, its \p contextualPurpose should be provided to +/// evaluate its position. +void diagnoseOutOfPlaceExprs( + ASTContext &ctx, ASTNode root, + llvm::Optional contextualPurpose); + /// Emit diagnostics for syntactic restrictions on a given expression. +/// +/// Note: \p contextualPurpose must be non-nil, unless +/// \p disableOutOfPlaceExprChecking is set to \c true. void performSyntacticExprDiagnostics( const Expr *E, const DeclContext *DC, - bool isExprStmt, bool disableExprAvailabilityChecking = false); + llvm::Optional contextualPurpose, + bool isExprStmt, bool disableExprAvailabilityChecking = false, + bool disableOutOfPlaceExprChecking = false); /// Emit diagnostics for a given statement. void performStmtDiagnostics(const Stmt *S, DeclContext *DC); diff --git a/lib/Sema/TypeCheckConstraints.cpp b/lib/Sema/TypeCheckConstraints.cpp index bbdfe49fc7221..fa925981a0de8 100644 --- a/lib/Sema/TypeCheckConstraints.cpp +++ b/lib/Sema/TypeCheckConstraints.cpp @@ -299,7 +299,11 @@ class FunctionSyntacticDiagnosticWalker : public ASTWalker { } PreWalkResult walkToExprPre(Expr *expr) override { - performSyntacticExprDiagnostics(expr, dcStack.back(), /*isExprStmt=*/false); + // We skip out-of-place expr checking here since we've already performed it. + performSyntacticExprDiagnostics(expr, dcStack.back(), /*ctp*/ llvm::None, + /*isExprStmt=*/false, + /*disableAvailabilityChecking*/ false, + /*disableOutOfPlaceExprChecking*/ true); if (auto closure = dyn_cast(expr)) { if (closure->isSeparatelyTypeChecked()) { @@ -346,8 +350,9 @@ void constraints::performSyntacticDiagnosticsForTarget( switch (target.kind) { case SyntacticElementTarget::Kind::expression: { // First emit diagnostics for the main expression. - performSyntacticExprDiagnostics(target.getAsExpr(), dc, - isExprStmt, disableExprAvailabilityChecking); + performSyntacticExprDiagnostics( + target.getAsExpr(), dc, target.getExprContextualTypePurpose(), + isExprStmt, disableExprAvailabilityChecking); return; } @@ -356,17 +361,25 @@ void constraints::performSyntacticDiagnosticsForTarget( // First emit diagnostics for the main expression. performSyntacticExprDiagnostics(stmt->getTypeCheckedSequence(), dc, - isExprStmt, + CTP_ForEachSequence, isExprStmt, disableExprAvailabilityChecking); if (auto *whereExpr = stmt->getWhere()) - performSyntacticExprDiagnostics(whereExpr, dc, /*isExprStmt*/ false); + performSyntacticExprDiagnostics(whereExpr, dc, CTP_Condition, + /*isExprStmt*/ false); return; } case SyntacticElementTarget::Kind::function: { + // Check for out of place expressions. This needs to be done on the entire + // function body rather than on individual expressions since we need the + // context of the parent nodes. + auto *body = target.getFunctionBody(); + diagnoseOutOfPlaceExprs(dc->getASTContext(), body, + /*contextualPurpose*/ llvm::None); + FunctionSyntacticDiagnosticWalker walker(dc); - target.getFunctionBody()->walk(walker); + body->walk(walker); return; } case SyntacticElementTarget::Kind::closure: diff --git a/test/Constraints/closures.swift b/test/Constraints/closures.swift index 16c4d719be063..affedf8c6edf6 100644 --- a/test/Constraints/closures.swift +++ b/test/Constraints/closures.swift @@ -1149,11 +1149,9 @@ struct R_76250381 { // rdar://77022842 - crash due to a missing argument to a ternary operator func rdar77022842(argA: Bool? = nil, argB: Bool? = nil) { if let a = argA ?? false, if let b = argB ?? { - // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} - // expected-error@-2 {{initializer for conditional binding must have Optional type, not 'Bool'}} - // expected-error@-3 {{cannot convert value of type '() -> ()' to expected argument type 'Bool?'}} - // expected-error@-4 {{cannot convert value of type 'Void' to expected condition type 'Bool'}} - // expected-error@-5 {{'if' must have an unconditional 'else' to be used as expression}} + // expected-error@-1 {{initializer for conditional binding must have Optional type, not 'Bool'}} + // expected-error@-2 {{cannot convert value of type '() -> ()' to expected argument type 'Bool?'}} + // expected-error@-3 {{cannot convert value of type 'Void' to expected condition type 'Bool'}} } // expected-error {{expected '{' after 'if' condition}} } diff --git a/test/Constraints/if_expr.swift b/test/Constraints/if_expr.swift index 21ef22247952e..b56b9306f4b2b 100644 --- a/test/Constraints/if_expr.swift +++ b/test/Constraints/if_expr.swift @@ -387,7 +387,6 @@ func testReturnMismatch() { let _ = if .random() { return 1 // expected-error {{unexpected non-void return value in void function}} // expected-note@-1 {{did you mean to add a return type?}} - // expected-error@-2 {{cannot 'return' in 'if' when used as expression}} } else { 0 } @@ -651,9 +650,46 @@ func builderWithBinding() -> Either { } } +@Builder +func builderWithInvalidBinding() -> Either { + let str = (if .random() { "a" } else { "b" }) + // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + if .random() { + str + } else { + 1 + } +} + +func takesBuilder(@Builder _ fn: () -> Either) {} + +func builderClosureWithBinding() { + takesBuilder { + // Make sure the binding gets type-checked as an if expression, but the + // other if block gets type-checked as a stmt. + let str = if .random() { "a" } else { "b" } + if .random() { + str + } else { + 1 + } + } +} + +func builderClosureWithInvalidBinding() { + takesBuilder { + let str = (if .random() { "a" } else { "b" }) + // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + if .random() { + str + } else { + 1 + } + } +} + func builderInClosure() { - func build(@Builder _ fn: () -> Either) {} - build { + takesBuilder { if .random() { "" } else { diff --git a/test/Constraints/switch_expr.swift b/test/Constraints/switch_expr.swift index b1b53a8d52000..066e0fc2e52b4 100644 --- a/test/Constraints/switch_expr.swift +++ b/test/Constraints/switch_expr.swift @@ -754,7 +754,7 @@ func builderNotPostfix() -> (Either, Bool) { @Builder func builderWithBinding() -> Either { - // Make sure the binding gets type-checked as an if expression, but the + // Make sure the binding gets type-checked as a switch expression, but the // other if block gets type-checked as a stmt. let str = switch Bool.random() { case true: "a" @@ -767,9 +767,49 @@ func builderWithBinding() -> Either { } } + +@Builder +func builderWithInvalidBinding() -> Either { + let str = (switch Bool.random() { default: "a" }) + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + if .random() { + str + } else { + 1 + } +} + +func takesBuilder(@Builder _ fn: () -> Either) {} + +func builderClosureWithBinding() { + takesBuilder { + // Make sure the binding gets type-checked as a switch expression, but the + // other if block gets type-checked as a stmt. + let str = switch Bool.random() { case true: "a" case false: "b" } + switch Bool.random() { + case true: + str + case false: + 1 + } + } +} + +func builderClosureWithInvalidBinding() { + takesBuilder { + let str = (switch Bool.random() { case true: "a" case false: "b" }) + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + switch Bool.random() { + case true: + str + case false: + 1 + } + } +} + func builderInClosure() { - func build(@Builder _ fn: () -> Either) {} - build { + takesBuilder { switch Bool.random() { case true: "" diff --git a/test/Macros/Inputs/syntax_macro_definitions.swift b/test/Macros/Inputs/syntax_macro_definitions.swift index c47f37923eeb8..9fd409df64c86 100644 --- a/test/Macros/Inputs/syntax_macro_definitions.swift +++ b/test/Macros/Inputs/syntax_macro_definitions.swift @@ -1998,6 +1998,20 @@ public struct NestedMagicLiteralMacro: ExpressionMacro { } } +public struct InvalidIfExprMacro: MemberMacro { + public static func expansion( + of node: AttributeSyntax, + providingMembersOf decl: some DeclGroupSyntax, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + return [""" + func bar() { + let _ = (if .random() { 0 } else { 1 }) + } + """] + } +} + public struct InitWithProjectedValueWrapperMacro: PeerMacro { public static func expansion( of node: AttributeSyntax, diff --git a/test/Macros/if_expr.swift b/test/Macros/if_expr.swift new file mode 100644 index 0000000000000..beb745e2e232f --- /dev/null +++ b/test/Macros/if_expr.swift @@ -0,0 +1,18 @@ +// RUN: %empty-directory(%t) +// RUN: %host-build-swift -swift-version 5 -emit-library -o %t/%target-library-name(MacroDefinition) -module-name=MacroDefinition %S/Inputs/syntax_macro_definitions.swift -g -no-toolchain-stdlib-rpath + +// RUN: not %target-swift-frontend -typecheck %s -swift-version 5 -load-plugin-library %t/%target-library-name(MacroDefinition) -serialize-diagnostics-path %t/diags.dia + +// RUN: c-index-test -read-diagnostics %t/diags.dia 2>&1 | %FileCheck -check-prefix DIAGS %s + +// REQUIRES: swift_swift_parser + +@attached(member, names: named(bar)) +macro TestMacro(_ x: T) = #externalMacro(module: "MacroDefinition", type: "InvalidIfExprMacro") + +// Make sure we diagnose both the use in the custom attribute and in the expansion. +// DIAGS-DAG: if_expr.swift:[[@LINE+1]]:12: error: 'if' may only be used as expression in return, throw, or as the source of an assignment +@TestMacro(if .random() { 0 } else { 0 }) +struct S {} + +// DIAGS-DAG: @__swiftmacro_7if_expr1S9TestMacrofMm_.swift:2:12: error: 'if' may only be used as expression in return, throw, or as the source of an assignment diff --git a/test/Parse/recovery.swift b/test/Parse/recovery.swift index cd3a9ae333fff..afe6fe65e2496 100644 --- a/test/Parse/recovery.swift +++ b/test/Parse/recovery.swift @@ -81,9 +81,7 @@ func missingControllingExprInIf() { if { // expected-error {{missing condition in 'if' statement}} } // expected-error {{expected '{' after 'if' condition}} - // expected-error@-2 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} - // expected-error@-3 {{cannot convert value of type 'Void' to expected condition type 'Bool'}} - // expected-error@-4 {{'if' must have an unconditional 'else' to be used as expression}} + // expected-error@-2 {{cannot convert value of type 'Void' to expected condition type 'Bool'}} if // expected-error {{missing condition in 'if' statement}} { @@ -239,7 +237,7 @@ func missingControllingExprInForEach() { func missingControllingExprInSwitch() { switch - switch { // expected-error {{expected expression in 'switch' statement}} expected-error {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + switch { // expected-error {{expected expression in 'switch' statement}} } // expected-error {{expected '{' after 'switch' subject expression}} switch // expected-error {{expected expression in 'switch' statement}} expected-error {{'switch' statement body must have at least one 'case' or 'default' block}} diff --git a/test/SILGen/if_expr.swift b/test/SILGen/if_expr.swift index 2a8f5c4ce55ce..82ef281347245 100644 --- a/test/SILGen/if_expr.swift +++ b/test/SILGen/if_expr.swift @@ -508,3 +508,8 @@ func testNever1() -> Never { func testNever2() -> Never { if .random() { fatalError() } else { fatalError() } } + +func testCaptureList() -> Int { + let fn = { [x = if .random() { 0 } else { 1 }] in x } + return fn() +} diff --git a/test/SILGen/switch_expr.swift b/test/SILGen/switch_expr.swift index c34e5cb2741c0..28c02b2d0531b 100644 --- a/test/SILGen/switch_expr.swift +++ b/test/SILGen/switch_expr.swift @@ -707,3 +707,8 @@ extension Never { self = switch value { case let v: v } } } + +func testCaptureList() -> Int { + let fn = { [x = switch Bool.random() { case true: 0 case false: 1 }] in x } + return fn() +} diff --git a/test/expr/unary/if_expr.swift b/test/expr/unary/if_expr.swift index 7f52006e4d859..b63b1089934c5 100644 --- a/test/expr/unary/if_expr.swift +++ b/test/expr/unary/if_expr.swift @@ -115,8 +115,7 @@ takesValue(if .random() { 0 } else { 1 }) // Cannot parse labeled if as expression. do { takesValue(x: if .random() { 0 } else { 1 }) - // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} - // expected-error@-2 {{extraneous argument label 'x:' in call}} + // expected-error@-1 {{extraneous argument label 'x:' in call}} takesValue(_: x: if .random() { 0 } else { 1 }) // expected-error@-1 {{expected expression in list of expressions}} @@ -140,7 +139,6 @@ takesValueAndTrailingClosure(if .random() { 0 } else { 1 }) { 2 } func takesInOut(_ x: inout T) {} takesInOut(&if .random() { 1 } else { 2 }) // expected-error@-1 {{cannot pass immutable value of type 'Int' as inout argument}} -// expected-error@-2 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} struct HasSubscript { static subscript(x: Int) -> Void { () } @@ -462,9 +460,9 @@ let o = !if .random() { true } else { false } // expected-error {{'if' may only let p = if .random() { 1 } else { 2 } + // expected-error {{ambiguous use of operator '+'}} if .random() { 3 } else { 4 } + if .random() { 5 } else { 6 } -// expected-error@-3 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} -// expected-error@-3 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} -// expected-error@-3 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + +let p1 = if .random() { 1 } else { 2 } + 5 +// expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} let q = .random() ? if .random() { 1 } else { 2 } : if .random() { 3 } else { 4 } @@ -503,8 +501,7 @@ do { // FIXME: The type error is likely due to not solving the conjunction before attempting default type var bindings. let _ = (if .random() { Int?.none } else { 1 as Int? })?.bitWidth - // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} - // expected-error@-2 {{type of expression is ambiguous without a type annotation}} + // expected-error@-1 {{type of expression is ambiguous without a type annotation}} } do { let _ = if .random() { Int?.none } else { 1 as Int? }! @@ -598,10 +595,26 @@ func returnBranches() -> Int { func returnBranches1() -> Int { return if .random() { // expected-error {{cannot convert return expression of type 'Void' to return type 'Int'}} + return 0 + } else { + return 1 + } +} + +func returnBranchVoid() { + return if .random() { return } else { return () } + // expected-error@-1 2{{cannot 'return' in 'if' when used as expression}} +} + +func returnBranchBinding() -> Int { + let x = if .random() { + // expected-warning@-1 {{constant 'x' inferred to have type 'Void', which may be unexpected}} + // expected-note@-2 {{add an explicit type annotation to silence this warning}} return 0 // expected-error {{cannot 'return' in 'if' when used as expression}} } else { return 1 // expected-error {{cannot 'return' in 'if' when used as expression}} } + return x // expected-error {{cannot convert return expression of type 'Void' to return type 'Int'}} } func returnBranches2() -> Int { @@ -1029,3 +1042,55 @@ struct SomeEraserP: EraserP {} dynamic func testDynamicOpaqueErase() -> some EraserP { if .random() { SomeEraserP() } else { SomeEraserP() } } + +struct NonExhaustiveProperty { + let i = if .random() { 0 } + // expected-error@-1 {{'if' must have an unconditional 'else' to be used as expression}} +} + +// MARK: Out of place if exprs + +func inDefaultArg(x: Int = if .random() { 0 } else { 0 }) {} +// expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + +func inDefaultArg2(x: Int = { (if .random() { 0 } else { 0 }) }()) {} +// expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + +struct InType { + let inPropertyInit1 = if .random() { 0 } else { 1 } + let inPropertyInit2 = (if .random() { 0 } else { 1 }) + // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + + let inPropertyInit3 = { + let _ = if .random() { 0 } else { 1 } + let _ = (if .random() { 0 } else { 1 }) + // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + + func foo() { + let _ = if .random() { 0 } else { 1 } + let _ = (if .random() { 0 } else { 1 }) + // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + } + if .random() { + return if .random() { 0 } else { 1 } + } else { + return (if .random() { 0 } else { 1 }) + // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + } + } + + subscript(x: Int = if .random() { 0 } else { 0 }) -> Int { + // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + + let _ = if .random() { 0 } else { 1 } + let _ = (if .random() { 0 } else { 1 }) + // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + return 0 + } +} + +func testCaptureList() { + let _ = { [x = if .random() { 0 } else { 1 }] in x } + let _ = { [x = (if .random() { 0 } else { 1 })] in x } + // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} +} diff --git a/test/expr/unary/switch_expr.swift b/test/expr/unary/switch_expr.swift index a164624a9ad0b..88c11409588d3 100644 --- a/test/expr/unary/switch_expr.swift +++ b/test/expr/unary/switch_expr.swift @@ -163,7 +163,6 @@ takesValue(switch Bool.random() { case true: 1 case false: 2 }) do { takesValue(x: switch Bool.random() { case true: 1 case false: 2 }) // expected-error@-1 {{extraneous argument label 'x:' in call}} - // expected-error@-2 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} takesValue(_: x: switch Bool.random() { case true: 1 case false: 2 }) // expected-error@-1 {{expected expression in list of expressions}} @@ -187,7 +186,6 @@ takesValueAndTrailingClosure(switch Bool.random() { case true: 0 case false: 1 } func takesInOut(_ x: inout T) {} takesInOut(&switch Bool.random() { case true: 1 case false: 2 }) // expected-error@-1 {{cannot pass immutable value of type 'Int' as inout argument}} -// expected-error@-2 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} struct HasSubscript { static subscript(x: Int) -> Void { () } @@ -495,21 +493,18 @@ do { // the user to just wrap the expression in parens. do { _ = (switch fatalError() {}, 1) // expected-error {{expected '{' after 'switch' subject expression}} - // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} - // expected-error@-2 {{extra trailing closure passed in call}} + // expected-error@-1 {{extra trailing closure passed in call}} _ = (switch fatalError() { #if FOO // expected-error@-1 {{extra trailing closure passed in call}} - // expected-error@-2 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} #endif }, 0) // expected-error {{expected '{' after 'switch' subject expression}} _ = (switch Bool.random() { #if FOO - // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} - // expected-error@-2 {{cannot pass immutable value of type '() -> ()' as inout argument}} - // expected-error@-3 {{type '() -> ()' cannot conform to 'RandomNumberGenerator'}} - // expected-note@-4 {{required by static method 'random(using:)' where 'T' = '() -> ()'}} - // expected-note@-5 {{only concrete types such as structs, enums and classes can conform to protocols}} + // expected-error@-1 {{cannot pass immutable value of type '() -> ()' as inout argument}} + // expected-error@-2 {{type '() -> ()' cannot conform to 'RandomNumberGenerator'}} + // expected-note@-3 {{required by static method 'random(using:)' where 'T' = '() -> ()'}} + // expected-note@-4 {{only concrete types such as structs, enums and classes can conform to protocols}} case true: // expected-error {{'case' label can only appear inside a 'switch' statement}} 1 case false: // expected-error {{'case' label can only appear inside a 'switch' statement}} @@ -518,11 +513,10 @@ do { }, 0) // expected-error {{expected '{' after 'switch' subject expression}} _ = (switch Bool.random() { #if FOO - // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} - // expected-error@-2 {{cannot pass immutable value of type '() -> ()' as inout argument}} - // expected-error@-3 {{type '() -> ()' cannot conform to 'RandomNumberGenerator'}} - // expected-note@-4 {{required by static method 'random(using:)' where 'T' = '() -> ()'}} - // expected-note@-5 {{only concrete types such as structs, enums and classes can conform to protocols}} + // expected-error@-1 {{cannot pass immutable value of type '() -> ()' as inout argument}} + // expected-error@-2 {{type '() -> ()' cannot conform to 'RandomNumberGenerator'}} + // expected-note@-3 {{required by static method 'random(using:)' where 'T' = '() -> ()'}} + // expected-note@-4 {{only concrete types such as structs, enums and classes can conform to protocols}} case true: // expected-error {{'case' label can only appear inside a 'switch' statement}} 1 case false: // expected-error {{'case' label can only appear inside a 'switch' statement}} @@ -620,9 +614,9 @@ let m = !switch Bool.random() { case true: true case false: true } let n = switch Bool.random() { case true: 1 case false: 2 } + // expected-error {{ambiguous use of operator '+'}} switch Bool.random() { case true: 3 case false: 4 } + switch Bool.random() { case true: 5 case false: 6 } -// expected-error@-3 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} -// expected-error@-3 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} -// expected-error@-3 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + +let n1 = switch Bool.random() { case true: 1 case false: 2 } + 5 +// expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} let p = .random() ? switch Bool.random() { case true: 1 case false: 2 } : switch Bool.random() { case true: 3 case false: 4 } @@ -661,8 +655,7 @@ do { // FIXME: The type error is likely due to not solving the conjunction before attempting default type var bindings. let _ = (switch Bool.random() { case true: Int?.none case false: 1 })?.bitWidth - // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} - // expected-error@-2 {{type of expression is ambiguous without a type annotation}} + // expected-error@-1 {{type of expression is ambiguous without a type annotation}} } do { let _ = switch Bool.random() { case true: Int?.none case false: 1 }! @@ -751,11 +744,28 @@ func returnBranches() -> Int { func returnBranches1() -> Int { return switch Bool.random() { // expected-error {{cannot convert return expression of type 'Void' to return type 'Int'}} + case true: + return 0 + case false: + return 1 + } +} + +func returnBranchVoid() { + return switch Bool.random() { case true: return case false: return () } + // expected-error@-1 2{{cannot 'return' in 'switch' when used as expression}} +} + +func returnBranchBinding() -> Int { + let x = switch Bool.random() { + // expected-warning@-1 {{constant 'x' inferred to have type 'Void', which may be unexpected}} + // expected-note@-2 {{add an explicit type annotation to silence this warning}} case true: return 0 // expected-error {{cannot 'return' in 'switch' when used as expression}} case false: return 1 // expected-error {{cannot 'return' in 'switch' when used as expression}} } + return x // expected-error {{cannot convert return expression of type 'Void' to return type 'Int'}} } func returnBranches2() -> Int { @@ -1301,3 +1311,50 @@ struct SomeEraserP: EraserP {} dynamic func testDynamicOpaqueErase() -> some EraserP { switch Bool.random() { default: SomeEraserP() } } + +// MARK: Out of place switch exprs + +func inDefaultArg(x: Int = switch Bool.random() { default: 0 }) {} +// expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + +func inDefaultArg2(x: Int = { (switch Bool.random() { default: 0 }) }()) {} +// expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + +struct InType { + let inPropertyInit1 = switch Bool.random() { case true: 0 case false: 1 } + let inPropertyInit2 = (switch Bool.random() { case true: 0 case false: 1 }) + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + + let inPropertyInit3 = { + let _ = switch Bool.random() { case true: 0 case false: 1 } + let _ = (switch Bool.random() { case true: 0 case false: 1 }) + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + + func foo() { + let _ = switch Bool.random() { case true: 0 case false: 1 } + let _ = (switch Bool.random() { case true: 0 case false: 1 }) + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + } + if .random() { + return switch Bool.random() { case true: 0 case false: 1 } + } else { + return (switch Bool.random() { case true: 0 case false: 1 }) + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + } + } + + subscript(x: Int = switch Bool.random() { case true: 0 case false: 0 }) -> Int { + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + + let _ = switch Bool.random() { case true: 0 case false: 1 } + let _ = (switch Bool.random() { case true: 0 case false: 1 }) + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + return 0 + } +} + +func testCaptureList() { + let _ = { [x = switch Bool.random() { default: 1 }] in x } + let _ = { [x = (switch Bool.random() { default: 1 })] in x } + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} +} From 5e7b70be5202e2892742f28de647c5df8a3cec6e Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Wed, 20 Sep 2023 17:45:21 +0100 Subject: [PATCH 3/3] [test] Fix-up test for 5.9 --- test/expr/unary/if_expr.swift | 2 +- test/expr/unary/switch_expr.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/expr/unary/if_expr.swift b/test/expr/unary/if_expr.swift index b63b1089934c5..c6275e3a6ca27 100644 --- a/test/expr/unary/if_expr.swift +++ b/test/expr/unary/if_expr.swift @@ -371,7 +371,7 @@ var d = if .random() { if .random() { 1 } else { 2 } } else { 3 } d = if .random() { 0 } else { 1 } -let e = "\(if .random() { 1 } else { 2 })" // expected-error {{'if' may only be used as expression in return, throw, or as the source of an assignment}} +let e = "\(if .random() { 1 } else { 2 })" // expected-error 2{{'if' may only be used as expression in return, throw, or as the source of an assignment}} let f = { if .random() { 1 } else { 2 } } diff --git a/test/expr/unary/switch_expr.swift b/test/expr/unary/switch_expr.swift index 88c11409588d3..5341791344412 100644 --- a/test/expr/unary/switch_expr.swift +++ b/test/expr/unary/switch_expr.swift @@ -446,7 +446,7 @@ case false: } let e = "\(switch Bool.random() { case true: 1 case false: 2 })" -// expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} +// expected-error@-1 2{{'switch' may only be used as expression in return, throw, or as the source of an assignment}} let f = { switch Bool.random() { case true: 1 case false: 2 } }