diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index 98f5c9f295b..37f7e544ef0 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -4378,206 +4378,6 @@ void VarDeclUsageChecker::handleIfConfig(IfConfigDecl *ICD) { } } -namespace { -class SingleValueStmtUsageChecker final : public ASTWalker { - ASTContext &Ctx; - DiagnosticEngine &Diags; - llvm::DenseSet ValidSingleValueStmtExprs; - -public: - SingleValueStmtUsageChecker( - ASTContext &ctx, ASTNode root, - std::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. - void markValidSingleValueStmt(Expr *E) { - if (!E) - return; - - if (auto *SVE = SingleValueStmtExpr::tryDigOutSingleValueStmtExpr(E)) - 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_ThrowStmt: - case CTP_Initialization: - markValidSingleValueStmt(E); - break; - default: - break; - } - } - - MacroWalking getMacroWalkingBehavior() const override { - return MacroWalking::ArgumentsAndExpansion; - } - - AssignExpr *findAssignment(Expr *E) const { - // Don't consider assignments if we have a parent expression (as otherwise - // this would be effectively allowing it in an arbitrary expression - // position). - if (Parent.getAsExpr()) - return nullptr; - - // Look through optional exprs, which are present for e.g x?.y = z, as - // we wrap the entire assign in the optional evaluation of the destination. - if (auto *OEE = dyn_cast(E)) { - E = OEE->getSubExpr(); - while (auto *IIO = dyn_cast(E)) - E = IIO->getSubExpr(); - } - return dyn_cast(E); - } - - PreWalkResult walkToExprPre(Expr *E) override { - if (auto *SVE = dyn_cast(E)) { - // Diagnose a SingleValueStmtExpr in a context that we do not currently - // support. If we start allowing these in arbitrary places, we'll need - // to ensure that autoclosures correctly contextualize them. - if (!ValidSingleValueStmtExprs.contains(SVE)) { - Diags.diagnose(SVE->getLoc(), diag::single_value_stmt_out_of_place, - SVE->getStmt()->getKind()); - } - - // Diagnose invalid SingleValueStmtExprs. This should only happen for - // expressions in positions that we didn't support before - // (e.g assignment or *explicit* return). - auto *S = SVE->getStmt(); - auto mayProduceSingleValue = S->mayProduceSingleValue(Ctx); - switch (mayProduceSingleValue.getKind()) { - case IsSingleValueStmtResult::Kind::Valid: - break; - case IsSingleValueStmtResult::Kind::UnterminatedBranches: { - for (auto *branch : mayProduceSingleValue.getUnterminatedBranches()) { - if (auto *BS = dyn_cast(branch)) { - if (BS->empty()) { - Diags.diagnose(branch->getStartLoc(), - diag::single_value_stmt_branch_empty, - S->getKind()); - continue; - } - } - // TODO: The wording of this diagnostic will need tweaking if either - // implicit last expressions or 'then' statements are enabled by - // default. - Diags.diagnose(branch->getEndLoc(), - diag::single_value_stmt_branch_must_end_in_result, - S->getKind(), isa(S)); - } - break; - } - case IsSingleValueStmtResult::Kind::NonExhaustiveIf: { - Diags.diagnose(S->getStartLoc(), - diag::if_expr_must_be_syntactically_exhaustive); - break; - } - case IsSingleValueStmtResult::Kind::NonExhaustiveDoCatch: { - Diags.diagnose(S->getStartLoc(), - diag::do_catch_expr_must_be_syntactically_exhaustive); - break; - } - case IsSingleValueStmtResult::Kind::HasLabel: { - // FIXME: We should offer a fix-it to remove (currently we don't track - // the colon SourceLoc). - auto label = cast(S)->getLabelInfo(); - Diags.diagnose(label.Loc, - diag::single_value_stmt_must_be_unlabeled, S->getKind()) - .highlight(label.Loc); - break; - } - case IsSingleValueStmtResult::Kind::InvalidJumps: { - // Diagnose each invalid jump. - for (auto *jump : mayProduceSingleValue.getInvalidJumps()) { - Diags.diagnose(jump->getStartLoc(), - diag::cannot_jump_in_single_value_stmt, - jump->getKind(), S->getKind()) - .highlight(jump->getSourceRange()); - } - break; - } - case IsSingleValueStmtResult::Kind::NoResult: - // This is fine, we will have typed the expression as Void (we verify - // as such in the ASTVerifier). - break; - case IsSingleValueStmtResult::Kind::CircularReference: - // Already diagnosed. - break; - case IsSingleValueStmtResult::Kind::UnhandledStmt: - break; - } - return Action::Continue(E); - } - - // Valid as the source of an assignment. - if (auto *AE = findAssignment(E)) - markValidSingleValueStmt(AE->getSrc()); - - // Valid as a single expression body of a closure. This is needed in - // addition to ReturnStmt checking, as we will remove the return if the - // expression is inferred to be Never. - if (auto *ACE = dyn_cast(E)) { - if (ACE->hasSingleExpressionBody()) - markValidSingleValueStmt(ACE->getSingleExpressionBody()); - } - return Action::Continue(E); - } - - PreWalkResult walkToStmtPre(Stmt *S) override { - // Valid in a return/throw/then. - if (auto *RS = dyn_cast(S)) { - if (RS->hasResult()) - markValidSingleValueStmt(RS->getResult()); - } - if (auto *TS = dyn_cast(S)) - markValidSingleValueStmt(TS->getSubExpr()); - - if (auto *TS = dyn_cast(S)) - markValidSingleValueStmt(TS->getResult()); - - return Action::Continue(S); - } - - PreWalkAction walkToDeclPre(Decl *D) override { - // Valid as an initializer of a pattern binding. - if (auto *PBD = dyn_cast(D)) { - for (auto idx : range(PBD->getNumPatternEntries())) - markValidSingleValueStmt(PBD->getInit(idx)); - - return Action::Continue(); - } - // We don't want to walk into any other decl, we will visit them as part of - // typeCheckDecl. - return Action::SkipNode(); - } -}; -} // end anonymous namespace - -void swift::diagnoseOutOfPlaceExprs( - ASTContext &ctx, ASTNode root, - std::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:: @@ -6426,10 +6226,9 @@ diagnoseDictionaryLiteralDuplicateKeyEntries(const Expr *E, //===----------------------------------------------------------------------===// /// Emit diagnostics for syntactic restrictions on a given expression. -void swift::performSyntacticExprDiagnostics( - const Expr *E, const DeclContext *DC, - std::optional contextualPurpose, bool isExprStmt, - bool disableOutOfPlaceExprChecking) { +void swift::performSyntacticExprDiagnostics(const Expr *E, + const DeclContext *DC, + bool isExprStmt) { auto &ctx = DC->getASTContext(); TypeChecker::diagnoseSelfAssignment(E); diagSyntacticUseRestrictions(E, DC, isExprStmt); @@ -6448,8 +6247,6 @@ void swift::performSyntacticExprDiagnostics( 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 a02c20b6e84..0b865008bb7 100644 --- a/lib/Sema/MiscDiagnostics.h +++ b/lib/Sema/MiscDiagnostics.h @@ -38,21 +38,9 @@ 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, - std::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, - std::optional contextualPurpose, bool isExprStmt, - bool disableOutOfPlaceExprChecking = false); + void performSyntacticExprDiagnostics(const Expr *E, const DeclContext *DC, + bool isExprStmt); /// Emit diagnostics for a given statement. void performStmtDiagnostics(const Stmt *S, DeclContext *DC); diff --git a/lib/Sema/PreCheckTarget.cpp b/lib/Sema/PreCheckTarget.cpp index 727a9c88d36..6bfb1927841 100644 --- a/lib/Sema/PreCheckTarget.cpp +++ b/lib/Sema/PreCheckTarget.cpp @@ -1054,6 +1054,11 @@ class PreCheckTarget final : public ASTWalker { /// Keep track of acceptable DiscardAssignmentExpr's. llvm::SmallPtrSet CorrectDiscardAssignmentExprs; + /// Keep track of any out-of-place SingleValueStmtExprs. We populate this as + /// we encounter SingleValueStmtExprs, and erase them as we walk up to a + /// valid parent in the post walk. + llvm::SetVector OutOfPlaceSingleValueStmtExprs; + /// Simplify expressions which are type sugar productions that got parsed /// as expressions due to the parser not knowing which identifiers are /// type names. @@ -1100,9 +1105,36 @@ class PreCheckTarget final : public ASTWalker { /// in simple pattern-like expressions, so we reject anything complex here. void markAcceptableDiscardExprs(Expr *E); -public: + /// Check and diagnose an invalid SingleValueStmtExpr. + void checkSingleValueStmtExpr(SingleValueStmtExpr *SVE); + + /// Diagnose any SingleValueStmtExprs in an unsupported position. + void + diagnoseOutOfPlaceSingleValueStmtExprs(const SyntacticElementTarget &target); + + /// Mark a given expression as a valid position for a SingleValueStmtExpr. + void markValidSingleValueStmt(Expr *E); + + /// For the given expr, mark any valid SingleValueStmtExpr children. + void markAnyValidSingleValueStmts(Expr *E); + + /// For the given statement, mark any valid SingleValueStmtExpr children. + void markAnyValidSingleValueStmts(Stmt *S); + PreCheckTarget(DeclContext *dc) : Ctx(dc->getASTContext()), DC(dc) {} +public: + static std::optional + check(const SyntacticElementTarget &target) { + PreCheckTarget checker(target.getDeclContext()); + auto newTarget = target.walk(checker); + if (!newTarget) + return std::nullopt; + // Diagnose any remaining out-of-place SingleValueStmtExprs. + checker.diagnoseOutOfPlaceSingleValueStmtExprs(*newTarget); + return *newTarget; + } + ASTContext &getASTContext() const { return Ctx; } bool walkToClosureExprPre(ClosureExpr *expr); @@ -1275,6 +1307,9 @@ public: if (auto *assignment = dyn_cast(expr)) markAcceptableDiscardExprs(assignment->getDest()); + if (auto *SVE = dyn_cast(expr)) + checkSingleValueStmtExpr(SVE); + return finish(true, expr); } @@ -1283,6 +1318,9 @@ public: assert(ExprStack.back() == expr); ExprStack.pop_back(); + // Mark any valid SingleValueStmtExpr children. + markAnyValidSingleValueStmts(expr); + // Type check the type parameters in an UnresolvedSpecializeExpr. if (auto *us = dyn_cast(expr)) { if (auto *typeExpr = simplifyUnresolvedSpecializeExpr(us)) @@ -1451,8 +1489,22 @@ public: return Action::Continue(stmt); } + PostWalkResult walkToStmtPost(Stmt *S) override { + markAnyValidSingleValueStmts(S); + return Action::Continue(S); + } + PreWalkAction walkToDeclPre(Decl *D) override { - return Action::VisitNodeIf(isa(D)); + return Action::VisitChildrenIf(isa(D)); + } + + PostWalkAction walkToDeclPost(Decl *D) override { + // Mark any valid SingleValueStmtExprs for initializations. + if (auto *PBD = dyn_cast(D)) { + for (auto idx : range(PBD->getNumPatternEntries())) + markValidSingleValueStmt(PBD->getInit(idx)); + } + return Action::Continue(); } PreWalkResult walkToPatternPre(Pattern *pattern) override { @@ -1487,6 +1539,145 @@ bool PreCheckTarget::walkToClosureExprPre(ClosureExpr *closure) { return true; } +void PreCheckTarget::markValidSingleValueStmt(Expr *E) { + if (!E) + return; + + if (auto *SVE = SingleValueStmtExpr::tryDigOutSingleValueStmtExpr(E)) + OutOfPlaceSingleValueStmtExprs.remove(SVE); +} + +void PreCheckTarget::checkSingleValueStmtExpr(SingleValueStmtExpr *SVE) { + // We add all SingleValueStmtExprs we see to the out-of-place list, then + // erase them as we walk up to valid parents. We do this instead of populating + // valid positions during the pre-walk to ensure we're looking at the AST + // after e.g folding SequenceExprs. + OutOfPlaceSingleValueStmtExprs.insert(SVE); + + // Diagnose invalid SingleValueStmtExprs. This should only happen for + // expressions in positions that we didn't support prior to their introduction + // (e.g assignment or *explicit* return). + auto &Diags = Ctx.Diags; + auto *S = SVE->getStmt(); + auto mayProduceSingleValue = S->mayProduceSingleValue(Ctx); + switch (mayProduceSingleValue.getKind()) { + case IsSingleValueStmtResult::Kind::Valid: + break; + case IsSingleValueStmtResult::Kind::UnterminatedBranches: { + for (auto *branch : mayProduceSingleValue.getUnterminatedBranches()) { + if (auto *BS = dyn_cast(branch)) { + if (BS->empty()) { + Diags.diagnose(branch->getStartLoc(), + diag::single_value_stmt_branch_empty, S->getKind()); + continue; + } + } + // TODO: The wording of this diagnostic will need tweaking if either + // implicit last expressions or 'then' statements are enabled by + // default. + Diags.diagnose(branch->getEndLoc(), + diag::single_value_stmt_branch_must_end_in_result, + S->getKind(), isa(S)); + } + break; + } + case IsSingleValueStmtResult::Kind::NonExhaustiveIf: { + Diags.diagnose(S->getStartLoc(), + diag::if_expr_must_be_syntactically_exhaustive); + break; + } + case IsSingleValueStmtResult::Kind::NonExhaustiveDoCatch: { + Diags.diagnose(S->getStartLoc(), + diag::do_catch_expr_must_be_syntactically_exhaustive); + break; + } + case IsSingleValueStmtResult::Kind::HasLabel: { + // FIXME: We should offer a fix-it to remove (currently we don't track + // the colon SourceLoc). + auto label = cast(S)->getLabelInfo(); + Diags.diagnose(label.Loc, + diag::single_value_stmt_must_be_unlabeled, S->getKind()) + .highlight(label.Loc); + break; + } + case IsSingleValueStmtResult::Kind::InvalidJumps: { + // Diagnose each invalid jump. + for (auto *jump : mayProduceSingleValue.getInvalidJumps()) { + Diags.diagnose(jump->getStartLoc(), + diag::cannot_jump_in_single_value_stmt, + jump->getKind(), S->getKind()) + .highlight(jump->getSourceRange()); + } + break; + } + case IsSingleValueStmtResult::Kind::NoResult: + // This is fine, we will have typed the expression as Void (we verify + // as such in the ASTVerifier). + break; + case IsSingleValueStmtResult::Kind::CircularReference: + // Already diagnosed. + break; + case IsSingleValueStmtResult::Kind::UnhandledStmt: + break; + } +} + +void PreCheckTarget::markAnyValidSingleValueStmts(Expr *E) { + auto findAssignment = [&]() -> AssignExpr * { + // Don't consider assignments if we have a parent expression (as otherwise + // this would be effectively allowing it in an arbitrary expression + // position). + if (Parent.getAsExpr()) + return nullptr; + + // Look through optional exprs, which are present for e.g x?.y = z, as + // we wrap the entire assign in the optional evaluation of the destination. + if (auto *OEE = dyn_cast(E)) { + E = OEE->getSubExpr(); + while (auto *IIO = dyn_cast(E)) + E = IIO->getSubExpr(); + } + return dyn_cast(E); + }; + + if (auto *AE = findAssignment()) + markValidSingleValueStmt(AE->getSrc()); +} + +void PreCheckTarget::markAnyValidSingleValueStmts(Stmt *S) { + // Valid in a return/throw/then. + if (auto *RS = dyn_cast(S)) { + if (RS->hasResult()) + markValidSingleValueStmt(RS->getResult()); + } + if (auto *TS = dyn_cast(S)) + markValidSingleValueStmt(TS->getSubExpr()); + + if (auto *TS = dyn_cast(S)) + markValidSingleValueStmt(TS->getResult()); +} + +void PreCheckTarget::diagnoseOutOfPlaceSingleValueStmtExprs( + const SyntacticElementTarget &target) { + // Top-level SingleValueStmtExprs are allowed in returns, throws, and + // bindings. + if (auto *E = target.getAsExpr()) { + switch (target.getExprContextualTypePurpose()) { + case CTP_ReturnStmt: + case CTP_ThrowStmt: + case CTP_Initialization: + markValidSingleValueStmt(E); + break; + default: + break; + } + } + for (auto *SVE : OutOfPlaceSingleValueStmtExprs) { + Ctx.Diags.diagnose(SVE->getLoc(), diag::single_value_stmt_out_of_place, + SVE->getStmt()->getKind()); + } +} + TypeExpr *PreCheckTarget::simplifyNestedTypeExpr(UnresolvedDotExpr *UDE) { if (!UDE->getName().isSimpleName() || UDE->getName().isSpecial()) @@ -2427,9 +2618,7 @@ bool ConstraintSystem::preCheckTarget(SyntacticElementTarget &target) { auto &ctx = DC->getASTContext(); FrontendStatsTracer StatsTracer(ctx.Stats, "precheck-target"); - PreCheckTarget preCheck(DC); - - auto newTarget = target.walk(preCheck); + auto newTarget = PreCheckTarget::check(target); if (!newTarget) return true; diff --git a/lib/Sema/TypeCheckConstraints.cpp b/lib/Sema/TypeCheckConstraints.cpp index 065f7316264..aa673489f46 100644 --- a/lib/Sema/TypeCheckConstraints.cpp +++ b/lib/Sema/TypeCheckConstraints.cpp @@ -340,10 +340,7 @@ public: } PreWalkResult walkToExprPre(Expr *expr) override { - // We skip out-of-place expr checking here since we've already performed it. - performSyntacticExprDiagnostics(expr, dcStack.back(), /*ctp*/ std::nullopt, - /*isExprStmt=*/false, - /*disableOutOfPlaceExprChecking*/ true); + performSyntacticExprDiagnostics(expr, dcStack.back(), /*isExprStmt=*/false); if (auto closure = dyn_cast(expr)) { if (closure->isSeparatelyTypeChecked()) { @@ -389,9 +386,7 @@ void constraints::performSyntacticDiagnosticsForTarget( switch (target.kind) { case SyntacticElementTarget::Kind::expression: { // First emit diagnostics for the main expression. - performSyntacticExprDiagnostics(target.getAsExpr(), dc, - target.getExprContextualTypePurpose(), - isExprStmt); + performSyntacticExprDiagnostics(target.getAsExpr(), dc, isExprStmt); return; } @@ -400,22 +395,15 @@ void constraints::performSyntacticDiagnosticsForTarget( // First emit diagnostics for the main expression. performSyntacticExprDiagnostics(stmt->getTypeCheckedSequence(), dc, - CTP_ForEachSequence, isExprStmt); + isExprStmt); if (auto *whereExpr = stmt->getWhere()) - performSyntacticExprDiagnostics(whereExpr, dc, CTP_Condition, - /*isExprStmt*/ false); + performSyntacticExprDiagnostics(whereExpr, dc, /*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*/ std::nullopt); - FunctionSyntacticDiagnosticWalker walker(dc); body->walk(walker); return; diff --git a/test/Constraints/closures.swift b/test/Constraints/closures.swift index 9c72e31c6f2..bfa15be3f72 100644 --- a/test/Constraints/closures.swift +++ b/test/Constraints/closures.swift @@ -1158,6 +1158,8 @@ func rdar77022842(argA: Bool? = nil, argB: Bool? = nil) { // expected-error@-1 {{initializer for conditional binding must have Optional type, not 'Bool'}} // expected-error@-2 {{closure passed to parameter of type 'Bool?' that does not accept a closure}} // expected-error@-3 {{cannot convert value of type 'Void' to expected condition type 'Bool'}} + // expected-error@-4 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + // expected-error@-5 {{'if' must have an unconditional 'else' to be used as expression}} } // expected-error {{expected '{' after 'if' condition}} } diff --git a/test/Constraints/if_expr.swift b/test/Constraints/if_expr.swift index 8f0b786a74c..9d89a476db0 100644 --- a/test/Constraints/if_expr.swift +++ b/test/Constraints/if_expr.swift @@ -384,8 +384,10 @@ func testVoidConversion() { 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?}} + return 1 + // expected-error@-1 {{cannot use 'return' to transfer control out of 'if' expression}} + // expected-error@-2 {{unexpected non-void return value in void function}} + // expected-note@-3 {{did you mean to add a return type?}} } else { 0 } @@ -433,7 +435,9 @@ func testAssignment() { } struct TestBadReturn { - var y = if .random() { return } else { 0 } // expected-error {{return invalid outside of a func}} + var y = if .random() { return } else { 0 } + // expected-error@-1 {{return invalid outside of a func}} + // expected-error@-2 {{cannot use 'return' to transfer control out of 'if' expression}} } struct SomeError: Error {} diff --git a/test/Constraints/rdar114402042.swift b/test/Constraints/rdar114402042.swift index b2521d0a0c2..c24ae88c5ae 100644 --- a/test/Constraints/rdar114402042.swift +++ b/test/Constraints/rdar114402042.swift @@ -23,10 +23,10 @@ func baz() -> String? { var x: Int? = { print( // expected-error {{cannot convert value of type '()' to closure result type 'Int?'}} // expected-note@-1 {{to match this opening '('}} - switch baz() { + switch baz() { // expected-error {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} case ""?: - return nil + return nil // expected-error {{cannot use 'return' to transfer control out of 'switch' expression}} default: - return nil + return nil // expected-error {{cannot use 'return' to transfer control out of 'switch' expression}} } }() // expected-error {{expected ')' in expression list}} diff --git a/test/Constraints/switch_expr.swift b/test/Constraints/switch_expr.swift index 22560db4557..aa7a38bb87b 100644 --- a/test/Constraints/switch_expr.swift +++ b/test/Constraints/switch_expr.swift @@ -191,7 +191,9 @@ func testAssignment() { } struct TestBadReturn { - var y = switch Bool.random() { case true: return case false: 0 } // expected-error {{return invalid outside of a func}} + var y = switch Bool.random() { case true: return case false: 0 } + // expected-error@-1 {{return invalid outside of a func}} + // expected-error@-2 {{cannot use 'return' to transfer control out of 'switch' expression}} } func testNil1(_ x: Bool) { diff --git a/test/Parse/recovery.swift b/test/Parse/recovery.swift index cf2668b9b39..c44dcc08ed6 100644 --- a/test/Parse/recovery.swift +++ b/test/Parse/recovery.swift @@ -82,6 +82,8 @@ func missingControllingExprInIf() { if { // expected-error {{missing condition in 'if' statement}} } // expected-error {{expected '{' after 'if' condition}} // expected-error@-2 {{cannot convert value of type 'Void' to expected condition type 'Bool'}} + // expected-error@-3 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + // expected-error@-4 {{'if' must have an unconditional 'else' to be used as expression}} if // expected-error {{missing condition in 'if' statement}} { @@ -239,6 +241,7 @@ func missingControllingExprInSwitch() { switch { // expected-error {{expected expression in 'switch' statement}} } // expected-error {{expected '{' after 'switch' subject expression}} + // expected-error@-2 {{'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 {{'switch' statement body must have at least one 'case' or 'default' block}} { diff --git a/test/expr/unary/if_expr.swift b/test/expr/unary/if_expr.swift index b28592211ee..9234d98fd9d 100644 --- a/test/expr/unary/if_expr.swift +++ b/test/expr/unary/if_expr.swift @@ -116,12 +116,14 @@ takesValue(if .random() { 0 } else { 1 }) do { takesValue(x: if .random() { 0 } else { 1 }) // expected-error@-1 {{extraneous argument label 'x:' in call}} + // expected-error@-2 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} takesValue(_: x: if .random() { 0 } else { 1 }) // expected-error@-1 {{expected argument label before colon}} // expected-error@-2 {{expected ',' separator}} // expected-error@-3 {{cannot find 'x' in scope}} // expected-error@-4 {{extra argument in call}} + // expected-error@-5 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} } func takesValueWithLabel(x: T) {} do { @@ -133,6 +135,7 @@ do { // expected-error@-2 {{expected ',' separator}} // expected-error@-3 {{cannot find 'y' in scope}} // expected-error@-4 {{extra argument in call}} + // expected-error@-5 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} } func takesValueAndTrailingClosure(_ x: T, _ fn: () -> Int) {} takesValueAndTrailingClosure(if .random() { 0 } else { 1 }) { 2 } @@ -141,6 +144,7 @@ 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,6 +466,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}} @@ -504,6 +511,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 {{type of expression is ambiguous without a type annotation}} + // expected-error@-2 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} } do { let _ = if .random() { Int?.none } else { 1 as Int? }! @@ -578,6 +586,8 @@ func stmts() { for _ in if .random() { [true] } else { [false] } {} // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + for _ in if .random() { [true] } else { [false] } {} // expected-error {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + // Make sure this doesn't parse as an if expr pattern with a label. let x = 0 switch 0 { @@ -609,9 +619,9 @@ func returnBranches() -> Int { func returnBranches1() -> Int { return if .random() { // expected-error {{cannot convert return expression of type 'Void' to return type 'Int'}} - return 0 + return 0 // expected-error {{cannot use 'return' to transfer control out of 'if' expression}} } else { - return 1 + return 1 // expected-error {{cannot use 'return' to transfer control out of 'if' expression}} } } diff --git a/test/expr/unary/switch_expr.swift b/test/expr/unary/switch_expr.swift index d407d315eb6..b3f8a3c036a 100644 --- a/test/expr/unary/switch_expr.swift +++ b/test/expr/unary/switch_expr.swift @@ -163,12 +163,14 @@ 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 argument label before colon}} // expected-error@-2 {{expected ',' separator}} // expected-error@-3 {{cannot find 'x' in scope}} // expected-error@-4 {{extra argument in call}} + // expected-error@-5 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} } func takesValueWithLabel(x: T) {} do { @@ -180,6 +182,7 @@ do { // expected-error@-2 {{expected ',' separator}} // expected-error@-3 {{cannot find 'y' in scope}} // expected-error@-4 {{extra argument in call}} + // expected-error@-5 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} } func takesValueAndTrailingClosure(_ x: T, _ fn: () -> Int) {} takesValueAndTrailingClosure(switch Bool.random() { case true: 0 case false: 1 }) { 2 } @@ -188,6 +191,7 @@ 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 { () } @@ -496,9 +500,11 @@ do { do { _ = (switch fatalError() {}, 1) // expected-error {{expected '{' after 'switch' subject expression}} // 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}} _ = (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}} @@ -507,6 +513,7 @@ do { // 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}} + // expected-error@-5 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} 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}} @@ -519,6 +526,7 @@ do { // 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}} + // expected-error@-5 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} 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}} @@ -618,6 +626,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}} @@ -660,6 +671,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 {{type of expression is ambiguous without a type annotation}} + // expected-error@-2 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} } do { let _ = switch Bool.random() { case true: Int?.none case false: 1 }! @@ -749,9 +761,9 @@ 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 + return 0 // expected-error {{cannot use 'return' to transfer control out of 'switch' expression}} case false: - return 1 + return 1 // expected-error {{cannot use 'return' to transfer control out of 'switch' expression}} } } diff --git a/test/stmt/then_stmt_disabled.swift b/test/stmt/then_stmt_disabled.swift index 803174d30ad..de3907f7f4a 100644 --- a/test/stmt/then_stmt_disabled.swift +++ b/test/stmt/then_stmt_disabled.swift @@ -6,6 +6,6 @@ let x = if .random() { then 0 // expected-error@-1 {{cannot find 'then' in scope}} // expected-error@-2 {{consecutive statements on a line must be separated by ';'}} -} else { +} else { // expected-error {{non-expression branch of 'if' expression may only end with a 'throw'}} 1 }