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/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 ab88e23a0642a..c6275e3a6ca27 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 { () } @@ -373,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 } } @@ -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 { @@ -1016,3 +1029,68 @@ 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() } +} + +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 b498598375dfb..5341791344412 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 { () } @@ -448,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 } } @@ -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 { @@ -1288,3 +1298,63 @@ 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() } +} + +// 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}} +}