diff --git a/include/swift/AST/ASTScope.h b/include/swift/AST/ASTScope.h index 3719586a56cbb..4f1bf66fa4a20 100644 --- a/include/swift/AST/ASTScope.h +++ b/include/swift/AST/ASTScope.h @@ -955,6 +955,7 @@ class PatternEntryInitializerScope final : public AbstractPatternEntryScope { protected: bool lookupLocalsOrMembers(DeclConsumer) const override; + bool isLabeledStmtLookupTerminator() const override; }; /// The scope introduced by a conditional clause initializer in an diff --git a/include/swift/AST/ASTTypeIDZone.def b/include/swift/AST/ASTTypeIDZone.def index 428bf7c64182b..00ace188d39cd 100644 --- a/include/swift/AST/ASTTypeIDZone.def +++ b/include/swift/AST/ASTTypeIDZone.def @@ -26,6 +26,7 @@ SWIFT_TYPEID(Fingerprint) SWIFT_TYPEID(GenericSignature) SWIFT_TYPEID(ImplicitImportList) SWIFT_TYPEID(ImplicitMemberAction) +SWIFT_TYPEID(IsSingleValueStmtResult) SWIFT_TYPEID(ParamSpecifier) SWIFT_TYPEID(PropertyWrapperAuxiliaryVariables) SWIFT_TYPEID(PropertyWrapperInitializerInfo) diff --git a/include/swift/AST/ASTTypeIDs.h b/include/swift/AST/ASTTypeIDs.h index a93b0cea8547e..4629489ebc490 100644 --- a/include/swift/AST/ASTTypeIDs.h +++ b/include/swift/AST/ASTTypeIDs.h @@ -41,6 +41,7 @@ class GenericParamList; class GenericSignature; class GenericTypeParamType; class InfixOperatorDecl; +class IsSingleValueStmtResult; class IterableDeclContext; class ModuleDecl; struct ImplicitImportList; diff --git a/include/swift/AST/CASTBridging.h b/include/swift/AST/CASTBridging.h index e942a34701042..2f22328fa9d7a 100644 --- a/include/swift/AST/CASTBridging.h +++ b/include/swift/AST/CASTBridging.h @@ -169,6 +169,9 @@ void *SwiftVarDecl_create(void *ctx, BridgedIdentifier _Nullable name, void *initExpr, void *loc, _Bool isStatic, _Bool isLet, void *dc); +void *SingleValueStmtExpr_createWithWrappedBranches(void *ctx, void *S, + void *DC, _Bool mustBeExpr); + void *IfStmt_create(void *ctx, void *ifLoc, void *cond, void *_Nullable then, void *_Nullable elseLoc, void *_Nullable elseStmt); diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index be1fabb39e94b..3375cd5388427 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -1080,6 +1080,29 @@ ERROR(ternary_expr_cases_mismatch,none, "result values in '? :' expression have mismatching types %0 and %1", (Type, Type)) +// Statements as expressions +ERROR(single_value_stmt_branches_mismatch,none, + "branches have mismatching types %0 and %1", + (Type, Type)) +ERROR(single_value_stmt_out_of_place,none, + "'%0' may only be used as expression in return, throw, or as the source " + "of an assignment", + (StmtKind)) +ERROR(single_value_stmt_must_be_unlabeled,none, + "'%0' cannot have a jump label when used as expression", + (StmtKind)) +ERROR(if_expr_must_be_syntactically_exhaustive,none, + "'if' must have an unconditional 'else' to be used as expression", + ()) +ERROR(single_value_stmt_branch_must_end_in_throw,none, + "non-expression branch of '%0' expression may only end with a 'throw'", + (StmtKind)) +ERROR(cannot_jump_in_single_value_stmt,none, + "cannot '%0' in '%1' when used as expression", + (StmtKind, StmtKind)) +WARNING(redundant_effect_marker_on_single_value_stmt,none, + "'%0' on an '%1' expression has no effect", (StringRef, StmtKind)) + ERROR(did_not_call_function_value,none, "function value was used as a property; add () to call it", ()) diff --git a/include/swift/AST/Expr.h b/include/swift/AST/Expr.h index 50b1b9d8f7cf4..8b6944cb5623c 100644 --- a/include/swift/AST/Expr.h +++ b/include/swift/AST/Expr.h @@ -5971,6 +5971,58 @@ class KeyPathDotExpr : public Expr { } }; +/// An expression that may wrap a statement which produces a single value. +class SingleValueStmtExpr : public Expr { +public: + enum class Kind { + If, Switch + }; + +private: + Stmt *S; + DeclContext *DC; + + SingleValueStmtExpr(Stmt *S, DeclContext *DC) + : Expr(ExprKind::SingleValueStmt, /*isImplicit*/ true), S(S), DC(DC) {} + +public: + /// Creates a new SingleValueStmtExpr wrapping a statement. + static SingleValueStmtExpr *create(ASTContext &ctx, Stmt *S, DeclContext *DC); + + /// Creates a new SingleValueStmtExpr wrapping a statement, and recursively + /// attempts to wrap any branches of that statement that can become single + /// value statement expressions. + /// + /// If \p mustBeExpr is true, branches will be eagerly wrapped even if they + /// may not be valid SingleValueStmtExprs (which Sema will later diagnose). + static SingleValueStmtExpr *createWithWrappedBranches(ASTContext &ctx, + Stmt *S, + DeclContext *DC, + bool mustBeExpr); + /// Retrieve the wrapped statement. + Stmt *getStmt() const { return S; } + void setStmt(Stmt *newS) { S = newS; } + + /// Retrieve the kind of statement being wrapped. + Kind getStmtKind() const; + + /// Retrieve the complete set of branches for the underlying statement. + ArrayRef getBranches(SmallVectorImpl &scratch) const; + + /// Retrieve the single expression branches of the statement, excluding + /// branches that either have multiple expressions, or have statements. + ArrayRef + getSingleExprBranches(SmallVectorImpl &scratch) const; + + DeclContext *getDeclContext() const { return DC; } + + SourceRange getSourceRange() const; + + static bool classof(const Expr *E) { + return E->getKind() == ExprKind::SingleValueStmt; + } +}; + /// Expression node that effects a "one-way" constraint in /// the constraint system, allowing type information to flow from the /// subexpression outward but not the other way. diff --git a/include/swift/AST/ExprNodes.def b/include/swift/AST/ExprNodes.def index 034cb5e5baed6..f4bed94b78e38 100644 --- a/include/swift/AST/ExprNodes.def +++ b/include/swift/AST/ExprNodes.def @@ -205,6 +205,7 @@ EXPR(LazyInitializer, Expr) EXPR(EditorPlaceholder, Expr) EXPR(ObjCSelector, Expr) EXPR(KeyPath, Expr) +EXPR(SingleValueStmt, Expr) UNCHECKED_EXPR(KeyPathDot, Expr) UNCHECKED_EXPR(OneWay, Expr) EXPR(Tap, Expr) diff --git a/include/swift/AST/Stmt.h b/include/swift/AST/Stmt.h index 2bc3656093127..f0be7068e8bf7 100644 --- a/include/swift/AST/Stmt.h +++ b/include/swift/AST/Stmt.h @@ -35,6 +35,7 @@ class ASTContext; class ASTWalker; class Decl; class DeclContext; +class Evaluator; class Expr; class FuncDecl; class Pattern; @@ -42,6 +43,7 @@ class PatternBindingDecl; class VarDecl; class CaseStmt; class DoCatchStmt; +class IsSingleValueStmtResult; class SwitchStmt; enum class StmtKind { @@ -133,7 +135,12 @@ class alignas(8) Stmt : public ASTAllocated { SourceRange getSourceRange() const; SourceLoc TrailingSemiLoc; - + + /// Whether the statement can produce a single value, and as such may be + /// treated as an expression. + IsSingleValueStmtResult mayProduceSingleValue(Evaluator &eval) const; + IsSingleValueStmtResult mayProduceSingleValue(ASTContext &ctx) const; + /// isImplicit - Determines whether this statement was implicitly-generated, /// rather than explicitly written in the AST. bool isImplicit() const { return Bits.Stmt.Implicit; } @@ -204,6 +211,10 @@ class BraceStmt final : public Stmt, ASTNode findAsyncNode(); + /// If this brace is wrapping a single expression, returns it. Otherwise + /// returns \c nullptr. + Expr *getSingleExpressionElement() const; + static bool classof(const Stmt *S) { return S->getKind() == StmtKind::Brace; } }; @@ -711,7 +722,14 @@ class IfStmt : public LabeledConditionalStmt { Stmt *getElseStmt() const { return Else; } void setElseStmt(Stmt *s) { Else = s; } - + + /// Retrieve the complete set of branches for this if statement, including + /// else if statements. + ArrayRef getBranches(SmallVectorImpl &scratch) const; + + /// Whether the if statement has an unconditional \c else. + bool isSyntacticallyExhaustive() const; + // Implement isa/cast/dyncast/etc. static bool classof(const Stmt *S) { return S->getKind() == StmtKind::If; } }; @@ -1283,7 +1301,10 @@ class SwitchStmt final : public LabeledStmt, AsCaseStmtRange getCases() const { return AsCaseStmtRange(getRawCases(), AsCaseStmtWithSkippingNonCaseStmts()); } - + + /// Retrieve the complete set of branches for this switch statement. + ArrayRef getBranches(SmallVectorImpl &scratch) const; + static bool classof(const Stmt *S) { return S->getKind() == StmtKind::Switch; } diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index d92ffa5575f35..5e2311ac6c43e 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -55,6 +55,7 @@ class PropertyWrapperInitializerInfo; struct PropertyWrapperLValueness; struct PropertyWrapperMutability; class RequirementRepr; +class ReturnStmt; class SpecializeAttr; class TrailingWhereClause; class TypeAliasDecl; @@ -3749,6 +3750,146 @@ class ContinueTargetRequest bool isCached() const { return true; } }; +/// Precheck a ReturnStmt, which involves some initial validation, as well as +/// applying a conversion to a FailStmt if needed. +class PreCheckReturnStmtRequest + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + Stmt *evaluate(Evaluator &evaluator, ReturnStmt *RS, DeclContext *DC) const; + +public: + bool isCached() const { return true; } +}; + +/// The result of the query for whether a statement can produce a single value. +class IsSingleValueStmtResult { +public: + enum class Kind { + /// The statement may become a SingleValueStmtExpr. + Valid, + + /// There are non-single-expression branches that do not end in a throw. + UnterminatedBranches, + + /// The statement is an 'if' statement without an unconditional 'else'. + NonExhaustiveIf, + + /// There are no single-expression branches. + NoExpressionBranches, + + /// There is an unhandled statement branch. This should only be the case + /// for invalid AST. + UnhandledStmt, + + /// There was a circular reference when evaluating the request. This can be + /// ignored, as we will have already diagnosed it. + CircularReference, + + /// There is a 'break' or 'continue' within the statement that prevents it + /// from being treated as an expression. + InvalidJumps, + + /// The statement has a jump label, which is invalid for an expression. + HasLabel + }; + +private: + Kind TheKind; + TinyPtrVector InvalidJumps; + TinyPtrVector UnterminatedBranches; + + IsSingleValueStmtResult(Kind kind) : TheKind(kind) { + assert(kind != Kind::UnterminatedBranches && kind != Kind::InvalidJumps); + } + + IsSingleValueStmtResult(Kind kind, TinyPtrVector stmts) + : TheKind(kind) { + switch (kind) { + case Kind::UnterminatedBranches: { + UnterminatedBranches = std::move(stmts); + break; + } + case Kind::InvalidJumps: { + InvalidJumps = std::move(stmts); + break; + } + default: + llvm_unreachable("Unhandled case in switch!"); + } + } + +public: + static IsSingleValueStmtResult valid() { + return IsSingleValueStmtResult(Kind::Valid); + } + static IsSingleValueStmtResult + unterminatedBranches(TinyPtrVector branches) { + return IsSingleValueStmtResult(Kind::UnterminatedBranches, + std::move(branches)); + } + static IsSingleValueStmtResult nonExhaustiveIf() { + return IsSingleValueStmtResult(Kind::NonExhaustiveIf); + } + static IsSingleValueStmtResult noExpressionBranches() { + return IsSingleValueStmtResult(Kind::NoExpressionBranches); + } + static IsSingleValueStmtResult unhandledStmt() { + return IsSingleValueStmtResult(Kind::UnhandledStmt); + } + static IsSingleValueStmtResult circularReference() { + return IsSingleValueStmtResult(Kind::CircularReference); + } + static IsSingleValueStmtResult invalidJumps(TinyPtrVector jumps) { + return IsSingleValueStmtResult(Kind::InvalidJumps, std::move(jumps)); + } + static IsSingleValueStmtResult hasLabel() { + return IsSingleValueStmtResult(Kind::HasLabel); + } + + Kind getKind() const { return TheKind; } + + /// For an unterminated branch kind, retrieves the branch. + const TinyPtrVector &getUnterminatedBranches() const { + assert(TheKind == Kind::UnterminatedBranches); + return UnterminatedBranches; + } + + /// For an invalid jump kind, retrieves the list of invalid jumps. + const TinyPtrVector &getInvalidJumps() const { + assert(TheKind == Kind::InvalidJumps); + return InvalidJumps; + } + + explicit operator bool() const { + return TheKind == Kind::Valid; + } +}; + +/// Computes whether a given statement can be treated as a SingleValueStmtExpr. +class IsSingleValueStmtRequest + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + IsSingleValueStmtResult + evaluate(Evaluator &evaluator, const Stmt *stmt) const; + +public: + bool isCached() const { return true; } +}; + class GetTypeWrapperInitializer : public SimpleRequest() != nullptr; } diff --git a/include/swift/Sema/ConstraintLocator.h b/include/swift/Sema/ConstraintLocator.h index 4de0c8e09318f..630aa075d90a4 100644 --- a/include/swift/Sema/ConstraintLocator.h +++ b/include/swift/Sema/ConstraintLocator.h @@ -85,6 +85,15 @@ enum ContextualTypePurpose : uint8_t { CTP_ComposedPropertyWrapper, ///< Composed wrapper type expected to match ///< former 'wrappedValue' type + CTP_SingleValueStmtBranch, ///< The contextual type for a branch in a single + ///< value statement expression. + + CTP_SingleValueStmtBranchInSingleExprClosure, ///< The contextual type for a + ///< branch in a single value + ///< statement expression that's + ///< part of a single expression + ///< return in a closure. + CTP_ExprPattern, ///< `~=` operator application associated with expression /// pattern. @@ -295,6 +304,14 @@ class ConstraintLocator : public llvm::FoldingSetNode { /// Determine whether this locator is for a macro expansion. bool isForMacroExpansion() const; + /// Whether this locator identifies a conjunction for the branches of a + /// SingleValueStmtExpr. + bool isForSingleValueStmtConjunction() const; + + /// Whether this locator identifies a conversion for a SingleValueStmtExpr + /// branch in an implicit single-expression return for a closure. + bool isForSingleValueStmtBranchInSingleExprClosure() const; + /// Determine whether this locator points directly to a given expression. template bool directlyAt() const { if (auto *expr = getAnchor().dyn_cast()) @@ -886,6 +903,22 @@ class LocatorPathElt::TernaryBranch final : public StoredIntegerElement<1> { } }; +/// The branch of a SingleValueStmtExpr. Note the stored index corresponds to +/// the expression branches, i.e it skips statement branch indices. +class LocatorPathElt::SingleValueStmtBranch final + : public StoredIntegerElement<1> { +public: + SingleValueStmtBranch(unsigned exprIdx) + : StoredIntegerElement(ConstraintLocator::SingleValueStmtBranch, + exprIdx) {} + + unsigned getExprBranchIndex() const { return getValue(); } + + static bool classof(const LocatorPathElt *elt) { + return elt->getKind() == ConstraintLocator::SingleValueStmtBranch; + } +}; + class LocatorPathElt::PatternMatch final : public StoredPointerElement { public: PatternMatch(Pattern *pattern) diff --git a/include/swift/Sema/ConstraintLocatorPathElts.def b/include/swift/Sema/ConstraintLocatorPathElts.def index 86df71de2c4d0..b69d97010eb7b 100644 --- a/include/swift/Sema/ConstraintLocatorPathElts.def +++ b/include/swift/Sema/ConstraintLocatorPathElts.def @@ -248,6 +248,9 @@ CUSTOM_LOCATOR_PATH_ELT(SyntacticElement) /// The element of the pattern binding declaration. CUSTOM_LOCATOR_PATH_ELT(PatternBindingElement) +/// A branch of a SingleValueStmtExpr. +CUSTOM_LOCATOR_PATH_ELT(SingleValueStmtBranch) + /// A declaration introduced by a pattern: name (i.e. `x`) or `_` ABSTRACT_LOCATOR_PATH_ELT(PatternDecl) /// The variable declared by a named pattern. diff --git a/include/swift/Sema/ConstraintSystem.h b/include/swift/Sema/ConstraintSystem.h index 31527604a989e..6ecbd1dae961c 100644 --- a/include/swift/Sema/ConstraintSystem.h +++ b/include/swift/Sema/ConstraintSystem.h @@ -5080,6 +5080,11 @@ class ConstraintSystem { LLVM_NODISCARD bool generateConstraints(AnyFunctionRef fn, BraceStmt *body); + /// Generate constraints for a given SingleValueStmtExpr. + /// + /// \returns \c true if constraint generation failed, \c false otherwise + bool generateConstraints(SingleValueStmtExpr *E); + /// Generate constraints for the given (unchecked) expression. /// /// \returns a possibly-sanitized expression, or null if an error occurred. @@ -5978,6 +5983,22 @@ class ConstraintSystem { SolutionApplicationTarget)> rewriteTarget); + /// Apply the given solution to the given SingleValueStmtExpr. + /// + /// \param solution The solution to apply. + /// \param SVE The SingleValueStmtExpr to rewrite. + /// \param DC The declaration context in which transformations will be + /// applied. + /// \param rewriteTarget Function that performs a rewrite of any + /// solution application target within the context. + /// + /// \returns true if solution cannot be applied. + bool applySolutionToSingleValueStmt( + Solution &solution, SingleValueStmtExpr *SVE, DeclContext *DC, + std::function< + Optional(SolutionApplicationTarget)> + rewriteTarget); + /// Reorder the disjunctive clauses for a given expression to /// increase the likelihood that a favored constraint will be successfully /// resolved before any others. diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 3338961f994d5..a8041d78af285 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -2978,6 +2978,13 @@ class PrintExpr : public ExprVisitor { PrintWithColorRAII(OS, ParenthesisColor) << ')'; } + void visitSingleValueStmtExpr(SingleValueStmtExpr *E) { + printCommon(E, "single_value_stmt_expr"); + OS << '\n'; + printRec(E->getStmt(), E->getDeclContext()->getASTContext()); + PrintWithColorRAII(OS, ParenthesisColor) << ')'; + } + void visitOneWayExpr(OneWayExpr *E) { printCommon(E, "one_way_expr"); OS << '\n'; diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index dbd46387e3222..54dd741953476 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -2978,6 +2978,10 @@ static bool usesFeatureParserDiagnostics(Decl *decl) { return false; } +static bool usesFeatureStatementExpressions(Decl *decl) { + return false; +} + static void suppressingFeatureSpecializeAttributeWithAvailability( PrintOptions &options, llvm::function_ref action) { @@ -4788,6 +4792,10 @@ void PrintAST::visitErasureExpr(ErasureExpr *expr) { void PrintAST::visitKeyPathExpr(KeyPathExpr *expr) { } +void PrintAST::visitSingleValueStmtExpr(SingleValueStmtExpr *expr) { + visit(expr->getStmt()); +} + void PrintAST::visitForceTryExpr(ForceTryExpr *expr) { Printer << "try! "; visit(expr->getSubExpr()); diff --git a/lib/AST/ASTScopeCreation.cpp b/lib/AST/ASTScopeCreation.cpp index e5f99bba8b2f3..ea4f883c2ef2f 100644 --- a/lib/AST/ASTScopeCreation.cpp +++ b/lib/AST/ASTScopeCreation.cpp @@ -470,9 +470,15 @@ class NodeAdder ASTScopeImpl *visitExpr(Expr *expr, ASTScopeImpl *p, ScopeCreator &scopeCreator) { - if (expr) - scopeCreator.addExprToScopeTree(expr, p); + if (!expr) + return p; + + // If we have a single value statement expression, we expand scopes based + // on the underlying statement. + if (auto *SVE = dyn_cast(expr)) + return visit(SVE->getStmt(), p, scopeCreator); + scopeCreator.addExprToScopeTree(expr, p); return p; } }; diff --git a/lib/AST/ASTScopeLookup.cpp b/lib/AST/ASTScopeLookup.cpp index 79470fcfe37ad..a2367cbc1ffa8 100644 --- a/lib/AST/ASTScopeLookup.cpp +++ b/lib/AST/ASTScopeLookup.cpp @@ -535,6 +535,12 @@ bool PatternEntryDeclScope::isLabeledStmtLookupTerminator() const { return false; } +bool PatternEntryInitializerScope::isLabeledStmtLookupTerminator() const { + // This is needed for SingleValueStmtExprs, which may be used in bindings, + // and have nested statements. + return false; +} + llvm::SmallVector ASTScopeImpl::lookupLabeledStmts(SourceFile *sourceFile, SourceLoc loc) { // Find the innermost scope from which to start our search. diff --git a/lib/AST/ASTVerifier.cpp b/lib/AST/ASTVerifier.cpp index 786dd7483bf81..668832bc0e1d7 100644 --- a/lib/AST/ASTVerifier.cpp +++ b/lib/AST/ASTVerifier.cpp @@ -33,6 +33,7 @@ #include "swift/AST/ProtocolConformance.h" #include "swift/AST/SourceFile.h" #include "swift/AST/Stmt.h" +#include "swift/AST/TypeCheckRequests.h" #include "swift/AST/TypeRepr.h" #include "swift/Basic/SourceManager.h" #include "swift/Subsystems.h" @@ -1181,6 +1182,31 @@ class Verifier : public ASTWalker { verifyCheckedBase(E); } + void verifyChecked(SingleValueStmtExpr *E) { + using Kind = IsSingleValueStmtResult::Kind; + switch (E->getStmt()->mayProduceSingleValue(Ctx).getKind()) { + case Kind::NoExpressionBranches: + // These are allowed as long as the type is Void. + checkSameType( + E->getType(), Ctx.getVoidType(), + "SingleValueStmtExpr with no expression branches must be Void"); + break; + case Kind::UnterminatedBranches: + case Kind::NonExhaustiveIf: + case Kind::UnhandledStmt: + case Kind::CircularReference: + case Kind::HasLabel: + case Kind::InvalidJumps: + // These should have been diagnosed. + Out << "invalid SingleValueStmtExpr should be been diagnosed:\n"; + E->dump(Out); + Out << "\n"; + abort(); + case Kind::Valid: + break; + } + } + void verifyParsed(AbstractClosureExpr *E) { Type Ty = E->getType(); if (!Ty) diff --git a/lib/AST/ASTWalker.cpp b/lib/AST/ASTWalker.cpp index a712fc611200d..3f0172f3f46ae 100644 --- a/lib/AST/ASTWalker.cpp +++ b/lib/AST/ASTWalker.cpp @@ -1220,6 +1220,15 @@ class Traversal : public ASTVisitorgetStmt())) { + E->setStmt(S); + } else { + return nullptr; + } + return E; + } + Expr *visitOneWayExpr(OneWayExpr *E) { if (auto oldSubExpr = E->getSubExpr()) { if (auto subExpr = doIt(oldSubExpr)) { diff --git a/lib/AST/CASTBridging.cpp b/lib/AST/CASTBridging.cpp index ec4657c4ca17b..15f57c8431745 100644 --- a/lib/AST/CASTBridging.cpp +++ b/lib/AST/CASTBridging.cpp @@ -261,6 +261,13 @@ void *SwiftVarDecl_create(void *ctx, BridgedIdentifier _Nullable nameId, reinterpret_cast(dc)); } +void *SingleValueStmtExpr_createWithWrappedBranches(void *_ctx, void *S, + void *DC, bool mustBeExpr) { + auto &ctx = *static_cast(_ctx); + return SingleValueStmtExpr::createWithWrappedBranches( + ctx, (Stmt *)S, (DeclContext *)DC, mustBeExpr); +} + void *IfStmt_create(void *ctx, void *ifLoc, void *cond, void *_Nullable then, void *_Nullable elseLoc, void *_Nullable elseStmt) { ASTContext &Context = *static_cast(ctx); diff --git a/lib/AST/Expr.cpp b/lib/AST/Expr.cpp index 6ee5e97b7e54c..fb14bf34d0e9f 100644 --- a/lib/AST/Expr.cpp +++ b/lib/AST/Expr.cpp @@ -335,6 +335,7 @@ ConcreteDeclRef Expr::getReferencedDecl(bool stopAtParenExpr) const { NO_REFERENCE(MagicIdentifierLiteral); NO_REFERENCE(DiscardAssignment); NO_REFERENCE(LazyInitializer); + NO_REFERENCE(SingleValueStmt); SIMPLE_REFERENCE(DeclRef, getDeclRef); SIMPLE_REFERENCE(SuperRef, getSelf); @@ -668,6 +669,7 @@ bool Expr::canAppendPostfixExpression(bool appendingPostfixOperator) const { case ExprKind::MagicIdentifierLiteral: case ExprKind::ObjCSelector: case ExprKind::KeyPath: + case ExprKind::SingleValueStmt: return true; case ExprKind::ObjectLiteral: @@ -995,6 +997,7 @@ bool Expr::isValidParentOfTypeExpr(Expr *typeExpr) const { case ExprKind::KeyPathDot: case ExprKind::OneWay: case ExprKind::Tap: + case ExprKind::SingleValueStmt: case ExprKind::TypeJoin: case ExprKind::MacroExpansion: return false; @@ -2471,6 +2474,91 @@ KeyPathExpr::Component::Component( ? nullptr : indexHashables.data(); } +SingleValueStmtExpr *SingleValueStmtExpr::create(ASTContext &ctx, Stmt *S, + DeclContext *DC) { + return new (ctx) SingleValueStmtExpr(S, DC); +} + +SingleValueStmtExpr *SingleValueStmtExpr::createWithWrappedBranches( + ASTContext &ctx, Stmt *S, DeclContext *DC, bool mustBeExpr) { + auto *SVE = create(ctx, S, DC); + + // Attempt to wrap any branches that can be wrapped. + SmallVector scratch; + for (auto *branch : SVE->getBranches(scratch)) { + auto *BS = dyn_cast(branch); + if (!BS) + continue; + + auto elts = BS->getElements(); + if (elts.size() != 1) + continue; + + auto *S = elts.front().dyn_cast(); + if (!S) + continue; + + if (mustBeExpr) { + // If this must be an expression, we can eagerly wrap any exhaustive if + // and switch branch. + if (auto *IS = dyn_cast(S)) { + if (!IS->isSyntacticallyExhaustive()) + continue; + } else if (!isa(S)) { + continue; + } + } else { + // Otherwise do the semantic checking to verify that we can wrap the + // branch. + if (!S->mayProduceSingleValue(ctx)) + continue; + } + BS->setLastElement( + SingleValueStmtExpr::createWithWrappedBranches(ctx, S, DC, mustBeExpr)); + } + return SVE; +} + +SourceRange SingleValueStmtExpr::getSourceRange() const { + return S->getSourceRange(); +} + +SingleValueStmtExpr::Kind SingleValueStmtExpr::getStmtKind() const { + switch (getStmt()->getKind()) { + case StmtKind::If: + return Kind::If; + case StmtKind::Switch: + return Kind::Switch; + default: + llvm_unreachable("Unhandled kind!"); + } +} + +ArrayRef +SingleValueStmtExpr::getBranches(SmallVectorImpl &scratch) const { + switch (getStmtKind()) { + case Kind::If: + return cast(getStmt())->getBranches(scratch); + case Kind::Switch: + return cast(getStmt())->getBranches(scratch); + } + llvm_unreachable("Unhandled case in switch!"); +} + +ArrayRef SingleValueStmtExpr::getSingleExprBranches( + SmallVectorImpl &scratch) const { + assert(scratch.empty()); + SmallVector stmtScratch; + for (auto *branch : getBranches(stmtScratch)) { + auto *BS = dyn_cast(branch); + if (!BS) + continue; + if (auto *E = BS->getSingleExpressionElement()) + scratch.push_back(E); + } + return scratch; +} + void InterpolatedStringLiteralExpr::forEachSegment(ASTContext &Ctx, llvm::function_ref callback) { auto appendingExpr = getAppendingExpr(); diff --git a/lib/AST/NameLookup.cpp b/lib/AST/NameLookup.cpp index e591a183b1976..016640125555c 100644 --- a/lib/AST/NameLookup.cpp +++ b/lib/AST/NameLookup.cpp @@ -3485,6 +3485,12 @@ void FindLocalVal::visitBraceStmt(BraceStmt *S, bool isTopLevelCode) { } for (auto elem : S->getElements()) { + // If we have a SingleValueStmtExpr, there may be local bindings in the + // wrapped statement. + if (auto *E = elem.dyn_cast()) { + if (auto *SVE = dyn_cast(E)) + visit(SVE->getStmt()); + } if (auto *S = elem.dyn_cast()) visit(S); } diff --git a/lib/AST/Stmt.cpp b/lib/AST/Stmt.cpp index 43100e1b4e919..1baccd0bcd143 100644 --- a/lib/AST/Stmt.cpp +++ b/lib/AST/Stmt.cpp @@ -292,6 +292,22 @@ ASTNode BraceStmt::findAsyncNode() { return asyncFinder.getAsyncNode(); } +Expr *BraceStmt::getSingleExpressionElement() const { + if (getElements().size() != 1) + return nullptr; + + return getElements()[0].dyn_cast(); +} + +IsSingleValueStmtResult Stmt::mayProduceSingleValue(Evaluator &eval) const { + return evaluateOrDefault(eval, IsSingleValueStmtRequest{this}, + IsSingleValueStmtResult::circularReference()); +} + +IsSingleValueStmtResult Stmt::mayProduceSingleValue(ASTContext &ctx) const { + return mayProduceSingleValue(ctx.evaluator); +} + SourceLoc ReturnStmt::getStartLoc() const { if (ReturnLoc.isInvalid() && Result) return Result->getStartLoc(); @@ -515,6 +531,39 @@ IfStmt::IfStmt(SourceLoc IfLoc, Expr *Cond, Stmt *Then, SourceLoc ElseLoc, implicit) { } +ArrayRef IfStmt::getBranches(SmallVectorImpl &scratch) const { + assert(scratch.empty()); + scratch.push_back(getThenStmt()); + + auto *elseBranch = getElseStmt(); + while (elseBranch) { + if (auto *IS = dyn_cast(elseBranch)) { + // Look through else ifs. + elseBranch = IS->getElseStmt(); + scratch.push_back(IS->getThenStmt()); + continue; + } + // An unconditional else, we're done. + scratch.push_back(elseBranch); + break; + } + return scratch; +} + +bool IfStmt::isSyntacticallyExhaustive() const { + auto *elseBranch = getElseStmt(); + while (elseBranch) { + // Look through else ifs. + if (auto *IS = dyn_cast(elseBranch)) { + elseBranch = IS->getElseStmt(); + continue; + } + // An unconditional else. + return true; + } + return false; +} + GuardStmt::GuardStmt(SourceLoc GuardLoc, Expr *Cond, BraceStmt *Body, Optional implicit, ASTContext &Ctx) : GuardStmt(GuardLoc, exprToCond(Cond, Ctx), Body, implicit) { @@ -688,6 +737,14 @@ SourceLoc swift::extractNearestSourceLoc(const Stmt *S) { return S->getStartLoc(); } +ArrayRef +SwitchStmt::getBranches(SmallVectorImpl &scratch) const { + assert(scratch.empty()); + for (auto *CS : getCases()) + scratch.push_back(CS->getBody()); + return scratch; +} + // See swift/Basic/Statistic.h for declaration: this enables tracing Stmts, is // defined here to avoid too much layering violation / circular linkage // dependency. diff --git a/lib/ASTGen/Sources/ASTGen/Exprs.swift b/lib/ASTGen/Sources/ASTGen/Exprs.swift index 26175ce2d74f5..121ffa0abc80f 100644 --- a/lib/ASTGen/Sources/ASTGen/Exprs.swift +++ b/lib/ASTGen/Sources/ASTGen/Exprs.swift @@ -62,6 +62,15 @@ extension ASTGenVisitor { return .expr(UnresolvedDotExpr_create(ctx, base, loc, name, loc)) } + public func visit(_ node: IfExprSyntax) -> ASTNode { + let stmt = makeIfStmt(node).rawValue + + // Wrap in a SingleValueStmtExpr to embed as an expression. + let sve = SingleValueStmtExpr_createWithWrappedBranches( + ctx, stmt, declContext, /*mustBeExpr*/ true) + return .expr(sve) + } + public func visit(_ node: TupleExprElementListSyntax) -> ASTNode { let elements = node.map { self.visit($0).rawValue } let labels: [BridgedIdentifier?] = node.map { diff --git a/lib/ASTGen/Sources/ASTGen/Stmts.swift b/lib/ASTGen/Sources/ASTGen/Stmts.swift index e9f0c214c17d0..9b8b625974520 100644 --- a/lib/ASTGen/Sources/ASTGen/Stmts.swift +++ b/lib/ASTGen/Sources/ASTGen/Stmts.swift @@ -14,7 +14,7 @@ extension ASTGenVisitor { }) } - public func visit(_ node: IfStmtSyntax) -> ASTNode { + func makeIfStmt(_ node: IfExprSyntax) -> ASTNode { let conditions = node.conditions.map { self.visit($0).rawValue } assert(conditions.count == 1) // TODO: handle multiple conditions. @@ -22,12 +22,22 @@ extension ASTGenVisitor { let loc = self.base.advanced(by: node.position.utf8Offset).raw if let elseBody = node.elseBody, node.elseKeyword != nil { - return .stmt(IfStmt_create(ctx, loc, conditions.first!, body, loc, visit(elseBody).rawValue)) + return .stmt(IfStmt_create(ctx, loc, conditions.first!, body, loc, + visit(elseBody).rawValue)) } return .stmt(IfStmt_create(ctx, loc, conditions.first!, body, nil, nil)) } + public func visit(_ node: ExpressionStmtSyntax) -> ASTNode { + switch Syntax(node.expression).as(SyntaxEnum.self) { + case .ifExpr(let e): + return makeIfStmt(e) + default: + fatalError("Unhandled case!") + } + } + public func visit(_ node: ReturnStmtSyntax) -> ASTNode { let loc = self.base.advanced(by: node.position.utf8Offset).raw diff --git a/lib/Basic/LangOptions.cpp b/lib/Basic/LangOptions.cpp index 048f014fb7a35..1c5c83108473b 100644 --- a/lib/Basic/LangOptions.cpp +++ b/lib/Basic/LangOptions.cpp @@ -235,6 +235,9 @@ bool LangOptions::isCustomConditionalCompilationFlagSet(StringRef Name) const { } bool LangOptions::hasFeature(Feature feature) const { + if (feature == Feature::StatementExpressions) + return true; + if (Features.contains(feature)) return true; diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index 2566464281f0c..640bd4bff9c6c 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -525,6 +525,21 @@ ParserResult Parser::parseExprUnary(Diag<> Message, bool isExprBasic) { return makeParserResult(expansion); } + // Try parse an 'if' or 'switch' as an expression. Note we do this here in + // parseExprUnary as we don't allow postfix syntax to hang off such + // expressions to avoid ambiguities such as postfix '.member', which can + // currently be parsed as a static dot member for a result builder. + if (Context.LangOpts.hasFeature(Feature::StatementExpressions) && + Tok.isAny(tok::kw_if, tok::kw_switch)) { + auto Result = parseStmt(); + Expr *E = nullptr; + if (Result.isNonNull()) { + E = SingleValueStmtExpr::createWithWrappedBranches( + Context, Result.get(), CurDeclContext, /*mustBeExpr*/ true); + } + return makeParserResult(ParserStatus(Result), E); + } + switch (Tok.getKind()) { default: // If the next token is not an operator, just parse this as expr-postfix. @@ -1055,6 +1070,10 @@ static bool isValidTrailingClosure(bool isExprBasic, Parser &P){ if (P.isStartOfGetSetAccessor()) return false; + // If this is the start of a switch body, this isn't a trailing closure. + if (P.peekToken().is(tok::kw_case)) + return false; + // If this is a normal expression (not an expr-basic) then trailing closures // are allowed, so this is obviously one. // TODO: We could handle try to disambiguate cases like: diff --git a/lib/Parse/ParseStmt.cpp b/lib/Parse/ParseStmt.cpp index e9cdcf603ae07..18d80bee4bd8c 100644 --- a/lib/Parse/ParseStmt.cpp +++ b/lib/Parse/ParseStmt.cpp @@ -76,6 +76,11 @@ bool Parser::isStartOfStmt() { case tok::kw_try: { // "try" cannot actually start any statements, but we parse it there for // better recovery in cases like 'try return'. + + // For 'if' and 'switch' we can parse as an expression. + if (peekToken().isAny(tok::kw_if, tok::kw_switch)) + return false; + Parser::BacktrackingScope backtrack(*this); consumeToken(tok::kw_try); return isStartOfStmt(); @@ -141,8 +146,27 @@ ParserStatus Parser::parseExprOrStmt(ASTNode &Result) { if (isStartOfStmt()) { ParserResult Res = parseStmt(); - if (Res.isNonNull()) - Result = Res.get(); + if (Res.isNonNull()) { + auto *S = Res.get(); + + // Special case: An 'if' or 'switch' statement followed by an 'as' must + // be an if/switch expression in a coercion. + // We could also achieve this by more eagerly attempting to parse an 'if' + // or 'switch' as an expression when in statement position, but that + // could result in less useful recovery behavior. + if (Context.LangOpts.hasFeature(Feature::StatementExpressions) && + (isa(S) || isa(S)) && Tok.is(tok::kw_as)) { + auto *SVE = SingleValueStmtExpr::createWithWrappedBranches( + Context, S, CurDeclContext, /*mustBeExpr*/ true); + auto As = parseExprAs(); + if (As.isParseErrorOrHasCompletion()) + return As; + + Result = SequenceExpr::create(Context, {SVE, As.get(), As.get()}); + } else { + Result = S; + } + } return Res; } @@ -730,13 +754,26 @@ ParserResult Parser::parseStmtReturn(SourceLoc tryLoc) { return Result; } + auto isStartOfReturnExpr = [&]() { + if (Tok.isAny(tok::r_brace, tok::semi, tok::eof, tok::pound_if, + tok::pound_error, tok::pound_warning, tok::pound_endif, + tok::pound_else, tok::pound_elseif)) { + return false; + } + // Allowed for if/switch expressions. + if (Tok.isAny(tok::kw_if, tok::kw_switch)) + return true; + + if (isStartOfStmt() || isStartOfSwiftDecl()) + return false; + + return true; + }; + // Handle the ambiguity between consuming the expression and allowing the // enclosing stmt-brace to get it by eagerly eating it unless the return is // followed by a '}', ';', statement or decl start keyword sequence. - if (Tok.isNot(tok::r_brace, tok::semi, tok::eof, tok::pound_if, - tok::pound_error, tok::pound_warning, tok::pound_endif, - tok::pound_else, tok::pound_elseif) && - !isStartOfStmt() && !isStartOfSwiftDecl()) { + if (isStartOfReturnExpr()) { SourceLoc ExprLoc = Tok.getLoc(); // Issue a warning when the returned expression is on a different line than diff --git a/lib/Refactoring/Refactoring.cpp b/lib/Refactoring/Refactoring.cpp index bcf492fef2433..6b87411af0b87 100644 --- a/lib/Refactoring/Refactoring.cpp +++ b/lib/Refactoring/Refactoring.cpp @@ -5349,32 +5349,61 @@ struct CallbackClassifier { : Blocks(Blocks), Params(Params), HandledSwitches(HandledSwitches), DiagEngine(DiagEngine), CurrentBlock(&Blocks.SuccessBlock) {} - void classifyNodes(ArrayRef Nodes, SourceLoc endCommentLoc) { - for (auto I = Nodes.begin(), E = Nodes.end(); I < E; ++I) { - auto *Statement = I->dyn_cast(); - if (auto *IS = dyn_cast_or_null(Statement)) { - NodesToPrint TempNodes; - if (auto *BS = dyn_cast(IS->getThenStmt())) { - TempNodes = NodesToPrint::inBraceStmt(BS); - } else { - TempNodes = NodesToPrint({IS->getThenStmt()}, /*commentLocs*/ {}); - } + /// Attempt to apply custom classification logic to a given node, returning + /// \c true if the node was classified, otherwise \c false. + bool tryClassifyNode(ASTNode Node) { + auto *Statement = Node.dyn_cast(); + if (!Statement) + return false; - classifyConditional(IS, IS->getCond(), std::move(TempNodes), - IS->getElseStmt()); - } else if (auto *GS = dyn_cast_or_null(Statement)) { - classifyConditional(GS, GS->getCond(), NodesToPrint(), GS->getBody()); - } else if (auto *SS = dyn_cast_or_null(Statement)) { - classifySwitch(SS); + if (auto *IS = dyn_cast(Statement)) { + NodesToPrint TempNodes; + if (auto *BS = dyn_cast(IS->getThenStmt())) { + TempNodes = NodesToPrint::inBraceStmt(BS); } else { - CurrentBlock->addNode(*I); + TempNodes = NodesToPrint({IS->getThenStmt()}, /*commentLocs*/ {}); + } + + classifyConditional(IS, IS->getCond(), std::move(TempNodes), + IS->getElseStmt()); + return true; + } else if (auto *GS = dyn_cast(Statement)) { + classifyConditional(GS, GS->getCond(), NodesToPrint(), GS->getBody()); + return true; + } else if (auto *SS = dyn_cast(Statement)) { + classifySwitch(SS); + return true; + } else if (auto *RS = dyn_cast(Statement)) { + // We can look through an implicit Void return of a SingleValueStmtExpr, + // as that's semantically a statement. + if (RS->hasResult() && RS->isImplicit()) { + auto Ty = RS->getResult()->getType(); + if (Ty && Ty->isVoid()) { + if (auto *SVE = dyn_cast(RS->getResult())) + return tryClassifyNode(SVE->getStmt()); + } } + } + return false; + } - if (DiagEngine.hadAnyError()) + /// Classify a node, or add the node to the block if it cannot be classified. + /// Returns \c true if there was an error. + bool classifyNode(ASTNode Node) { + auto DidClassify = tryClassifyNode(Node); + if (!DidClassify) + CurrentBlock->addNode(Node); + return DiagEngine.hadAnyError(); + } + + void classifyNodes(ArrayRef Nodes, SourceLoc EndCommentLoc) { + for (auto Node : Nodes) { + auto HadError = classifyNode(Node); + if (HadError) return; } // Make sure to pick up any trailing comments. - CurrentBlock->addPossibleCommentLoc(endCommentLoc); + CurrentBlock->addPossibleCommentLoc(EndCommentLoc); } /// Whether any of the provided ASTNodes have a child expression that force @@ -6749,6 +6778,15 @@ class AsyncConverter : private SourceEntityWalker { } } + // A void SingleValueStmtExpr is semantically more like a statement than + // an expression, so recurse without bumping the expr depth or wrapping in + // continuation. + if (auto *SVE = dyn_cast(E)) { + auto ty = SVE->getType(); + if (!ty || ty->isVoid()) + return true; + } + // We didn't do any special conversion for this expression. If needed, wrap // it in a continuation. wrapScopeInContinationIfNecessary(E); @@ -6766,6 +6804,11 @@ class AsyncConverter : private SourceEntityWalker { } bool walkToExprPost(Expr *E) override { + if (auto *SVE = dyn_cast(E)) { + auto ty = SVE->getType(); + if (!ty || ty->isVoid()) + return true; + } NestedExprCount--; return true; } diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 5d332c35c1942..ec44153f04fea 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -39,6 +39,7 @@ #include "swift/AST/ProtocolConformance.h" #include "swift/AST/SubstitutionMap.h" #include "swift/AST/Types.h" +#include "swift/Basic/Defer.h" #include "swift/Basic/SourceManager.h" #include "swift/Basic/type_traits.h" #include "swift/SIL/Consumption.h" @@ -509,6 +510,8 @@ namespace { RValue visitAssignExpr(AssignExpr *E, SGFContext C); RValue visitEnumIsCaseExpr(EnumIsCaseExpr *E, SGFContext C); + RValue visitSingleValueStmtExpr(SingleValueStmtExpr *E, SGFContext C); + RValue visitBindOptionalExpr(BindOptionalExpr *E, SGFContext C); RValue visitOptionalEvaluationExpr(OptionalEvaluationExpr *E, SGFContext C); @@ -2122,6 +2125,39 @@ RValue RValueEmitter::visitEnumIsCaseExpr(EnumIsCaseExpr *E, return emitBoolLiteral(SGF, E, selected, C); } +RValue RValueEmitter::visitSingleValueStmtExpr(SingleValueStmtExpr *E, + SGFContext C) { + // A void SingleValueStmtExpr either only has Void expression branches, or + // we've decided that it should have purely statement semantics. In either + // case, we can just emit the statement as-is, and produce the void rvalue. + if (E->getType()->isVoid()) { + SGF.emitStmt(E->getStmt()); + return SGF.emitEmptyTupleRValue(E, C); + } + auto &lowering = SGF.getTypeLowering(E->getType()); + auto resultAddr = SGF.emitTemporaryAllocation(E, lowering.getLoweredType()); + + // This won't give us a useful diagnostic if the result doesn't end up + // initialized ("variable '' used before being initialized"), but it + // will at least catch a potential miscompile when the SIL verifier is + // disabled. + resultAddr = SGF.B.createMarkUninitialized( + E, resultAddr, MarkUninitializedInst::Kind::Var); + KnownAddressInitialization init(resultAddr); + + // Collect the target exprs that will be used for initialization. + SmallVector scratch; + SILGenFunction::SingleValueStmtInitialization initInfo(&init); + for (auto *E : E->getSingleExprBranches(scratch)) + initInfo.Exprs.insert(E); + + // Push the initialization for branches of the statement to initialize into. + SGF.SingleValueStmtInitStack.push_back(std::move(initInfo)); + SWIFT_DEFER { SGF.SingleValueStmtInitStack.pop_back(); }; + SGF.emitStmt(E->getStmt()); + return RValue(SGF, E, SGF.emitManagedRValueWithCleanup(resultAddr)); +} + RValue RValueEmitter::visitCoerceExpr(CoerceExpr *E, SGFContext C) { if (auto result = tryEmitAsBridgingConversion(SGF, E->getSubExpr(), true, C)) return RValue(SGF, E, *result); diff --git a/lib/SILGen/SILGenFunction.cpp b/lib/SILGen/SILGenFunction.cpp index a77d5ad1952d3..9ad526c8d4e06 100644 --- a/lib/SILGen/SILGenFunction.cpp +++ b/lib/SILGen/SILGenFunction.cpp @@ -1182,6 +1182,18 @@ void SILGenFunction::emitGeneratorFunction( mergeCleanupBlocks(); } +Initialization *SILGenFunction::getSingleValueStmtInit(Expr *E) { + if (SingleValueStmtInitStack.empty()) + return nullptr; + + // Check to see if this is an expression branch of an active + // SingleValueStmtExpr initialization. + if (!SingleValueStmtInitStack.back().Exprs.contains(E)) + return nullptr; + + return SingleValueStmtInitStack.back().Init; +} + void SILGenFunction::emitProfilerIncrement(ASTNode Node) { emitProfilerIncrement(ProfileCounterRef::node(Node)); } diff --git a/lib/SILGen/SILGenFunction.h b/lib/SILGen/SILGenFunction.h index f176fa42eb85f..8000e67382e44 100644 --- a/lib/SILGen/SILGenFunction.h +++ b/lib/SILGen/SILGenFunction.h @@ -310,6 +310,20 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction std::vector BreakContinueDestStack; std::vector SwitchStack; + + /// Information for a parent SingleValueStmtExpr initialization. + struct SingleValueStmtInitialization { + /// The target expressions to be used for initialization. + SmallPtrSet Exprs; + Initialization *Init; + + SingleValueStmtInitialization(Initialization *init) : Init(init) {} + }; + + /// A stack of active SingleValueStmtExpr initializations that may be + /// initialized by the branches of a statement. + std::vector SingleValueStmtInitStack; + /// Keep track of our current nested scope. /// /// The boolean tracks whether this is a binding scope, which should be @@ -630,6 +644,11 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction SmallVectorImpl &directResultsBuffer, SmallVectorImpl &cleanups); + /// Check to see if an initalization for a SingleValueStmtExpr is active, and + /// if the provided expression is for one of its branches. If so, returns the + /// initialization to use for the expression. Otherwise returns \c nullptr. + Initialization *getSingleValueStmtInit(Expr *E); + //===--------------------------------------------------------------------===// // Entry points for codegen //===--------------------------------------------------------------------===// diff --git a/lib/SILGen/SILGenStmt.cpp b/lib/SILGen/SILGenStmt.cpp index d07281e162a56..f7187eb745d98 100644 --- a/lib/SILGen/SILGenStmt.cpp +++ b/lib/SILGen/SILGenStmt.cpp @@ -415,7 +415,16 @@ void StmtEmitter::visitBraceStmt(BraceStmt *S) { if (isa(S)) StmtType = ThrowStmtType; } else if (auto *E = ESD.dyn_cast()) { - SGF.emitIgnoredExpr(E); + // Check to see if we have an initialization for a SingleValueStmtExpr + // active, and if so, use it for this expression branch. If the expression + // is uninhabited, we can skip this, and let unreachability checking + // handle it. + auto *init = SGF.getSingleValueStmtInit(E); + if (init && !E->getType()->isStructurallyUninhabited()) { + SGF.emitExprInto(E, init); + } else { + SGF.emitIgnoredExpr(E); + } } else { auto *D = ESD.get(); diff --git a/lib/Sema/BuilderTransform.cpp b/lib/Sema/BuilderTransform.cpp index e7384b4496e1b..11804c5162850 100644 --- a/lib/Sema/BuilderTransform.cpp +++ b/lib/Sema/BuilderTransform.cpp @@ -364,8 +364,7 @@ class BuilderClosureVisitor } } - VarDecl *visitBraceStmt(BraceStmt *braceStmt) { - SmallVector expressions; + void visitBraceElement(ASTNode node, SmallVectorImpl &expressions) { auto addChild = [&](VarDecl *childVar) { if (!childVar) return; @@ -373,59 +372,68 @@ class BuilderClosureVisitor expressions.push_back(builder.buildVarRef(childVar, childVar->getLoc())); }; - for (auto node : braceStmt->getElements()) { - // Implicit returns in single-expression function bodies are treated - // as the expression. - if (auto returnStmt = - dyn_cast_or_null(node.dyn_cast())) { - assert(returnStmt->isImplicit()); - node = returnStmt->getResult(); - } - - if (auto stmt = node.dyn_cast()) { - addChild(visit(stmt)); - continue; - } + // Implicit returns in single-expression function bodies are treated + // as the expression. + if (auto returnStmt = + dyn_cast_or_null(node.dyn_cast())) { + assert(returnStmt->isImplicit()); + node = returnStmt->getResult(); + } - if (auto decl = node.dyn_cast()) { - // Just ignore #if; the chosen children should appear in the - // surrounding context. This isn't good for source tools but it - // at least works. - if (isa(decl)) - continue; + if (auto stmt = node.dyn_cast()) { + addChild(visit(stmt)); + return; + } - // Skip #warning/#error; we'll handle them when applying the builder. - if (isa(decl)) { - continue; - } + if (auto decl = node.dyn_cast()) { + // Just ignore #if; the chosen children should appear in the + // surrounding context. This isn't good for source tools but it + // at least works. + if (isa(decl)) + return; - // Pattern bindings are okay so long as all of the entries are - // initialized. - if (auto patternBinding = dyn_cast(decl)) { - visitPatternBindingDecl(patternBinding); - continue; - } + // Skip #warning/#error; we'll handle them when applying the builder. + if (isa(decl)) + return; - // Ignore variable declarations, because they're always handled within - // their enclosing pattern bindings. - if (isa(decl)) - continue; + // Pattern bindings are okay so long as all of the entries are + // initialized. + if (auto patternBinding = dyn_cast(decl)) { + visitPatternBindingDecl(patternBinding); + return; + } - if (!unhandledNode) - unhandledNode = decl; + // Ignore variable declarations, because they're always handled within + // their enclosing pattern bindings. + if (isa(decl)) + return; - continue; - } + if (!unhandledNode) + unhandledNode = decl; - auto expr = node.get(); - if (cs && builder.supports(ctx.Id_buildExpression)) { - expr = buildCallIfWanted(expr->getLoc(), ctx.Id_buildExpression, - { expr }, { Identifier() }); - } + return; + } - addChild(captureExpr(expr, /*oneWay=*/true, node.get())); + auto expr = node.get(); + if (auto *SVE = dyn_cast(expr)) { + // This should never be treated as an expression in a result builder, it + // should have statement semantics. + visitBraceElement(SVE->getStmt(), expressions); + return; + } + if (cs && builder.supports(ctx.Id_buildExpression)) { + expr = buildCallIfWanted(expr->getLoc(), ctx.Id_buildExpression, + {expr}, {Identifier()}); } + addChild(captureExpr(expr, /*oneWay=*/true, node.get())); + } + + VarDecl *visitBraceStmt(BraceStmt *braceStmt) { + SmallVector expressions; + for (auto node : braceStmt->getElements()) + visitBraceElement(node, expressions); + if (!cs || hadError) return nullptr; @@ -1054,6 +1062,12 @@ class ResultBuilderTransform } auto *expr = element.get(); + if (auto *SVE = dyn_cast(expr)) { + // This should never be treated as an expression in a result builder, it + // should have statement semantics. + return transformBraceElement(SVE->getStmt(), newBody, + buildBlockArguments); + } if (builder.supports(ctx.Id_buildExpression)) { expr = builder.buildCall(expr->getLoc(), ctx.Id_buildExpression, {expr}, {Identifier()}); @@ -1779,107 +1793,118 @@ class BuilderClosureRewriter solution(solution), dc(dc), builderTransform(builderTransform), rewriteTarget(rewriteTarget) { } - NullablePtr - visitBraceStmt(BraceStmt *braceStmt, ResultBuilderTarget target, - Optional innerTarget = None) { - std::vector newElements; - - // If there is an "inner" target corresponding to this brace, declare - // it's temporary variable if needed. - if (innerTarget) { - declareTemporaryVariable(innerTarget->captured.first, newElements); + /// Visit the element of a brace statement, returning \c false if the element + /// was rewritten successfully, or \c true if there was an error. + bool visitBraceElement(ASTNode node, std::vector &newElements) { + // Implicit returns in single-expression function bodies are treated + // as the expression. + if (auto returnStmt = + dyn_cast_or_null(node.dyn_cast())) { + assert(returnStmt->isImplicit()); + node = returnStmt->getResult(); } - for (auto node : braceStmt->getElements()) { - // Implicit returns in single-expression function bodies are treated - // as the expression. - if (auto returnStmt = - dyn_cast_or_null(node.dyn_cast())) { - assert(returnStmt->isImplicit()); - node = returnStmt->getResult(); + if (auto expr = node.dyn_cast()) { + if (auto *SVE = dyn_cast(expr)) { + // This should never be treated as an expression in a result builder, it + // should have statement semantics. + return visitBraceElement(SVE->getStmt(), newElements); } + // Skip error expressions. + if (isa(expr)) + return false; - if (auto expr = node.dyn_cast()) { - // Skip error expressions. - if (isa(expr)) - continue; + // Each expression turns into a 'let' that captures the value of + // the expression. + auto recorded = takeCapturedExpr(expr); - // Each expression turns into a 'let' that captures the value of - // the expression. - auto recorded = takeCapturedExpr(expr); + // Rewrite the expression + Expr *finalExpr = rewriteExpr(recorded.generatedExpr); - // Rewrite the expression - Expr *finalExpr = rewriteExpr(recorded.generatedExpr); + // Form a new pattern binding to bind the temporary variable to the + // transformed expression. + declareTemporaryVariable(recorded.temporaryVar, newElements, finalExpr); + return false; + } - // Form a new pattern binding to bind the temporary variable to the - // transformed expression. - declareTemporaryVariable(recorded.temporaryVar, newElements, finalExpr); - continue; + if (auto stmt = node.dyn_cast()) { + // "throw" statements produce no value. Transform them directly. + if (auto throwStmt = dyn_cast(stmt)) { + if (auto newStmt = visitThrowStmt(throwStmt)) { + newElements.push_back(newStmt.get()); + } + return false; } - if (auto stmt = node.dyn_cast()) { - // "throw" statements produce no value. Transform them directly. - if (auto throwStmt = dyn_cast(stmt)) { - if (auto newStmt = visitThrowStmt(throwStmt)) { - newElements.push_back(newStmt.get()); - } - continue; - } + // Each statement turns into a (potential) temporary variable + // binding followed by the statement itself. + auto captured = takeCapturedStmt(stmt); - // Each statement turns into a (potential) temporary variable - // binding followed by the statement itself. - auto captured = takeCapturedStmt(stmt); + declareTemporaryVariable(captured.first, newElements); - declareTemporaryVariable(captured.first, newElements); + auto finalStmt = + visit(stmt, ResultBuilderTarget{ResultBuilderTarget::TemporaryVar, + std::move(captured)}); - auto finalStmt = visit( - stmt, - ResultBuilderTarget{ResultBuilderTarget::TemporaryVar, - std::move(captured)}); + // Re-write of statements that envolve type-checking + // could fail, such a failure terminates the walk. + if (!finalStmt) + return true; - // Re-write of statements that envolve type-checking - // could fail, such a failure terminates the walk. - if (!finalStmt) - return nullptr; + newElements.push_back(finalStmt.get()); + return false; + } - newElements.push_back(finalStmt.get()); - continue; - } + auto decl = node.get(); - auto decl = node.get(); + // Skip #if declarations. + if (isa(decl)) { + newElements.push_back(decl); + return false; + } - // Skip #if declarations. - if (isa(decl)) { - newElements.push_back(decl); - continue; - } + // Diagnose #warning / #error during application. + if (auto poundDiag = dyn_cast(decl)) { + TypeChecker::typeCheckDecl(poundDiag); + newElements.push_back(decl); + return false; + } - // Diagnose #warning / #error during application. - if (auto poundDiag = dyn_cast(decl)) { - TypeChecker::typeCheckDecl(poundDiag); - newElements.push_back(decl); - continue; - } + // Skip variable declarations; they're always part of a pattern + // binding. + if (isa(decl)) { + TypeChecker::typeCheckDecl(decl); + newElements.push_back(decl); + return false; + } - // Skip variable declarations; they're always part of a pattern - // binding. - if (isa(decl)) { - TypeChecker::typeCheckDecl(decl); - newElements.push_back(decl); - continue; - } + // Handle pattern bindings. + if (auto patternBinding = dyn_cast(decl)) { + auto resultTarget = + rewriteTarget(SolutionApplicationTarget{patternBinding}); + assert(resultTarget.has_value() && + "Could not rewrite pattern binding entries!"); + TypeChecker::typeCheckDecl(resultTarget->getAsPatternBinding()); + newElements.push_back(resultTarget->getAsPatternBinding()); + return false; + } - // Handle pattern bindings. - if (auto patternBinding = dyn_cast(decl)) { - auto resultTarget = rewriteTarget(SolutionApplicationTarget{patternBinding}); - assert(resultTarget.has_value() - && "Could not rewrite pattern binding entries!"); - TypeChecker::typeCheckDecl(resultTarget->getAsPatternBinding()); - newElements.push_back(resultTarget->getAsPatternBinding()); - continue; - } + llvm_unreachable("Cannot yet handle declarations"); + } - llvm_unreachable("Cannot yet handle declarations"); + NullablePtr + visitBraceStmt(BraceStmt *braceStmt, ResultBuilderTarget target, + Optional innerTarget = None) { + std::vector newElements; + + // If there is an "inner" target corresponding to this brace, declare + // it's temporary variable if needed. + if (innerTarget) + declareTemporaryVariable(innerTarget->captured.first, newElements); + + for (auto node : braceStmt->getElements()) { + if (visitBraceElement(node, newElements)) + return nullptr; } // If there is an "inner" target corresponding to this brace, initialize diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 23301a9c3c3fd..1f02775d854e2 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -5375,6 +5375,10 @@ namespace { llvm_unreachable("found KeyPathDotExpr in CSApply"); } + Expr *visitSingleValueStmtExpr(SingleValueStmtExpr *E) { + llvm_unreachable("Handled by the walker directly"); + } + Expr *visitOneWayExpr(OneWayExpr *E) { auto type = simplifyType(cs.getType(E)); return coerceToType(E->getSubExpr(), type, cs.getConstraintLocator(E)); @@ -8521,7 +8525,12 @@ namespace { return Action::SkipChildren(closure); } - if (auto tap = dyn_cast_or_null(expr)) { + if (auto *SVE = dyn_cast(expr)) { + rewriteSingleValueStmtExpr(SVE); + return Action::SkipChildren(SVE); + } + + if (auto tap = dyn_cast(expr)) { // We remember the DeclContext because the code to handle // single-expression-body closures above changes it. TapsToTypeCheck.push_back(std::make_pair(tap, Rewriter.dc)); @@ -8640,6 +8649,36 @@ namespace { } } } + + bool rewriteSingleValueStmtExpr(SingleValueStmtExpr *SVE) { + auto &solution = Rewriter.solution; + auto resultTy = solution.getResolvedType(SVE); + Rewriter.cs.setType(SVE, resultTy); + + SmallVector scratch; + SmallPtrSet exprBranches; + for (auto *branch : SVE->getSingleExprBranches(scratch)) + exprBranches.insert(branch); + + return Rewriter.cs.applySolutionToSingleValueStmt( + solution, SVE, solution.getDC(), [&](SolutionApplicationTarget target) { + // We need to fixup the conversion type to the full result type, + // not the branch result type. This is necessary as there may be + // an additional conversion required for the branch. + if (auto *E = target.getAsExpr()) { + if (exprBranches.contains(E)) + target.setExprConversionType(resultTy); + } + auto resultTarget = rewriteTarget(target); + if (!resultTarget) + return resultTarget; + + if (auto expr = resultTarget->getAsExpr()) + solution.setExprTypes(expr); + + return resultTarget; + }); + } }; } // end anonymous namespace @@ -9096,26 +9135,28 @@ ExprWalker::rewriteTarget(SolutionApplicationTarget target) { case CTP_ExprPattern: case CTP_ForEachStmt: case CTP_ForEachSequence: - case swift::CTP_ReturnSingleExpr: - case swift::CTP_YieldByValue: - case swift::CTP_YieldByReference: - case swift::CTP_ThrowStmt: - case swift::CTP_EnumCaseRawValue: - case swift::CTP_DefaultParameter: - case swift::CTP_AutoclosureDefaultParameter: - case swift::CTP_CalleeResult: - case swift::CTP_CallArgument: - case swift::CTP_ClosureResult: - case swift::CTP_ArrayElement: - case swift::CTP_DictionaryKey: - case swift::CTP_DictionaryValue: - case swift::CTP_CoerceOperand: - case swift::CTP_AssignSource: - case swift::CTP_SubscriptAssignSource: - case swift::CTP_Condition: - case swift::CTP_WrappedProperty: - case swift::CTP_ComposedPropertyWrapper: - case swift::CTP_CannotFail: + case CTP_ReturnSingleExpr: + case CTP_YieldByValue: + case CTP_YieldByReference: + case CTP_ThrowStmt: + case CTP_EnumCaseRawValue: + case CTP_DefaultParameter: + case CTP_AutoclosureDefaultParameter: + case CTP_CalleeResult: + case CTP_CallArgument: + case CTP_ClosureResult: + case CTP_ArrayElement: + case CTP_DictionaryKey: + case CTP_DictionaryValue: + case CTP_CoerceOperand: + case CTP_AssignSource: + case CTP_SubscriptAssignSource: + case CTP_Condition: + case CTP_WrappedProperty: + case CTP_ComposedPropertyWrapper: + case CTP_CannotFail: + case CTP_SingleValueStmtBranch: + case CTP_SingleValueStmtBranchInSingleExprClosure: result.setExpr(rewrittenExpr); break; } @@ -9329,12 +9370,25 @@ ExprWalker::rewriteTarget(SolutionApplicationTarget target) { // expression function which got deduced to be `Never`. Type convertType = target.getExprConversionType(); auto shouldCoerceToContextualType = [&]() { - return convertType && - !convertType->hasPlaceholder() && - !target.isOptionalSomePatternInit() && - !(solution.getResolvedType(resultExpr)->isUninhabited() && - cs.getContextualTypePurpose(target.getAsExpr()) - == CTP_ReturnSingleExpr); + if (!convertType) + return false; + + if (convertType->hasPlaceholder()) + return false; + + if (target.isOptionalSomePatternInit()) + return false; + + if (solution.getResolvedType(resultExpr)->isUninhabited() || + solution.simplifyType(convertType)->isVoid()) { + auto contextPurpose = cs.getContextualTypePurpose(target.getAsExpr()); + if (contextPurpose == CTP_ReturnSingleExpr || + contextPurpose == CTP_SingleValueStmtBranch || + contextPurpose == CTP_SingleValueStmtBranchInSingleExprClosure) { + return false; + } + } + return true; }; // If we're supposed to convert the expression to some particular type, diff --git a/lib/Sema/CSBindings.cpp b/lib/Sema/CSBindings.cpp index ef5ddc9b5b655..fbcd84625f34f 100644 --- a/lib/Sema/CSBindings.cpp +++ b/lib/Sema/CSBindings.cpp @@ -1071,6 +1071,15 @@ bool BindingSet::favoredOverConjunction(Constraint *conjunction) const { if (forClosureResult() || forGenericParameter()) return false; } + + if (conjunction->getLocator()->isForSingleValueStmtConjunction()) { + // If the bindings should be delayed, try the conjunction first. + // TODO: Seems like this ought to apply generally? Currently regresses a + // diagnostic in a test. + if (isDelayed()) + return false; + } + return true; } @@ -1150,8 +1159,11 @@ PotentialBindings::inferFromRelational(Constraint *constraint) { // on the other side since it could only be either a closure // parameter or a result type, and we can't get a full set // of bindings for them until closure's body is opened. + // Also do the same for SingleValueStmtExprs, as we must solve the + // conjunction for the branches before we have the full set of bindings. if (auto *typeVar = first->getAs()) { - if (typeVar->getImpl().isClosureType()) { + if (typeVar->getImpl().isClosureType() || + typeVar->getImpl().getLocator()->directlyAt()) { DelayedBy.push_back(constraint); return None; } diff --git a/lib/Sema/CSDiagnostics.cpp b/lib/Sema/CSDiagnostics.cpp index 8865afe52121c..33524a4ce4d7d 100644 --- a/lib/Sema/CSDiagnostics.cpp +++ b/lib/Sema/CSDiagnostics.cpp @@ -738,6 +738,8 @@ Optional> GenericArgumentsMismatchFailure::getDiagnosticFor( case CTP_CalleeResult: case CTP_EnumCaseRawValue: case CTP_ExprPattern: + case CTP_SingleValueStmtBranch: + case CTP_SingleValueStmtBranchInSingleExprClosure: break; } return None; @@ -2488,7 +2490,10 @@ bool ContextualFailure::diagnoseAsError() { diagnostic = diag::ternary_expr_cases_mismatch; break; } - + case ConstraintLocator::SingleValueStmtBranch: { + diagnostic = diag::single_value_stmt_branches_mismatch; + break; + } case ConstraintLocator::ContextualType: { if (diagnoseConversionToBool()) return true; @@ -2688,6 +2693,8 @@ getContextualNilDiagnostic(ContextualTypePurpose CTP) { case CTP_WrappedProperty: case CTP_ComposedPropertyWrapper: case CTP_ExprPattern: + case CTP_SingleValueStmtBranch: + case CTP_SingleValueStmtBranchInSingleExprClosure: return None; case CTP_EnumCaseRawValue: @@ -3442,6 +3449,10 @@ ContextualFailure::getDiagnosticFor(ContextualTypePurpose context, case CTP_WrappedProperty: return diag::wrapped_value_mismatch; + case CTP_SingleValueStmtBranch: + case CTP_SingleValueStmtBranchInSingleExprClosure: + return diag::cannot_convert_initializer_value; + case CTP_CaseStmt: case CTP_ThrowStmt: case CTP_ForEachStmt: diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index f9606657abc48..eb039526014e9 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -3711,6 +3711,10 @@ namespace { llvm_unreachable("found KeyPathDotExpr in CSGen"); } + Type visitSingleValueStmtExpr(SingleValueStmtExpr *E) { + llvm_unreachable("Handled by the walker directly"); + } + Type visitOneWayExpr(OneWayExpr *expr) { auto locator = CS.getConstraintLocator(expr); auto resultTypeVar = CS.createTypeVariable(locator, 0); @@ -3989,6 +3993,12 @@ namespace { return Action::SkipChildren(expr); } + if (auto *SVE = dyn_cast(expr)) { + if (CS.generateConstraints(SVE)) + return Action::Stop(); + return Action::SkipChildren(expr); + } + // Note that the subexpression of a #selector expression is // unevaluated. if (auto sel = dyn_cast(expr)) { diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 3673afa00feb8..c2824d3307858 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -6218,13 +6218,17 @@ bool ConstraintSystem::repairFailures( break; } - case ConstraintLocator::TernaryBranch: { + case ConstraintLocator::TernaryBranch: + case ConstraintLocator::SingleValueStmtBranch: { + if (lhs->hasPlaceholder() || rhs->hasPlaceholder()) + return true; + recordAnyTypeVarAsPotentialHole(lhs); recordAnyTypeVarAsPotentialHole(rhs); - // If `if` expression has a contextual type, let's consider it a source of - // truth and produce a contextual mismatch instead of per-branch failure, - // because it's a better pointer than potential then-to-else type mismatch. + // If there's a contextual type, let's consider it the source of truth and + // produce a contextual mismatch instead of per-branch failure, because + // it's a better pointer than potential then-to-else type mismatch. if (auto contextualType = getContextualType(anchor, /*forConstraint=*/false)) { auto purpose = getContextualTypePurpose(anchor); @@ -6246,7 +6250,7 @@ bool ConstraintSystem::repairFailures( } // If there is no contextual type, this is most likely a contextual type - // mismatch between then/else branches of ternary operator. + // mismatch between the branches. conversionsOrFixes.push_back(ContextualMismatch::create( *this, lhs, rhs, getConstraintLocator(locator))); break; @@ -7369,6 +7373,31 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind, return getTypeMatchSuccess(); } } + + // We also need to propagate this conversion into the branches for single + // value statements. + // + // As with the previous checks, we only allow the Void conversion in + // an implicit single-expression closure. In the more general case, we + // only allow the Never conversion. + if (auto contextualTy = elt->getAs()) { + if (contextualTy->isFor(CTP_SingleValueStmtBranchInSingleExprClosure) || + (type1->isUninhabited() && + contextualTy->isFor(CTP_SingleValueStmtBranch))) { + increaseScore(SK_FunctionConversion); + return getTypeMatchSuccess(); + } + } + // We need to check this in addition to the contextual type purpose for + // a single value statement, as this handles the outer join, whereas the + // previous check only handles the inner conversion to the branch type. + auto *loc = getConstraintLocator(locator); + if (loc->isForSingleValueStmtBranchInSingleExprClosure() || + (type1->isUninhabited() && + loc->isLastElement())) { + increaseScore(SK_FunctionConversion); + return getTypeMatchSuccess(); + } } } @@ -14050,6 +14079,14 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint( if (branchElt->forElse()) impact = 10; } + if (auto branchElt = + locator + ->getLastElementAs()) { + // Similar to a ternary, except we have N branches. Let's prefer the fix + // on the first branch. + if (branchElt->getExprBranchIndex() > 0) + impact = 10; + } // Increase impact of invalid conversions to `Any` and `AnyHashable` // associated with collection elements (i.e. for-in sequence element) @@ -14525,6 +14562,8 @@ void ConstraintSystem::addContextualConversionConstraint( case CTP_WrappedProperty: case CTP_ComposedPropertyWrapper: case CTP_ExprPattern: + case CTP_SingleValueStmtBranch: + case CTP_SingleValueStmtBranchInSingleExprClosure: break; } diff --git a/lib/Sema/CSSolver.cpp b/lib/Sema/CSSolver.cpp index 0128ea5c3b3dd..0d7460c1ba0a2 100644 --- a/lib/Sema/CSSolver.cpp +++ b/lib/Sema/CSSolver.cpp @@ -1000,6 +1000,11 @@ void ConstraintSystem::shrink(Expr *expr) { return Action::SkipChildren(expr); } + // Same as TapExpr and ClosureExpr, we'll handle SingleValueStmtExprs + // separately. + if (isa(expr)) + return Action::SkipChildren(expr); + if (auto coerceExpr = dyn_cast(expr)) { if (coerceExpr->isLiteralInit()) ApplyExprs.push_back({coerceExpr, 1}); diff --git a/lib/Sema/CSStep.cpp b/lib/Sema/CSStep.cpp index 107623b949959..e69cf1c596535 100644 --- a/lib/Sema/CSStep.cpp +++ b/lib/Sema/CSStep.cpp @@ -1091,3 +1091,47 @@ void ConjunctionStep::restoreOuterState(const Score &solutionScore) const { constraint.setActive(true); } } + +void ConjunctionStep::SolverSnapshot::applySolution(const Solution &solution) { + CS.applySolution(solution); + + if (!CS.shouldAttemptFixes()) + return; + + // If inference succeeded, we are done. + auto score = solution.getFixedScore(); + if (score.Data[SK_Fix] == 0) + return; + + auto holeify = [&](Type componentTy) { + if (auto *typeVar = componentTy->getAs()) { + CS.assignFixedType( + typeVar, PlaceholderType::get(CS.getASTContext(), typeVar)); + } + }; + + // If this conjunction represents a closure and inference + // has failed, let's bind all of unresolved type variables + // in its interface type to holes to avoid extraneous + // fixes produced by outer context. + auto locator = Conjunction->getLocator(); + if (locator->directlyAt()) { + auto closureTy = + CS.getClosureType(castToExpr(locator->getAnchor())); + + CS.simplifyType(closureTy).visit(holeify); + } + + // Same for a SingleValueStmtExpr, but we need to do it for the branch + // contextual types, which are the type variables that connect the conjuction + // to the outer system. + if (locator->isForSingleValueStmtConjunction()) { + auto *SVE = castToExpr(locator->getAnchor()); + SmallVector scratch; + for (auto *E : SVE->getSingleExprBranches(scratch)) { + auto contextInfo = CS.getContextualTypeInfo(E); + assert(contextInfo && "Didn't generate constraints?"); + CS.simplifyType(contextInfo->getType()).visit(holeify); + } + } +} diff --git a/lib/Sema/CSStep.h b/lib/Sema/CSStep.h index 5138412100741..d358a66156f9f 100644 --- a/lib/Sema/CSStep.h +++ b/lib/Sema/CSStep.h @@ -895,35 +895,7 @@ class ConjunctionStep : public BindingStep { CG.addConstraint(&constraint); } - void applySolution(const Solution &solution) { - CS.applySolution(solution); - - if (!CS.shouldAttemptFixes()) - return; - - // If inference succeeded, we are done. - auto score = solution.getFixedScore(); - if (score.Data[SK_Fix] == 0) - return; - - // If this conjunction represents a closure and inference - // has failed, let's bind all of unresolved type variables - // in its interface type to holes to avoid extraneous - // fixes produced by outer context. - - auto locator = Conjunction->getLocator(); - if (locator->directlyAt()) { - auto closureTy = - CS.getClosureType(castToExpr(locator->getAnchor())); - - CS.simplifyType(closureTy).visit([&](Type componentTy) { - if (auto *typeVar = componentTy->getAs()) { - CS.assignFixedType( - typeVar, PlaceholderType::get(CS.getASTContext(), typeVar)); - } - }); - } - } + void applySolution(const Solution &solution); }; /// Best solution solver reached so far. diff --git a/lib/Sema/CSSyntacticElement.cpp b/lib/Sema/CSSyntacticElement.cpp index bc3f0712e0fa5..15b7c668fdf2b 100644 --- a/lib/Sema/CSSyntacticElement.cpp +++ b/lib/Sema/CSSyntacticElement.cpp @@ -291,6 +291,11 @@ static void createConjunction(ConstraintSystem &cs, ConstraintLocator *locator) { bool isIsolated = false; + // Single value statement conjunctions are always isolated, as we want to + // solve the branches independently of the rest of the system. + if (locator->isForSingleValueStmtConjunction()) + isIsolated = true; + SmallVector constraints; SmallVector referencedVars; @@ -323,6 +328,11 @@ static void createConjunction(ConstraintSystem &cs, isIsolated = true; } + if (locator->isForSingleValueStmtConjunction()) { + auto *SVE = castToExpr(locator->getAnchor()); + referencedVars.push_back(cs.getType(SVE)->castTo()); + } + UnresolvedClosureParameterCollector paramCollector(cs); for (const auto &entry : elements) { @@ -365,7 +375,8 @@ ElementInfo makeElement(ASTNode node, ConstraintLocator *locator, } struct SyntacticElementContext - : public llvm::PointerUnion { + : public llvm::PointerUnion { // Inherit the constructors from PointerUnion. using PointerUnion::PointerUnion; @@ -385,11 +396,18 @@ struct SyntacticElementContext return {func}; } + static SyntacticElementContext + forSingleValueStmtExpr(SingleValueStmtExpr *SVE) { + return {SVE}; + } + DeclContext *getAsDeclContext() const { if (auto *fn = this->dyn_cast()) { return fn; } else if (auto *closure = this->dyn_cast()) { return closure; + } else if (auto *SVE = dyn_cast()) { + return SVE->getDeclContext(); } else { llvm_unreachable("unsupported kind"); } @@ -418,11 +436,13 @@ struct SyntacticElementContext } } - BraceStmt *getBody() const { + Stmt *getStmt() const { if (auto *fn = this->dyn_cast()) { return fn->getBody(); } else if (auto *closure = this->dyn_cast()) { return closure->getBody(); + } else if (auto *SVE = dyn_cast()) { + return SVE->getStmt(); } else { llvm_unreachable("unsupported kind"); } @@ -726,17 +746,12 @@ class SyntacticElementConstraintGenerator // Other declarations will be handled at application time. } - void visitBreakStmt(BreakStmt *breakStmt) { - } - - void visitContinueStmt(ContinueStmt *continueStmt) { - } - - void visitDeferStmt(DeferStmt *deferStmt) { - } - - void visitFallthroughStmt(FallthroughStmt *fallthroughStmt) { - } + // These statements don't require any type-checking. + void visitBreakStmt(BreakStmt *breakStmt) {} + void visitContinueStmt(ContinueStmt *continueStmt) {} + void visitDeferStmt(DeferStmt *deferStmt) {} + void visitFallthroughStmt(FallthroughStmt *fallthroughStmt) {} + void visitFailStmt(FailStmt *fail) {} void visitStmtCondition(LabeledConditionalStmt *S, SmallVectorImpl &elements, @@ -984,9 +999,13 @@ class SyntacticElementConstraintGenerator SmallVector elements; for (auto element : braceStmt->getElements()) { - bool isDiscarded = - element.is() && - (!ctx.LangOpts.Playground && !ctx.LangOpts.DebuggerSupport); + bool isDiscarded = false; + auto contextInfo = cs.getContextualTypeInfo(element); + + if (auto *E = element.dyn_cast()) { + if (!ctx.LangOpts.Playground && !ctx.LangOpts.DebuggerSupport) + isDiscarded = !contextInfo || contextInfo->purpose == CTP_Unused; + } if (auto *decl = element.dyn_cast()) { if (auto *PDB = dyn_cast(decl)) { @@ -995,17 +1014,21 @@ class SyntacticElementConstraintGenerator } } - elements.push_back( - makeElement(element, - cs.getConstraintLocator( - locator, LocatorPathElt::SyntacticElement(element)), - /*contextualInfo=*/{}, isDiscarded)); + elements.push_back(makeElement( + element, + cs.getConstraintLocator(locator, + LocatorPathElt::SyntacticElement(element)), + contextInfo.getValueOr(ContextualTypeInfo()), isDiscarded)); } createConjunction(cs, elements, locator); } void visitReturnStmt(ReturnStmt *returnStmt) { + auto contextualResultInfo = getContextualResultInfo(); + assert(contextualResultInfo && + "Should have bailed in the precheck if we're not in a function"); + // Single-expression closures are effectively a `return` statement, // so let's give them a special locator as to indicate that. // Return statements might not have a result if we have a closure whose @@ -1021,9 +1044,8 @@ class SyntacticElementConstraintGenerator return; } - auto contextualResultInfo = getContextualResultInfo(); cs.addConstraint(ConstraintKind::Conversion, cs.getType(expr), - contextualResultInfo.getType(), + contextualResultInfo->getType(), cs.getConstraintLocator( context.getAsAbstractClosureExpr().get(), LocatorPathElt::ClosureBody( @@ -1044,10 +1066,9 @@ class SyntacticElementConstraintGenerator resultExpr = getVoidExpr(cs.getASTContext(), returnStmt->getEndLoc()); } - auto contextualResultInfo = getContextualResultInfo(); SolutionApplicationTarget target(resultExpr, context.getAsDeclContext(), - contextualResultInfo.purpose, - contextualResultInfo.getType(), + contextualResultInfo->purpose, + contextualResultInfo->getType(), /*isDiscarded=*/false); if (cs.generateConstraints(target, FreeTypeVariableBinding::Disallow)) { @@ -1056,31 +1077,31 @@ class SyntacticElementConstraintGenerator } cs.setContextualType(target.getAsExpr(), - TypeLoc::withoutLoc(contextualResultInfo.getType()), - contextualResultInfo.purpose); + TypeLoc::withoutLoc(contextualResultInfo->getType()), + contextualResultInfo->purpose); cs.setSolutionApplicationTarget(returnStmt, target); } - ContextualTypeInfo getContextualResultInfo() const { - auto funcRef = context.getAsAnyFunctionRef(); + Optional getContextualResultInfo() const { + auto funcRef = AnyFunctionRef::fromDeclContext(context.getAsDeclContext()); if (!funcRef) - return {Type(), CTP_Unused}; + return None; if (auto transform = cs.getAppliedResultBuilderTransform(*funcRef)) - return {transform->bodyResultType, CTP_ReturnStmt}; + return ContextualTypeInfo{transform->bodyResultType, CTP_ReturnStmt}; if (auto *closure = getAsExpr(funcRef->getAbstractClosureExpr())) - return {cs.getClosureType(closure)->getResult(), CTP_ClosureResult}; + return ContextualTypeInfo{cs.getClosureType(closure)->getResult(), + CTP_ClosureResult}; - return {funcRef->getBodyResultType(), CTP_ReturnStmt}; + return ContextualTypeInfo{funcRef->getBodyResultType(), CTP_ReturnStmt}; } #define UNSUPPORTED_STMT(STMT) void visit##STMT##Stmt(STMT##Stmt *) { \ llvm_unreachable("Unsupported statement kind " #STMT); \ } UNSUPPORTED_STMT(Yield) - UNSUPPORTED_STMT(Fail) #undef UNSUPPORTED_STMT private: @@ -1187,6 +1208,53 @@ bool ConstraintSystem::generateConstraints(AnyFunctionRef fn, BraceStmt *body) { return generator.hadError; } +bool ConstraintSystem::generateConstraints(SingleValueStmtExpr *E) { + auto *S = E->getStmt(); + auto &ctx = getASTContext(); + + auto *loc = getConstraintLocator(E); + Type resultTy = createTypeVariable(loc, /*options*/ 0); + setType(E, resultTy); + + // Assign contextual types for each of the expression branches, which will + // either be the contextual type for the overall expression, or fresh type + // variables. + SmallVector, 4> branchTypes; + SmallVector scratch; + auto branches = E->getSingleExprBranches(scratch); + for (auto idx : indices(branches)) { + auto *branchExpr = branches[idx]; + auto *branchLoc = + getConstraintLocator(E, LocatorPathElt::SingleValueStmtBranch(idx)); + auto branchTy = createTypeVariable(branchLoc, /*options*/ 0); + + auto contextPurpose = + branchLoc->isForSingleValueStmtBranchInSingleExprClosure() + ? CTP_SingleValueStmtBranchInSingleExprClosure + : CTP_SingleValueStmtBranch; + setContextualType(branchExpr, TypeLoc::withoutLoc(branchTy), + contextPurpose); + branchTypes.emplace_back(branchTy, branchLoc); + } + + if (branchTypes.empty()) { + // If we only have statement branches, the expression is typed as Void. This + // should only be the case for 'if' and 'switch' statements that must be + // expressions that have branches that all end in a throw, and we'll warn + // that we've inferred Void. + addConstraint(ConstraintKind::Bind, resultTy, ctx.getVoidType(), loc); + } else { + // Join the branch types. + addJoinConstraint(loc, branchTypes, resultTy); + } + + // Generate the conjunction for the branches. + auto context = SyntacticElementContext::forSingleValueStmtExpr(E); + SyntacticElementConstraintGenerator generator(*this, context, loc); + generator.visit(S); + return generator.hadError; +} + bool ConstraintSystem::isInResultBuilderContext(ClosureExpr *closure) const { if (!closure->hasSingleExpressionBody()) { auto *DC = closure->getParent(); @@ -1236,6 +1304,8 @@ ConstraintSystem::simplifySyntacticElementConstraint( context = SyntacticElementContext::forClosure(closure); } else if (auto *fn = getAsDecl(anchor)) { context = SyntacticElementContext::forFunction(fn); + } else if (auto *SVE = getAsExpr(anchor)) { + context = SyntacticElementContext::forSingleValueStmtExpr(SVE); } else { return SolutionKind::Error; } @@ -1294,10 +1364,21 @@ class SyntacticElementSolutionApplication SyntacticElementSolutionApplication(Solution &solution, SyntacticElementContext context, - Type resultType, RewriteTargetFn rewriteTarget) - : solution(solution), context(context), resultType(resultType), - rewriteTarget(rewriteTarget) {} + : solution(solution), context(context), rewriteTarget(rewriteTarget) { + if (auto fn = AnyFunctionRef::fromDeclContext(context.getAsDeclContext())) { + if (auto transform = solution.getAppliedBuilderTransform(*fn)) { + resultType = solution.simplifyType(transform->bodyResultType); + } else if (auto *closure = + getAsExpr(fn->getAbstractClosureExpr())) { + resultType = solution.getResolvedType(closure) + ->castTo() + ->getResult(); + } else { + resultType = fn->getBodyResultType(); + } + } + } virtual ~SyntacticElementSolutionApplication() {} @@ -1375,6 +1456,10 @@ class SyntacticElementSolutionApplication return fallthroughStmt; } + ASTNode visitFailStmt(FailStmt *failStmt) { + return failStmt; + } + ASTNode visitDeferStmt(DeferStmt *deferStmt) { TypeChecker::typeCheckDecl(deferStmt->getTempDecl()); @@ -1584,7 +1669,7 @@ class SyntacticElementSolutionApplication return caseStmt; } - ASTNode visitBraceElement(ASTNode node) { + virtual ASTNode visitBraceElement(ASTNode node) { auto &cs = solution.getConstraintSystem(); if (auto *expr = node.dyn_cast()) { // Rewrite the expression. @@ -1592,7 +1677,18 @@ class SyntacticElementSolutionApplication if (auto rewrittenTarget = rewriteTarget(target)) { node = rewrittenTarget->getAsExpr(); - if (target.isDiscardedExpr()) + auto isDiscarded = target.isDiscardedExpr(); + + // A branch of a single value statement expression is effectively + // discarded if the contextual type ends up being Void. + auto contextualPurpose = target.getExprContextualTypePurpose(); + if (contextualPurpose == CTP_SingleValueStmtBranch || + contextualPurpose == CTP_SingleValueStmtBranchInSingleExprClosure) { + isDiscarded = rewrittenTarget->getExprConversionType() + ->lookThroughAllOptionalTypes() + ->isVoid(); + } + if (isDiscarded) TypeChecker::checkIgnoredExpr(castToExpr(node)); } else { hadError = true; @@ -1784,13 +1880,12 @@ class SyntacticElementSolutionApplication llvm_unreachable("Unsupported statement kind " #STMT); \ } UNSUPPORTED_STMT(Yield) - UNSUPPORTED_STMT(Fail) #undef UNSUPPORTED_STMT public: - /// Apply solution to the closure and return updated body. - ASTNode apply() { - auto body = visit(context.getBody()); + /// Apply the solution to the context and return updated statement. + Stmt *apply() { + auto body = visit(context.getStmt()); // Since local functions can capture variables that are declared // after them, let's type-check them after all of the pattern @@ -1798,7 +1893,7 @@ class SyntacticElementSolutionApplication for (auto *func : LocalFuncs) TypeChecker::typeCheckDecl(func); - return body; + return body ? body.get() : nullptr; } }; @@ -1811,11 +1906,11 @@ class ResultBuilderRewriter : public SyntacticElementSolutionApplication { RewriteTargetFn rewriteTarget) : SyntacticElementSolutionApplication( solution, SyntacticElementContext::forFunctionRef(context), - transform.bodyResultType, rewriteTarget), + rewriteTarget), Transform(transform) {} bool apply() { - auto body = visit(context.getBody()); + auto body = visit(context.getStmt()); if (!body || hadError) return true; @@ -1847,6 +1942,15 @@ class ResultBuilderRewriter : public SyntacticElementSolutionApplication { return doStmt; } + ASTNode visitBraceElement(ASTNode node) override { + if (auto *SVE = getAsExpr(node)) { + // This should never be treated as an expression in a result builder, + // it should have statement semantics. + return visitBraceElement(SVE->getStmt()); + } + return SyntacticElementSolutionApplication::visitBraceElement(node); + } + NullablePtr transformDo(DoStmt *doStmt) { if (!doStmt->isImplicit()) return nullptr; @@ -2192,29 +2296,15 @@ bool ConstraintSystem::applySolutionToBody(Solution &solution, // transformations. llvm::SaveAndRestore savedDC(currentDC, fn.getAsDeclContext()); - Type resultTy; - - if (auto transform = solution.getAppliedBuilderTransform(fn)) { - resultTy = solution.simplifyType(transform->bodyResultType); - } else if (auto *closure = - getAsExpr(fn.getAbstractClosureExpr())) { - resultTy = - solution.getResolvedType(closure)->castTo()->getResult(); - } else { - resultTy = fn.getBodyResultType(); - } - SyntacticElementSolutionApplication application( - solution, SyntacticElementContext::forFunctionRef(fn), resultTy, - rewriteTarget); + solution, SyntacticElementContext::forFunctionRef(fn), rewriteTarget); - auto body = application.apply(); + auto *body = application.apply(); if (!body || application.hadError) return true; - fn.setTypecheckedBody(castToStmt(body), - fn.hasSingleExpressionBody()); + fn.setTypecheckedBody(cast(body), fn.hasSingleExpressionBody()); return false; } @@ -2233,6 +2323,21 @@ bool ConjunctionElement::mightContainCodeCompletionToken( } } +bool ConstraintSystem::applySolutionToSingleValueStmt( + Solution &solution, SingleValueStmtExpr *SVE, DeclContext *DC, + RewriteTargetFn rewriteTarget) { + + auto context = SyntacticElementContext::forSingleValueStmtExpr(SVE); + SyntacticElementSolutionApplication application(solution, context, + rewriteTarget); + auto *stmt = application.apply(); + if (!stmt || application.hadError) + return true; + + SVE->setStmt(stmt); + return false; +} + void ConjunctionElement::findReferencedVariables( ConstraintSystem &cs, SmallPtrSetImpl &typeVars) const { auto referencedVars = Element->getTypeVariables(); @@ -2241,10 +2346,32 @@ void ConjunctionElement::findReferencedVariables( if (Element->getKind() != ConstraintKind::SyntacticElement) return; + // Add any type variables present in the fixed types for the branches of + // a single value statement. + for (auto *tv : referencedVars) { + auto *tvLoc = tv->getImpl().getLocator(); + if (!tvLoc->isLastElement()) + continue; + + auto fixedTy = cs.getFixedTypeRecursive(tv, /*wantRValue*/ false); + if (fixedTy->hasTypeVariable()) { + SmallPtrSet tvs; + fixedTy->getTypeVariables(tvs); + typeVars.insert(tvs.begin(), tvs.end()); + } + } + ASTNode element = Element->getSyntacticElement(); auto *locator = Element->getLocator(); - TypeVariableRefFinder refFinder(cs, locator->getAnchor(), typeVars); + ASTNode parent = locator->getAnchor(); + if (auto *SVE = getAsExpr(parent)) { + // Use a parent closure if we have one. This is needed to correctly handle + // return statements that refer to an outer closure. + if (auto *CE = dyn_cast(SVE->getDeclContext())) + parent = CE; + } + TypeVariableRefFinder refFinder(cs, parent, typeVars); // If this is a pattern of `for-in` statement, let's walk into `for-in` // sequence expression because both elements are type-checked together. diff --git a/lib/Sema/CompletionContextFinder.cpp b/lib/Sema/CompletionContextFinder.cpp index 84188d07eeb8e..a41fe70ca8aa1 100644 --- a/lib/Sema/CompletionContextFinder.cpp +++ b/lib/Sema/CompletionContextFinder.cpp @@ -24,6 +24,10 @@ CompletionContextFinder::walkToExprPre(Expr *E) { closure}); } + if (isa(E)) { + Contexts.push_back({ContextKind::SingleValueStmtExpr, E}); + } + if (isa(E)) { Contexts.push_back({ContextKind::StringInterpolation, E}); } @@ -101,6 +105,7 @@ Optional CompletionContextFinder::getFallbackCompletionExpr() const { for (auto context : Contexts) { switch (context.Kind) { case ContextKind::StringInterpolation: + case ContextKind::SingleValueStmtExpr: LLVM_FALLTHROUGH; case ContextKind::FallbackExpression: if (!fallback && context.E != InitialExpr) diff --git a/lib/Sema/Constraint.cpp b/lib/Sema/Constraint.cpp index 481f48d12eee6..1abaab2651db5 100644 --- a/lib/Sema/Constraint.cpp +++ b/lib/Sema/Constraint.cpp @@ -1089,7 +1089,11 @@ Constraint *Constraint::createSyntacticElement(ConstraintSystem &cs, ContextualTypeInfo context, ConstraintLocator *locator, bool isDiscarded) { + // Collect type variables. SmallPtrSet typeVars; + if (auto contextTy = context.getType()) + contextTy->getTypeVariables(typeVars); + unsigned size = totalSizeToAlloc(typeVars.size()); void *mem = cs.getAllocator().Allocate(size, alignof(Constraint)); return new (mem) Constraint(node, context, isDiscarded, locator, typeVars); diff --git a/lib/Sema/ConstraintLocator.cpp b/lib/Sema/ConstraintLocator.cpp index 6c5b9e137167b..7d97200b4abcb 100644 --- a/lib/Sema/ConstraintLocator.cpp +++ b/lib/Sema/ConstraintLocator.cpp @@ -101,6 +101,7 @@ unsigned LocatorPathElt::getNewSummaryFlags() const { case ConstraintLocator::PackExpansionPattern: case ConstraintLocator::PatternBindingElement: case ConstraintLocator::NamedPatternDecl: + case ConstraintLocator::SingleValueStmtBranch: case ConstraintLocator::AnyPatternDecl: return 0; @@ -381,6 +382,13 @@ void LocatorPathElt::dump(raw_ostream &out) const { break; } + case ConstraintLocator::SingleValueStmtBranch: { + auto branch = elt.castTo(); + out << "expr branch [" << branch.getExprBranchIndex() << "] of " + "single value stmt"; + break; + } + case ConstraintLocator::PatternMatch: out << "pattern match"; break; @@ -595,6 +603,40 @@ bool ConstraintLocator::isForMacroExpansion() const { return directlyAt(); } +bool ConstraintLocator::isForSingleValueStmtConjunction() const { + auto *SVE = getAsExpr(getAnchor()); + if (!SVE) + return false; + + // Ignore a trailing SyntacticElement path element for the statement. + auto path = getPath(); + if (auto elt = getLastElementAs()) { + if (elt->getElement() == ASTNode(SVE->getStmt())) + path = path.drop_back(); + } + + // Other than the trailing SyntaticElement, we must be at the anchor. + return path.empty(); +} + +bool ConstraintLocator::isForSingleValueStmtBranchInSingleExprClosure() const { + if (!isLastElement()) + return false; + + auto *SVE = getAsExpr(getAnchor()); + if (!SVE) + return false; + + auto *CE = dyn_cast(SVE->getDeclContext()); + if (!CE || !CE->hasSingleExpressionBody()) + return false; + + // Only consider implicit returns, explicit returns should be treated + // normally. + auto *RS = getAsStmt(CE->getBody()->getLastElement()); + return RS && RS->isImplicit(); +} + GenericTypeParamType *ConstraintLocator::getGenericParameter() const { // Check whether we have a path that terminates at a generic parameter. return isForGenericParameter() ? diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index fdcc3966010eb..03c338aedcb1f 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -5322,6 +5322,16 @@ void constraints::simplifyLocator(ASTNode &anchor, continue; } + case ConstraintLocator::SingleValueStmtBranch: { + auto branchElt = path[0].castTo(); + auto exprIdx = branchElt.getExprBranchIndex(); + auto *SVE = castToExpr(anchor); + SmallVector scratch; + anchor = SVE->getSingleExprBranches(scratch)[exprIdx]; + path = path.slice(1); + continue; + } + case ConstraintLocator::KeyPathDynamicMember: case ConstraintLocator::ImplicitDynamicMemberSubscript: { // Key path dynamic member lookup should be completely transparent. @@ -6544,6 +6554,8 @@ bool SolutionApplicationTarget::contextualTypeIsOnlyAHint() const { case CTP_ComposedPropertyWrapper: case CTP_CannotFail: case CTP_ExprPattern: + case CTP_SingleValueStmtBranch: + case CTP_SingleValueStmtBranchInSingleExprClosure: return false; } llvm_unreachable("invalid contextual type"); diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index 4b7f43e2bcc02..8d5b17ada4fab 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -101,6 +101,8 @@ bool BaseDiagnosticWalker::shouldWalkIntoDeclInClosureContext(Decl *D) { /// invalid positions. /// - Marker protocols cannot occur as the type of an as? or is expression. /// - KeyPath expressions cannot refer to effectful properties / subscripts +/// - SingleValueStmtExprs may only appear in certain places and has +/// restrictions on the control flow allowed. /// - Move expressions must have a declref expr subvalue. /// static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, @@ -3788,6 +3790,148 @@ void VarDeclUsageChecker::handleIfConfig(IfConfigDecl *ICD) { } } +namespace { +class SingleValueStmtUsageChecker final : public ASTWalker { + ASTContext &Ctx; + DiagnosticEngine &Diags; + llvm::DenseSet ValidSingleValueStmtExprs; + +public: + SingleValueStmtUsageChecker(ASTContext &ctx) : Ctx(ctx), Diags(ctx.Diags) {} + +private: + /// Mark a given expression as a valid position for a SingleValueStmtExpr. + void markValidSingleValueStmt(Expr *E) { + if (!E) + return; + + while (true) { + // Look through implicit conversions. + if (auto *ICE = dyn_cast(E)) { + E = ICE->getSubExpr(); + continue; + } + // Look through coercions. + if (auto *CE = dyn_cast(E)) { + E = CE->getSubExpr(); + continue; + } + // Look through try/await. We'll warn on these during effect checking. + if (auto *TE = dyn_cast(E)) { + E = TE->getSubExpr(); + continue; + } + if (auto *AE = dyn_cast(E)) { + E = AE->getSubExpr(); + continue; + } + break; + } + if (auto *SVE = dyn_cast(E)) + ValidSingleValueStmtExprs.insert(SVE); + } + + PreWalkResult walkToExprPre(Expr *E) override { + if (auto *SVE = dyn_cast(E)) { + // Diagnose a SingleValueStmtExpr in a context that we do not currently + // support. + if (!ValidSingleValueStmtExprs.contains(SVE)) { + Diags.diagnose(SVE->getLoc(), diag::single_value_stmt_out_of_place, + SVE->getStmt()->getKind()); + } + + // Nested SingleValueStmtExprs are allowed. + SmallVector scratch; + for (auto *branch : SVE->getSingleExprBranches(scratch)) + markValidSingleValueStmt(branch); + + // 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()) { + Diags.diagnose(branch->getEndLoc(), + diag::single_value_stmt_branch_must_end_in_throw, + S->getKind()); + } + break; + } + case IsSingleValueStmtResult::Kind::NonExhaustiveIf: { + Diags.diagnose(S->getStartLoc(), + diag::if_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::NoExpressionBranches: + // 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, as long as it's not a nested + // expression (as otherwise this would be effectively allowing it in an + // arbitrary expression position). + if (auto *AE = dyn_cast(E)) { + if (!Parent.getAsExpr()) + markValidSingleValueStmt(AE->getSrc()); + } + + return Action::Continue(E); + } + + PreWalkResult walkToStmtPre(Stmt *S) override { + // Valid in a return/throw. + if (auto *RS = dyn_cast(S)) { + if (RS->hasResult()) + markValidSingleValueStmt(RS->getResult()); + } + if (auto *TS = dyn_cast(S)) + markValidSingleValueStmt(TS->getSubExpr()); + + 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(); + } +}; +} // end anonymous namespace + /// Apply the warnings managed by VarDeclUsageChecker to the top level /// code declarations that haven't been checked yet. void swift:: @@ -3795,6 +3939,8 @@ 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. @@ -3802,14 +3948,18 @@ void swift::performAbstractFuncDeclDiagnostics(AbstractFunctionDecl *AFD) { // Don't produce these diagnostics for implicitly generated code. if (AFD->getLoc().isInvalid() || AFD->isImplicit() || AFD->isInvalid()) return; - - // Check for unused variables, as well as variables that are could be - // declared as constants. Skip local functions though, since they will - // be checked as part of their parent function or TopLevelCodeDecl. + if (!AFD->getDeclContext()->isLocalContext()) { + // Check for unused variables, as well as variables that are could be + // declared as constants. Skip local functions though, since they will + // be checked as part of their parent function or TopLevelCodeDecl. 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(); diff --git a/lib/Sema/PreCheckExpr.cpp b/lib/Sema/PreCheckExpr.cpp index 54917168159b7..9a53e3c59204a 100644 --- a/lib/Sema/PreCheckExpr.cpp +++ b/lib/Sema/PreCheckExpr.cpp @@ -930,6 +930,9 @@ namespace { /// The current number of nested \c SequenceExprs that we're within. unsigned SequenceExprDepth = 0; + /// The current number of nested \c SingleValueStmtExprs that we're within. + unsigned SingleValueStmtExprDepth = 0; + /// Simplify expressions which are type sugar productions that got parsed /// as expressions due to the parser not knowing which identifiers are /// type names. @@ -1049,6 +1052,13 @@ namespace { if (auto closure = dyn_cast(expr)) return finish(walkToClosureExprPre(closure), expr); + if (auto *SVE = dyn_cast(expr)) { + // Record the scope of a single value stmt expr, as we want to skip + // pre-checking of any patterns, similar to closures. + SingleValueStmtExprDepth += 1; + return finish(true, expr); + } + if (auto unresolved = dyn_cast(expr)) { TypeChecker::checkForForbiddenPrefix( getASTContext(), unresolved->getName().getBaseName()); @@ -1157,6 +1167,10 @@ namespace { DC = ce->getParent(); } + // Restore the depth for the single value stmt counter. + if (isa(expr)) + SingleValueStmtExprDepth -= 1; + // A 'self.init' or 'super.init' application inside a constructor will // evaluate to void, with the initializer's result implicitly rebound // to 'self'. Recognize the unresolved constructor expression and @@ -1300,6 +1314,17 @@ namespace { } PreWalkResult walkToStmtPre(Stmt *stmt) override { + if (auto *RS = dyn_cast(stmt)) { + // Pre-check a return statement, which includes potentially turning it + // into a FailStmt. + auto &eval = Ctx.evaluator; + auto *S = evaluateOrDefault(eval, PreCheckReturnStmtRequest{RS, DC}, + nullptr); + if (!S) + return Action::Stop(); + + return Action::Continue(S); + } return Action::Continue(stmt); } @@ -1309,9 +1334,10 @@ namespace { PreWalkResult walkToPatternPre(Pattern *pattern) override { // Constraint generation is responsible for pattern verification and - // type-checking in the body of the closure, so there is no need to - // walk into patterns. - return Action::SkipChildrenIf(isa(DC), pattern); + // type-checking in the body of the closure and single value stmt expr, + // so there is no need to walk into patterns. + return Action::SkipChildrenIf( + isa(DC) || SingleValueStmtExprDepth > 0, pattern); } }; } // end anonymous namespace @@ -1331,6 +1357,22 @@ bool PreCheckExpression::walkToClosureExprPre(ClosureExpr *closure) { closure->getParent()->isChildContextOf(DC)) && "Decl context isn't correct"); DC = closure; + + // If we have a single statement that can become an expression, turn it + // into an expression now. + auto *body = closure->getBody(); + if (body->getNumElements() == 1) { + if (auto *S = body->getLastElement().dyn_cast()) { + if (Ctx.LangOpts.hasFeature(Feature::StatementExpressions) && + S->mayProduceSingleValue(Ctx)) { + auto *SVE = SingleValueStmtExpr::createWithWrappedBranches( + Ctx, S, DC, /*mustBeExpr*/ false); + auto *RS = new (Ctx) ReturnStmt(SourceLoc(), SVE); + body->setLastElement(RS); + closure->setBody(body, /*isSingleExpression*/ true); + } + } + } return true; } diff --git a/lib/Sema/TypeCheckEffects.cpp b/lib/Sema/TypeCheckEffects.cpp index 5fce49a3955ff..f19c47c4def0f 100644 --- a/lib/Sema/TypeCheckEffects.cpp +++ b/lib/Sema/TypeCheckEffects.cpp @@ -2720,11 +2720,12 @@ class CheckEffectsCoverage : public EffectsHandlingWalker // course we're in a context that could never handle an 'async'. Then, we // produce an error. if (!Flags.has(ContextFlags::HasAnyAsyncSite)) { - if (CurContext.handlesAsync(ConditionalEffectKind::Conditional)) - Ctx.Diags.diagnose(E->getAwaitLoc(), diag::no_async_in_await); - else + if (CurContext.handlesAsync(ConditionalEffectKind::Conditional)) { + diagnoseRedundantAwait(E); + } else { CurContext.diagnoseUnhandledAsyncSite(Ctx.Diags, E, None, /*forAwait=*/ true); + } } // Inform the parent of the walk that an 'await' exists here. @@ -2742,7 +2743,7 @@ class CheckEffectsCoverage : public EffectsHandlingWalker // Warn about 'try' expressions that weren't actually needed. if (!Flags.has(ContextFlags::HasTryThrowSite)) { if (!E->isImplicit()) - Ctx.Diags.diagnose(E->getTryLoc(), diag::no_throw_in_try); + diagnoseRedundantTry(E); // Diagnose all the call sites within a single unhandled 'try' // at the same time. @@ -2762,9 +2763,8 @@ class CheckEffectsCoverage : public EffectsHandlingWalker E->getSubExpr()->walk(*this); // Warn about 'try' expressions that weren't actually needed. - if (!Flags.has(ContextFlags::HasTryThrowSite)) { - Ctx.Diags.diagnose(E->getLoc(), diag::no_throw_in_try); - } + if (!Flags.has(ContextFlags::HasTryThrowSite)) + diagnoseRedundantTry(E); scope.preserveCoverageFromOptionalOrForcedTryOperand(); return ShouldNotRecurse; @@ -2778,9 +2778,8 @@ class CheckEffectsCoverage : public EffectsHandlingWalker E->getSubExpr()->walk(*this); // Warn about 'try' expressions that weren't actually needed. - if (!Flags.has(ContextFlags::HasTryThrowSite)) { - Ctx.Diags.diagnose(E->getLoc(), diag::no_throw_in_try); - } + if (!Flags.has(ContextFlags::HasTryThrowSite)) + diagnoseRedundantTry(E); scope.preserveCoverageFromOptionalOrForcedTryOperand(); return ShouldNotRecurse; @@ -2818,6 +2817,32 @@ class CheckEffectsCoverage : public EffectsHandlingWalker return ShouldRecurse; } + void diagnoseRedundantTry(AnyTryExpr *E) const { + auto *semanticExpr = E->getSemanticsProvidingExpr(); + if (auto *SVE = dyn_cast(semanticExpr)) { + // For an if/switch expression, produce a custom diagnostic. + Ctx.Diags.diagnose(E->getTryLoc(), + diag::redundant_effect_marker_on_single_value_stmt, + "try", SVE->getStmt()->getKind()) + .highlight(E->getTryLoc()); + return; + } + Ctx.Diags.diagnose(E->getTryLoc(), diag::no_throw_in_try); + } + + void diagnoseRedundantAwait(AwaitExpr *E) const { + auto *semanticExpr = E->getSemanticsProvidingExpr(); + if (auto *SVE = dyn_cast(semanticExpr)) { + // For an if/switch expression, produce a custom diagnostic. + Ctx.Diags.diagnose(E->getAwaitLoc(), + diag::redundant_effect_marker_on_single_value_stmt, + "await", SVE->getStmt()->getKind()) + .highlight(E->getAwaitLoc()); + return; + } + Ctx.Diags.diagnose(E->getAwaitLoc(), diag::no_async_in_await); + } + void diagnoseUncoveredAsyncSite(const Expr *anchor) const { auto asyncPointIter = uncoveredAsync.find(anchor); if (asyncPointIter == uncoveredAsync.end()) diff --git a/lib/Sema/TypeCheckStmt.cpp b/lib/Sema/TypeCheckStmt.cpp index aef0c7638f78b..5325bace162a8 100644 --- a/lib/Sema/TypeCheckStmt.cpp +++ b/lib/Sema/TypeCheckStmt.cpp @@ -1004,20 +1004,20 @@ class StmtChecker : public StmtVisitor { Stmt *visitBraceStmt(BraceStmt *BS); Stmt *visitReturnStmt(ReturnStmt *RS) { - auto TheFunc = AnyFunctionRef::fromDeclContext(DC); + // First, let's do a pre-check, and bail if the return is completely + // invalid. + auto &eval = getASTContext().evaluator; + auto *S = + evaluateOrDefault(eval, PreCheckReturnStmtRequest{RS, DC}, nullptr); + + // We do a cast here as it may have been turned into a FailStmt. We should + // return that without doing anything else. + RS = dyn_cast_or_null(S); + if (!RS) + return S; - if (!TheFunc.has_value()) { - getASTContext().Diags.diagnose(RS->getReturnLoc(), - diag::return_invalid_outside_func); - return nullptr; - } - - // If the return is in a defer, then it isn't valid either. - if (isInDefer()) { - getASTContext().Diags.diagnose(RS->getReturnLoc(), - diag::jump_out_of_defer, "return"); - return nullptr; - } + auto TheFunc = AnyFunctionRef::fromDeclContext(DC); + assert(TheFunc && "Should have bailed from pre-check if this is None"); Type ResultTy = TheFunc->getBodyResultType(); if (!ResultTy || ResultTy->hasError()) @@ -1055,40 +1055,6 @@ class StmtChecker : public StmtVisitor { } Expr *E = RS->getResult(); - - // In an initializer, the only expression allowed is "nil", which indicates - // failure from a failable initializer. - if (auto ctor = dyn_cast_or_null( - TheFunc->getAbstractFunctionDecl())) { - // The only valid return expression in an initializer is the literal - // 'nil'. - auto nilExpr = dyn_cast(E->getSemanticsProvidingExpr()); - if (!nilExpr) { - getASTContext().Diags.diagnose(RS->getReturnLoc(), - diag::return_init_non_nil) - .highlight(E->getSourceRange()); - RS->setResult(nullptr); - return RS; - } - - // "return nil" is only permitted in a failable initializer. - if (!ctor->isFailable()) { - getASTContext().Diags.diagnose(RS->getReturnLoc(), - diag::return_non_failable_init) - .highlight(E->getSourceRange()); - getASTContext().Diags.diagnose(ctor->getLoc(), diag::make_init_failable, - ctor->getName()) - .fixItInsertAfter(ctor->getLoc(), "?"); - RS->setResult(nullptr); - return RS; - } - - // Replace the "return nil" with a new 'fail' statement. - return new (getASTContext()) FailStmt(RS->getReturnLoc(), - nilExpr->getLoc(), - RS->isImplicit()); - } - TypeCheckExprOptions options = {}; if (LeaveBraceStmtBodyUnchecked) { @@ -1541,6 +1507,62 @@ class StmtChecker : public StmtVisitor { }; } // end anonymous namespace +Stmt *PreCheckReturnStmtRequest::evaluate(Evaluator &evaluator, ReturnStmt *RS, + DeclContext *DC) const { + auto &ctx = DC->getASTContext(); + auto fn = AnyFunctionRef::fromDeclContext(DC); + + // Not valid outside of a function. + if (!fn) { + ctx.Diags.diagnose(RS->getReturnLoc(), diag::return_invalid_outside_func); + return nullptr; + } + + // If the return is in a defer, then it isn't valid either. + if (isDefer(DC)) { + ctx.Diags.diagnose(RS->getReturnLoc(), diag::jump_out_of_defer, "return"); + return nullptr; + } + + // The rest of the checks only concern return statements with results. + if (!RS->hasResult()) + return RS; + + auto *E = RS->getResult(); + + // In an initializer, the only expression allowed is "nil", which indicates + // failure from a failable initializer. + if (auto *ctor = + dyn_cast_or_null(fn->getAbstractFunctionDecl())) { + + // The only valid return expression in an initializer is the literal + // 'nil'. + auto *nilExpr = dyn_cast(E->getSemanticsProvidingExpr()); + if (!nilExpr) { + ctx.Diags.diagnose(RS->getReturnLoc(), diag::return_init_non_nil) + .highlight(E->getSourceRange()); + RS->setResult(nullptr); + return RS; + } + + // "return nil" is only permitted in a failable initializer. + if (!ctor->isFailable()) { + ctx.Diags.diagnose(RS->getReturnLoc(), diag::return_non_failable_init) + .highlight(E->getSourceRange()); + ctx.Diags + .diagnose(ctor->getLoc(), diag::make_init_failable, ctor->getName()) + .fixItInsertAfter(ctor->getLoc(), "?"); + RS->setResult(nullptr); + return RS; + } + + // Replace the "return nil" with a new 'fail' statement. + return new (ctx) + FailStmt(RS->getReturnLoc(), nilExpr->getLoc(), RS->isImplicit()); + } + return RS; +} + static bool isDiscardableType(Type type) { return (type->hasError() || type->isUninhabited() || @@ -2400,8 +2422,23 @@ TypeCheckFunctionBodyRequest::evaluate(Evaluator &evaluator, } else if (func->hasSingleExpressionBody() && func->getResultInterfaceType()->isVoid()) { // The function returns void. We don't need an explicit return, no matter - // what the type of the expression is. Take the inserted return back out. + // what the type of the expression is. Take the inserted return back out. body->setLastElement(func->getSingleExpressionBody()); + } else if (func->getBody()->getNumElements() == 1 && + !func->getResultInterfaceType()->isVoid()) { + // If there is a single statement in the body that can be turned into a + // single expression return, do so now. + if (auto *S = func->getBody()->getLastElement().dyn_cast()) { + if (ctx.LangOpts.hasFeature(Feature::StatementExpressions) && + S->mayProduceSingleValue(evaluator)) { + auto *SVE = SingleValueStmtExpr::createWithWrappedBranches( + ctx, S, /*DC*/ func, /*mustBeExpr*/ false); + auto *RS = new (ctx) ReturnStmt(SourceLoc(), SVE); + body->setLastElement(RS); + func->setHasSingleExpressionBody(); + func->setSingleExpressionBody(SVE); + } + } } } else if (auto *ctor = dyn_cast(AFD)) { // If this is user-defined constructor that requires `_storage` @@ -2528,6 +2565,145 @@ void TypeChecker::typeCheckTopLevelCodeDecl(TopLevelCodeDecl *TLCD) { performTopLevelDeclDiagnostics(TLCD); } +/// Whether the given brace statement ends with a throw. +static bool doesBraceEndWithThrow(BraceStmt *BS) { + auto elts = BS->getElements(); + if (elts.empty()) + return false; + + auto lastElt = elts.back(); + auto *S = lastElt.dyn_cast(); + if (!S) + return false; + + return isa(S); +} + +namespace { +/// An ASTWalker that searches for any break/continue statements that jump out +/// of the context the walker starts at. +class JumpOutOfContextFinder : public ASTWalker { + TinyPtrVector &Jumps; + SmallPtrSet ParentLabeledStmts; + +public: + JumpOutOfContextFinder(TinyPtrVector &jumps) : Jumps(jumps) {} + + PreWalkResult walkToStmtPre(Stmt *S) override { + if (auto *LS = dyn_cast(S)) + ParentLabeledStmts.insert(LS); + + // Cannot 'break', 'continue', or 'return' out of the statement. A jump to + // a statement within a branch however is fine. + if (auto *BS = dyn_cast(S)) { + if (!ParentLabeledStmts.contains(BS->getTarget())) + Jumps.push_back(BS); + } + if (auto *CS = dyn_cast(S)) { + if (!ParentLabeledStmts.contains(CS->getTarget())) + Jumps.push_back(CS); + } + if (isa(S) || isa(S)) + Jumps.push_back(S); + + return Action::Continue(S); + } + PostWalkResult walkToStmtPost(Stmt *S) override { + if (auto *LS = dyn_cast(S)) { + auto removed = ParentLabeledStmts.erase(LS); + assert(removed); + (void)removed; + } + return Action::Continue(S); + } + + PreWalkResult walkToExprPre(Expr *E) override { + // We don't need to walk into closures, you can't jump out of them. + return Action::SkipChildrenIf(isa(E), E); + } + PreWalkAction walkToDeclPre(Decl *D) override { + // We don't need to walk into any nested local decls. + return Action::VisitChildrenIf(isa(D)); + } +}; +} // end anonymous namespace + +IsSingleValueStmtResult +areBranchesValidForSingleValueStmt(Evaluator &eval, ArrayRef branches) { + TinyPtrVector invalidJumps; + TinyPtrVector unterminatedBranches; + JumpOutOfContextFinder jumpFinder(invalidJumps); + + // Must have a single expression brace, and non-single-expression branches + // must end with a throw. + bool hadSingleExpr = false; + for (auto *branch : branches) { + auto *BS = dyn_cast(branch); + if (!BS) + return IsSingleValueStmtResult::unhandledStmt(); + + // Check to see if there are any invalid jumps. + BS->walk(jumpFinder); + + if (BS->getSingleExpressionElement()) { + hadSingleExpr = true; + continue; + } + + // We also allow single value statement branches, which we can wrap in + // a SingleValueStmtExpr. + auto elts = BS->getElements(); + if (elts.size() == 1) { + if (auto *S = elts.back().dyn_cast()) { + if (S->mayProduceSingleValue(eval)) { + hadSingleExpr = true; + continue; + } + } + } + if (!doesBraceEndWithThrow(BS)) + unterminatedBranches.push_back(BS); + } + + if (!invalidJumps.empty()) + return IsSingleValueStmtResult::invalidJumps(std::move(invalidJumps)); + + if (!unterminatedBranches.empty()) { + return IsSingleValueStmtResult::unterminatedBranches( + std::move(unterminatedBranches)); + } + + if (!hadSingleExpr) + return IsSingleValueStmtResult::noExpressionBranches(); + + return IsSingleValueStmtResult::valid(); +} + +IsSingleValueStmtResult +IsSingleValueStmtRequest::evaluate(Evaluator &eval, const Stmt *S) const { + if (!isa(S) && !isa(S)) + return IsSingleValueStmtResult::unhandledStmt(); + + // Statements must be unlabeled. + auto *LS = cast(S); + if (LS->getLabelInfo()) + return IsSingleValueStmtResult::hasLabel(); + + if (auto *IS = dyn_cast(S)) { + // Must be exhaustive. + if (!IS->isSyntacticallyExhaustive()) + return IsSingleValueStmtResult::nonExhaustiveIf(); + + SmallVector scratch; + return areBranchesValidForSingleValueStmt(eval, IS->getBranches(scratch)); + } + if (auto *SS = dyn_cast(S)) { + SmallVector scratch; + return areBranchesValidForSingleValueStmt(eval, SS->getBranches(scratch)); + } + llvm_unreachable("Unhandled case"); +} + void swift::checkUnknownAttrRestrictions( ASTContext &ctx, CaseStmt *caseBlock, bool &limitExhaustivityChecks) { diff --git a/test/ASTGen/verify-parse.swift b/test/ASTGen/verify-parse.swift index b0f81367b2cdf..ec5004eb12972 100644 --- a/test/ASTGen/verify-parse.swift +++ b/test/ASTGen/verify-parse.swift @@ -24,3 +24,16 @@ func test3(y: Int) -> Int { let x = y return x } + +func test4(_ b: Bool) -> Int { + if b { 0 } else { 1 } +} + +func test5(_ b: Bool) -> Int { + return if b { 0 } else { 1 } +} + +func test6(_ b: Bool) -> Int { + let x = if b { 0 } else { 1 } + return x +} diff --git a/test/Constraints/closures.swift b/test/Constraints/closures.swift index 8cb972ed32d73..5e474a26eae3f 100644 --- a/test/Constraints/closures.swift +++ b/test/Constraints/closures.swift @@ -1149,9 +1149,11 @@ 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 {{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 {{expected expression in conditional}} + // 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 {{expected '{' after 'if' condition}} } diff --git a/test/Constraints/if_expr.swift b/test/Constraints/if_expr.swift new file mode 100644 index 0000000000000..c3021e763fdb7 --- /dev/null +++ b/test/Constraints/if_expr.swift @@ -0,0 +1,434 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-feature StatementExpressions + +enum E { + case e + case f + case g(Int) +} + +func testDotSyntax1() -> E { + if .random() { .e } else { .f } +} +func testDotSyntax2() -> E? { + if .random() { .e } else { .f } +} +func testDotSyntax3() -> E? { + if .random() { .e } else { .none } +} +func testDotSyntax4() -> Int { + let i = if .random() { 0 } else { .random() } + // expected-error@-1 {{cannot infer contextual base in reference to member 'random'}} + + return i +} + +let testVar1: E = if .random() { .e } else { .f } +let testVar2: E? = if .random() { .e } else { .f } +let testVar3: E? = if .random() { .e } else { .none } +let testVar4: E? = if .random() { nil } else { .e } + +let testVar5 = if .random() { 0 } else { 1.0 } +// expected-error@-1 {{branches have mismatching types 'Int' and 'Double'}} + +let testVar6: Double = if .random() { 0 } else { 1.0 } + +let testVar7: Double = if .random() { 0 + 1 } else if .random() { 1.0 + 3 } else { 9 + 0.0 } + +let testContextualMismatch1: String = if .random() { 1 } else { "" } +// expected-error@-1 {{cannot convert value of type 'Int' to specified type 'String'}} + +let testContextualMismatch2: String = if .random() { 1 } else { 2 } +// expected-error@-1 {{cannot convert value of type 'Int' to specified type 'String'}} + +let testMismatch1 = if .random() { 1 } else if .random() { "" } else { 0.0 } +// expected-error@-1 {{branches have mismatching types 'Int' and 'String'}} +// expected-error@-2 {{branches have mismatching types 'Double' and 'String'}} + +func testMismatch2() -> Double { + let x = if .random() { + 0 + } else if .random() { + 0 + } else { + 1.0 // expected-error {{branches have mismatching types 'Double' and 'Int'}} + } + return x +} +func testOptionalBinding1(_ x: Int?) -> Int { + if let x = x { x } else { 0 } +} +func testOptionalBinding2(_ x: Int?, _ y: Int?) -> Int { + if let x = x, let y = y { x + y } else { 0 } +} +func testPatternBinding2(_ e: E) -> Int { + if case .g(let i) = e { i } else { 0 } +} + +func testTernary() -> Int { + if .random() { .random() ? 1 : 0 } else { .random() ? 3 : 2 } +} + +func testReturn() -> Int { + if .random() { + return 0 + } else { + 1 // expected-warning {{integer literal is unused}} + } +} + +func testNeverConversion1() -> Int { + if .random() { + 1 + } else { + fatalError() + } +} + +func testNeverConversion2() -> Int { + return if .random() { + 1 + } else { + fatalError() + } +} + +func testNeverConversion3() -> Int { + if .random() { + 1 + } else { + if .random() { + fatalError() + } else { + 2 + } + } +} + +func testNeverConversion4() -> Int { + return if .random() { + 1 + } else { + if .random() { + fatalError() + } else { + 2 + } + } +} + +func testNeverConversion5() -> Int { + { + if .random() { + 1 + } else { + if .random() { + fatalError() + } else { + 2 + } + } + }() +} + +func testVoidConversion() { + func foo(_ fn: () -> Void) {} + func bar(_ fn: () -> T) {} + + // Okay for an implicit return, including nested as this preserves source + // compatibility. + foo { + if .random() { + 0 // expected-warning {{integer literal is unused}} + } else { + 0 // expected-warning {{integer literal is unused}} + } + } + foo { + if .random() { + 0 // expected-warning {{integer literal is unused}} + } else { + if .random() { + 0 // expected-warning {{integer literal is unused}} + } else { + 0 // expected-warning {{integer literal is unused}} + } + } + } + bar { + if .random() { + 0 + } else { + 0 + } + } + bar { + if .random() { + 0 + } else { + if .random() { + 0 + } else { + 0 + } + } + } + bar { + if .random() { + () + } else { + 0 // expected-warning {{integer literal is unused}} + } + } + bar { + if .random() { + 0 // expected-warning {{integer literal is unused}} + } else { + if .random() { + () + } else { + 0 // expected-warning {{integer literal is unused}} + } + } + } + + // Not okay for an explicit return. + foo { + return if .random() { + 0 // expected-error {{cannot convert value of type 'Int' to specified type 'Void'}} + } else { + 0 + } + } + foo { + return if .random() { + 0 // expected-error {{cannot convert value of type 'Int' to specified type 'Void'}} + } else { + if .random() { + 0 + } else { + 0 + } + } + } + bar { + return if .random() { + 0 + } else { + 0 + } + } + bar { + return if .random() { + 0 + } else { + if .random() { + 0 + } else { + 0 + } + } + } + bar { + return if .random() { + () // expected-error {{branches have mismatching types '()' and 'Int'}} + } else { + 0 + } + } + bar { + return if .random() { + 0 + } else { + if .random() { + () // expected-error {{branches have mismatching types '()' and 'Int'}} + } else { + 0 + } + } + } +} + +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 + } +} + +func testOptionalGeneric() { + func bar(_ fn: () -> T?) -> T? { fn() } + bar { + if .random() { + () + } else { + () + } + } +} + +func testNestedOptional() -> Int? { + if .random() { + 1 + } else { + if .random() { + 1 + } else { + nil + } + } +} + +let neverVar = if .random() { fatalError() } else { fatalError() } +// expected-warning@-1 {{constant 'neverVar' inferred to have type 'Never'}} +// expected-note@-2 {{add an explicit type annotation to silence this warning}} + +func testNonVoidToVoid() { + if .random() { 0 } else { 1 } // expected-warning 2{{integer literal is unused}} +} + +func uninferableNil() { + let _ = if .random() { nil } else { 2.0 } // expected-error {{'nil' requires a contextual type}} +} + +func testAssignment() { + var d: Double = if .random() { 0 } else { 1.0 } + d = if .random() { 0 } else { 1 } + _ = d +} + +struct TestBadReturn { + var y = if .random() { return } else { 0 } // expected-error {{return invalid outside of a func}} +} + +struct SomeError: Error {} + +func testThrowInference() { + func notThrowing(_ fn: () -> Int) {} + notThrowing { // expected-error {{invalid conversion from throwing function of type '() throws -> Int' to non-throwing function type '() -> Int'}} + if .random() { + 0 + } else { + throw SomeError() + } + } + + @discardableResult + func rethrowing(_ fn: () throws -> T) rethrows -> T { try fn() } + rethrowing { + // expected-error@-1 {{call can throw, but it is not marked with 'try' and the error is not handled}} + // expected-note@-2 {{call is to 'rethrows' function, but argument function can throw}} + if .random() { + 0 + } else { + throw SomeError() + } + } +} + +// MARK: Subtyping + +class A {} +class B : A {} +class C : A {} + +func testSubtyping1() -> A { + // We can join to A. + let x = if .random() { B() } else { C() } + let y = .random() ? B() : C() + if .random() { + return x + } else { + return y + } +} + +// MARK: Opaque result types + +protocol P {} +extension Int : P {} + +@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) +func testOpaqueReturn1() -> some P { + if .random() { + 0 + } else { + 1 + } +} + +@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) +func testOpaqueReturn2() -> some P { + if .random() { + 0 + } else { + fatalError() + } +} + +// MARK: Result builders + +enum Either { + case first(T), second(U) +} + +@resultBuilder +struct Builder { + static func buildBlock(_ x: T) -> T { x } + static func buildBlock(_ x: T, _ y: U) -> (T, U) { (x, y) } + + static func buildEither(first x: T) -> Either { .first(x) } + static func buildEither(second x: U) -> Either { .second(x) } + + static func buildExpression(_ x: Double) -> Double { x } + static func buildExpression(_ x: T) -> T { x } +} + +@Builder +func singleExprBuilder() -> Either { + if .random() { + "" + } else { + 1 + } +} + +@Builder +func builderStaticMember() -> (Either, Double) { + if .random() { + "" + } else { + 1 + } + .pi // This becomes a static member ref, not a member on an if expression. +} + +@Builder +func builderNotPostfix() -> (Either, Bool) { + if .random() { "" } else { 1 } !.random() // expected-error {{consecutive statements on a line must be separated by ';'}} +} + +@Builder +func builderWithBinding() -> Either { + // 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 builderInClosure() { + func build(@Builder _ fn: () -> Either) {} + build { + if .random() { + "" + } else { + 1 + } + } +} diff --git a/test/Constraints/switch_expr.swift b/test/Constraints/switch_expr.swift new file mode 100644 index 0000000000000..8658ea5480f38 --- /dev/null +++ b/test/Constraints/switch_expr.swift @@ -0,0 +1,529 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-feature StatementExpressions + +enum E { + case e + case f + case g(Int) +} + +func testDotSyntax1() -> E { + switch Bool.random() { case true: .e case false: .f } +} +func testDotSyntax2() -> E? { + switch Bool.random() { case true: .e case false: .f } +} +func testDotSyntax3() -> E? { + switch Bool.random() { case true: .e case false: .none } +} +func testDotSyntax4() -> Int { + let i = switch Bool.random() { case true: 0 case false: .random() } + // expected-error@-1 {{cannot infer contextual base in reference to member 'random'}} + + return i +} + +let testVar1: E = switch Bool.random() { case true: .e case false: .f } +let testVar2: E? = switch Bool.random() { case true: .e case false: .f } +let testVar3: E? = switch Bool.random() { case true: .e case false: .none } +let testVar4: E? = switch Bool.random() { case true: nil case false: .e } + +let testVar5 = switch Bool.random() { case true: 0 case false: 1.0 } +// expected-error@-1 {{branches have mismatching types 'Int' and 'Double'}} + +let testVar6: Double = switch Bool.random() { case true: 0 case false: 1.0 } + +let testVar7: Double = switch 0 { + case 1: 0 + 1 + case 2: 1.0 + 3 + default: 9 + 0.0 +} + +let testContextualMismatch1: String = switch Bool.random() { case true: 1 case false: "" } +// expected-error@-1 {{cannot convert value of type 'Int' to specified type 'String'}} + +let testContextualMismatch2: String = switch Bool.random() { case true: 1 case false: 2 } +// expected-error@-1 {{cannot convert value of type 'Int' to specified type 'String'}} + +func proposalExample1(_ x: Int) -> Float { + let y = switch x { + case 0..<0x80: 1 // expected-error {{branches have mismatching types 'Int' and 'Double'}} + case 0x80..<0x0800: 2.0 + case 0x0800..<0x1_0000: 3.0 + default: 4.5 + } + return y +} + +func proposalExample2(_ x: Int) -> Float { + let y: Float = switch x { + case 0..<0x80: 1 + case 0x80..<0x0800: 2.0 + case 0x0800..<0x1_0000: 3.0 + default: 4.5 + } + return y +} + +enum Node { case B, R } + +enum Tree { + indirect case node(Node, Tree, Tree, Tree) + case leaf + + func proposalExample3(_ z: Tree, d: Tree) -> Tree { + switch self { + case let .node(.B, .node(.R, .node(.R, a, x, b), y, c), z, d): + .node(.R, .node(.B,a,x,b),y,.node(.B,c,z,d)) + case let .node(.B, .node(.R, a, x, .node(.R, b, y, c)), z, d): + .node(.R, .node(.B,a,x,b),y,.node(.B,c,z,d)) + case let .node(.B, a, x, .node(.R, .node(.R, b, y, c), z, d)): + .node(.R, .node(.B,a,x,b),y,.node(.B,c,z,d)) + case let .node(.B, a, x, .node(.R, b, y, .node(.R, c, z, d))): + .node(.R, .node(.B,a,x,b),y,.node(.B,c,z,d)) + default: + self + } + } +} + +enum F { + case a(Int) +} + +func overloadedWithGenericAndInt(_ x: T) -> T { x } +func overloadedWithGenericAndInt(_ x: Int) -> Int { x } + +struct S { + var f: F + mutating func foo() -> Int { + switch f { + case .a(let x): + // Make sure we don't try and shrink, which would lead to trying to + // type-check the switch early. + overloadedWithGenericAndInt(x + x) + } + } +} + +func testSingleCaseReturn(_ f: F) -> Int { + switch f { + case .a(let i): i + } +} + +func testSingleCaseReturnClosure(_ f: F) -> Int { + let fn = { + switch f { + case .a(let i): i + } + } + return fn() +} + +func testWhereClause(_ f: F) -> Int { + switch f { + case let .a(x) where x.isMultiple(of: 2): + return 0 + default: + return 1 + } +} + +func testNestedOptional() -> Int? { + switch Bool.random() { + case true: + 1 + case false: + if .random() { + 1 + } else { + nil + } + } +} + +func testNestedOptionalSwitch() -> Int? { + switch Bool.random() { + case true: + 1 + case false: + switch Bool.random() { + case true: + 1 + case false: + nil + } + } +} + +func testNestedOptionalMismatch1() -> Int? { + switch Bool.random() { + case true: + 1 + case false: + if .random() { + 1 + } else { + "" // expected-error {{cannot convert value of type 'String' to specified type 'Int'}} + } + } +} + + +func testNestedOptionalMismatch2() -> Int { + switch Bool.random() { + case true: + 1 + case false: + if .random() { + 1 + } else { + // FIXME: Seems like we could do better here + nil // expected-error {{cannot convert value of type 'ExpressibleByNilLiteral' to specified type 'Int'}} + } + } +} + +func testAssignment() { + var d: Double = switch Bool.random() { case true: 0 case false: 1.0 } + d = switch Bool.random() { case true: 0 case false: 1 } + _ = d +} + +struct TestBadReturn { + var y = switch Bool.random() { case true: return case false: 0 } // expected-error {{return invalid outside of a func}} +} + +func testNeverConversion1() -> Int { + switch Bool.random() { + case true: + 1 + case false: + fatalError() + } +} + +func testNeverConversion2() -> Int { + return switch Bool.random() { + case true: + 1 + case false: + fatalError() + } +} + +func testNeverConversion3() -> Int { + switch Bool.random() { + case true: + 1 + case false: + if .random() { + fatalError() + } else { + 2 + } + } +} + +func testNeverConversion4() -> Int { + return switch Bool.random() { + case true: + 1 + case false: + if .random() { + fatalError() + } else { + 2 + } + } +} + +func testNeverConversion5() -> Int { + { + switch Bool.random() { + case true: + 1 + case false: + if .random() { + fatalError() + } else { + 2 + } + } + }() +} + +func testVoidConversion() { + func foo(_ fn: () -> Void) {} + func bar(_ fn: () -> T) {} + + // Okay for an implicit return, including nested as this preserves source + // compatibility. + foo { + switch Bool.random() { + case true: + 0 // expected-warning {{integer literal is unused}} + case false: + 0 // expected-warning {{integer literal is unused}} + } + } + foo { + switch Bool.random() { + case true: + 0 // expected-warning {{integer literal is unused}} + case false: + if .random() { + 0 // expected-warning {{integer literal is unused}} + } else { + 0 // expected-warning {{integer literal is unused}} + } + } + } + bar { + switch Bool.random() { + case true: + 0 + case false: + 0 + } + } + bar { + switch Bool.random() { + case true: + 0 + case false: + if .random() { + 0 + } else { + 0 + } + } + } + bar { + switch Bool.random() { + case true: + () + case false: + 0 // expected-warning {{integer literal is unused}} + } + } + bar { + switch Bool.random() { + case true: + 0 // expected-warning {{integer literal is unused}} + case false: + if .random() { + () + } else { + 0 // expected-warning {{integer literal is unused}} + } + } + } + + // Not okay for an explicit return. + foo { + return switch Bool.random() { + case true: + 0 // expected-error {{cannot convert value of type 'Int' to specified type 'Void'}} + case false: + 0 + } + } + foo { + return switch Bool.random() { + case true: + 0 // expected-error {{cannot convert value of type 'Int' to specified type 'Void'}} + case false: + if .random() { + 0 + } else { + 0 + } + } + } + bar { + return switch Bool.random() { + case true: + 0 + case false: + 0 + } + } + bar { + return switch Bool.random() { + case true: + 0 + case false: + if .random() { + 0 + } else { + 0 + } + } + } + bar { + return switch Bool.random() { + case true: + () // expected-error {{branches have mismatching types '()' and 'Int'}} + case false: + 0 + } + } + bar { + return switch Bool.random() { + case true: + 0 + case false: + if .random() { + () // expected-error {{branches have mismatching types '()' and 'Int'}} + } else { + 0 + } + } + } +} + +struct SomeError: Error {} + +func testThrowInference() { + func notThrowing(_ fn: () -> Int) {} + notThrowing { // expected-error {{invalid conversion from throwing function of type '() throws -> Int' to non-throwing function type '() -> Int'}} + switch Bool.random() { + case true: + 0 + case false: + throw SomeError() + } + } + + @discardableResult + func rethrowing(_ fn: () throws -> T) rethrows -> T { try fn() } + rethrowing { + // expected-error@-1 {{call can throw, but it is not marked with 'try' and the error is not handled}} + // expected-note@-2 {{call is to 'rethrows' function, but argument function can throw}} + switch Bool.random() { + case true: + 0 + case false: + throw SomeError() + } + } +} + +// MARK: Subtyping + +class A {} +class B : A {} +class C : A {} + +func testSubtyping1() -> A { + // We can join to A. + let x = switch Bool.random() { + case true: + B() + case false: + C() + } + let y = .random() ? B() : C() + if .random() { + return x + } else { + return y + } +} + +// MARK: Opaque result types + +protocol P {} +extension Int : P {} + +@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) +func testOpaqueReturn1() -> some P { + switch Bool.random() { + case true: + 0 + case false: + 1 + } +} + +@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) +func testOpaqueReturn2() -> some P { + switch Bool.random() { + case true: + 0 + case false: + fatalError() + } +} + +// MARK: Result builders + +enum Either { + case first(T), second(U) +} + +@resultBuilder +struct Builder { + static func buildBlock(_ x: T) -> T { x } + static func buildBlock(_ x: T, _ y: U) -> (T, U) { (x, y) } + + static func buildEither(first x: T) -> Either { .first(x) } + static func buildEither(second x: U) -> Either { .second(x) } + + static func buildExpression(_ x: Double) -> Double { x } + static func buildExpression(_ x: T) -> T { x } +} + +@Builder +func singleExprBuilder() -> Either { + switch Bool.random() { + case true: + "" + case false: + 1 + } +} + +@Builder +func builderStaticMember() -> (Either, Double) { + switch Bool.random() { + case true: + "" + case false: + 1 + } + .pi // This becomes a static member ref, not a member on an if expression. +} + +@Builder +func builderNotPostfix() -> (Either, Bool) { + switch Bool.random() { case true: "" case false: 1 } !.random() // expected-error {{consecutive statements on a line must be separated by ';'}} +} + +@Builder +func builderWithBinding() -> Either { + // Make sure the binding gets type-checked as an if expression, but the + // other if block gets type-checked as a stmt. + let str = switch Bool.random() { + case true: "a" + case false: "b" + } + if .random() { + str + } else { + 1 + } +} + +func builderInClosure() { + func build(@Builder _ fn: () -> Either) {} + build { + switch Bool.random() { + case true: + "" + case false: + 1 + } + } +} diff --git a/test/Parse/recovery.swift b/test/Parse/recovery.swift index 3d00746bf6b3f..e9e04c777fd6b 100644 --- a/test/Parse/recovery.swift +++ b/test/Parse/recovery.swift @@ -77,10 +77,13 @@ class ClassWithStaticDecls { //===--- Recovery for missing controlling expression in statements. func missingControllingExprInIf() { - if // expected-error {{expected expression, var, or let in 'if' condition}} + if 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}} if // expected-error {{missing condition in 'if' statement}} { @@ -234,10 +237,10 @@ func missingControllingExprInForEach() { } func missingControllingExprInSwitch() { - switch // expected-error {{expected expression in 'switch' statement}} + switch - switch { // expected-error {{expected expression in 'switch' statement}} expected-error {{'switch' statement body must have at least one 'case' or 'default' block}} - } + 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}} + } // 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/Profiler/coverage_if_expr.swift b/test/Profiler/coverage_if_expr.swift new file mode 100644 index 0000000000000..20000e60984a6 --- /dev/null +++ b/test/Profiler/coverage_if_expr.swift @@ -0,0 +1,22 @@ +// RUN: %target-swift-frontend -Xllvm -sil-full-demangle -profile-generate -profile-coverage-mapping -emit-sorted-sil -emit-sil -module-name coverage_if_expr %s +// RUN: %target-swift-frontend -Xllvm -sil-full-demangle -profile-generate -profile-coverage-mapping -emit-sorted-sil -emit-sil -module-name coverage_if_expr %s | %FileCheck %s +// RUN: %target-swift-frontend -profile-generate -profile-coverage-mapping -emit-ir %s + +// CHECK-LABEL: sil_coverage_map {{.*}} "$s16coverage_if_expr0b1_C0SiyF" +func if_expr() -> Int { // CHECK: [[@LINE]]:23 -> {{[0-9]+}}:2 : 0 + if .random() { // CHECK-NEXT: [[@LINE]]:6 -> [[@LINE]]:15 : 0 + 0 // CHECK-NEXT: [[@LINE-1]]:16 -> [[@LINE+4]]:4 : 1 + // CHECK-NEXT: [[@LINE+3]]:4 -> {{[0-9]+}}:2 : 0 + + // FIXME: The below line is incorrect, it should be (0 - 1), but that's an existing bug with else ifs. + } else if .random() { // CHECK-NEXT: [[@LINE]]:13 -> [[@LINE]]:22 : 0 + 1 // CHECK-NEXT: [[@LINE-1]]:23 -> [[@LINE+1]]:4 : 2 + } else { // CHECK-NEXT: [[@LINE]]:4 -> {{[0-9]+}}:2 : 0 + + // FIXME: The below line is incorrect, it should be ((0 - 1) - 2), but that's an existing bug with else ifs. + 2 // CHECK-NEXT: [[@LINE-3]]:10 -> [[@LINE+4]]:4 : (0 - 2) + + // FIXME: Also incorrect + // CHECK-NEXT: [[@LINE+1]]:4 -> [[@LINE+2]]:2 : 0 + } // CHECK-NEXT: } +} diff --git a/test/Profiler/coverage_switch_expr.swift b/test/Profiler/coverage_switch_expr.swift new file mode 100644 index 0000000000000..7923c30f1c58b --- /dev/null +++ b/test/Profiler/coverage_switch_expr.swift @@ -0,0 +1,14 @@ +// RUN: %target-swift-frontend -Xllvm -sil-full-demangle -profile-generate -profile-coverage-mapping -emit-sorted-sil -emit-sil -module-name coverage_switch_expr %s | %FileCheck %s +// RUN: %target-swift-frontend -profile-generate -profile-coverage-mapping -emit-ir %s + +// CHECK-LABEL: sil_coverage_map {{.*}} "$s20coverage_switch_expr0b1_C0ySiSbSgF" +func switch_expr(_ b: Bool?) -> Int { // CHECK-NEXT: [[@LINE]]:37 -> {{[0-9]+}}:2 : 0 + switch b { // CHECK-NEXT: [[@LINE]]:10 -> [[@LINE]]:11 : 0 + case true?: // CHECK-NEXT: [[@LINE]]:3 -> [[@LINE+1]]:6 : 1 + 0 + case false?: // CHECK-NEXT: [[@LINE]]:3 -> [[@LINE+1]]:6 : 2 + 1 + case nil: // CHECK-NEXT: [[@LINE]]:3 -> [[@LINE+1]]:6 : 3 + 2 // FIXME: The following is incorrect + } // CHECK-NEXT: [[@LINE]]:4 -> [[@LINE+1]]:2 : ((1 + 2) + 3) +} // CHECK-NEXT: } diff --git a/test/SILGen/if_expr.swift b/test/SILGen/if_expr.swift new file mode 100644 index 0000000000000..066fba3f40204 --- /dev/null +++ b/test/SILGen/if_expr.swift @@ -0,0 +1,232 @@ +// RUN: %target-swift-emit-silgen -enable-experimental-feature StatementExpressions %s | %FileCheck %s +// RUN: %target-swift-emit-ir -enable-experimental-feature StatementExpressions %s + +func foo() -> Int { + if .random() { 1 } else { 2 } +} + +// CHECK-LABEL: sil hidden [ossa] @$s7if_expr3fooSiyF : $@convention(thin) () -> Int +// CHECK: [[RESULT_STORAGE:%[0-9]+]] = alloc_stack $Int +// CHECK: [[RESULT:%[0-9]+]] = mark_uninitialized [var] [[RESULT_STORAGE]] : $*Int +// CHECK: cond_br {{%[0-9]+}}, [[TRUEBB:bb[0-9]+]], [[FALSEBB:bb[0-9]+]] +// +// CHECK: [[TRUEBB]]: +// CHECK: [[ONE_BUILTIN:%[0-9]+]] = integer_literal $Builtin.IntLiteral, 1 +// CHECK: [[ONE:%[0-9]+]] = apply {{%[0-9]+}}([[ONE_BUILTIN]], {{%[0-9]+}}) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int +// CHECK: store [[ONE]] to [trivial] [[RESULT]] : $*Int +// CHECK: br [[EXITBB:bb[0-9]+]] +// +// CHECK: [[FALSEBB]]: +// CHECK: [[TWO_BUILTIN:%[0-9]+]] = integer_literal $Builtin.IntLiteral, 2 +// CHECK: [[TWO:%[0-9]+]] = apply {{%[0-9]+}}([[TWO_BUILTIN]], {{%[0-9]+}}) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int +// CHECK: store [[TWO]] to [trivial] [[RESULT]] : $*Int +// CHECK: br [[EXITBB]] +// +// CHECK: [[EXITBB]]: +// CHECK: [[VAL:%[0-9]+]] = load [trivial] [[RESULT]] : $*Int +// CHECK: dealloc_stack [[RESULT_STORAGE]] : $*Int +// CHECK: return [[VAL]] : $Int + +class C {} + +func bar(_ x: C) -> C { + if .random() { x } else { C() } +} + +// CHECK-LABEL: sil hidden [ossa] @$s7if_expr3baryAA1CCADF : $@convention(thin) (@guaranteed C) -> @owned C +// CHECK: bb0([[CPARAM:%[0-9]+]] : @guaranteed $C): +// CHECK: [[RESULT_STORAGE:%[0-9]+]] = alloc_stack $C +// CHECK: [[RESULT:%[0-9]+]] = mark_uninitialized [var] [[RESULT_STORAGE]] : $*C +// CHECK: cond_br {{%[0-9]+}}, [[TRUEBB:bb[0-9]+]], [[FALSEBB:bb[0-9]+]] +// +// CHECK: [[TRUEBB]]: +// CHECK: [[C:%[0-9]+]] = copy_value [[CPARAM]] : $C +// CHECK: store [[C]] to [init] [[RESULT]] : $*C +// CHECK: br [[EXITBB:bb[0-9]+]] +// +// CHECK: [[FALSEBB]]: +// CHECK: [[CTOR:%[0-9]+]] = function_ref @$s7if_expr1CCACycfC : $@convention(method) (@thick C.Type) -> @owned C +// CHECK: [[C:%[0-9]+]] = apply [[CTOR]]({{%[0-9]+}}) : $@convention(method) (@thick C.Type) -> @owned C +// CHECK: store [[C]] to [init] [[RESULT]] : $*C +// CHECK: br [[EXITBB]] +// +// CHECK: [[EXITBB]]: +// CHECK: [[VAL:%[0-9]+]] = load [take] [[RESULT]] : $*C +// CHECK: dealloc_stack [[RESULT_STORAGE]] : $*C +// CHECK: return [[VAL]] : $C + +struct Err: Error {} + +func baz() throws -> Int { + if .random() { + 0 + } else if .random() { + throw Err() + } else { + 2 + } +} + +// CHECK-LABEL: sil hidden [ossa] @$s7if_expr3bazSiyKF : $@convention(thin) () -> (Int, @error any Error) +// CHECK: [[RESULT_STORAGE:%[0-9]+]] = alloc_stack $Int +// CHECK: [[RESULT:%[0-9]+]] = mark_uninitialized [var] [[RESULT_STORAGE]] : $*Int +// CHECK: cond_br {{%[0-9]+}}, [[TRUEBB:bb[0-9]+]], [[FALSEBB:bb[0-9]+]] +// +// CHECK: [[FALSEBB]]: +// CHECK: cond_br {{%[0-9]+}}, [[FALSETRUEBB:bb[0-9]+]], [[FALSEFALSEBB:bb[0-9]+]] +// +// CHECK: [[FALSETRUEBB]]: +// CHECK: throw {{%[0-9]+}} : $any Error +// +// CHECK: [[FALSEFALSEBB]]: +// CHECK: br [[EXITBB:bb[0-9]+]] +// +// CHECK: [[EXITBB]]: +// CHECK: [[VAL:%[0-9]+]] = load [trivial] [[RESULT]] : $*Int +// CHECK: dealloc_stack [[RESULT_STORAGE]] : $*Int +// CHECK: return [[VAL]] : $Int + +func qux() throws -> Int { + if .random() { 0 } else { try baz() } +} + +// CHECK-LABEL: sil hidden [ossa] @$s7if_expr3quxSiyKF : $@convention(thin) () -> (Int, @error any Error) +// CHECK: [[RESULT_STORAGE:%[0-9]+]] = alloc_stack $Int +// CHECK: [[RESULT:%[0-9]+]] = mark_uninitialized [var] [[RESULT_STORAGE]] : $*Int +// CHECK: cond_br {{%[0-9]+}}, [[TRUEBB:bb[0-9]+]], [[FALSEBB:bb[0-9]+]] +// +// CHECK: [[FALSEBB]]: +// CHECK: try_apply {{%[0-9]+}}() : $@convention(thin) () -> (Int, @error any Error), normal [[NORMALBB:bb[0-9]+]], error [[ERRORBB:bb[0-9]+]] +// +// CHECK: [[NORMALBB]]([[BAZVAL:%[0-9]+]] : $Int): +// CHECK: store [[BAZVAL]] to [trivial] [[RESULT]] : $*Int +// CHECK: br [[EXITBB:bb[0-9]+]] +// +// CHECK: [[EXITBB]]: +// CHECK: [[VAL:%[0-9]+]] = load [trivial] [[RESULT]] : $*Int +// CHECK: dealloc_stack [[RESULT_STORAGE]] : $*Int +// CHECK: return [[VAL]] : $Int +// +// CHECK: [[ERRORBB]]([[ERR:%[0-9]+]] : @owned $any Error): +// CHECK: dealloc_stack [[RESULT_STORAGE]] : $*Int +// CHECK: throw [[ERR]] : $any Error + +func optionalVoidCrash() { + func takesClosure(_ x: () -> T) {} + + struct S { + func bar() {} + } + + var s: S? + takesClosure { + if true { + s?.bar() + } else { + () + } + } +} + +func testClosure() throws -> Int { + let fn = { + if .random() { + 0 + } else { + try baz() + } + } + return try fn() +} + +// CHECK-LABEL: sil private [ossa] @$s7if_expr11testClosureSiyKFSiyKcfU_ : $@convention(thin) () -> (Int, @error any Error) +// CHECK: [[RESULT_STORAGE:%[0-9]+]] = alloc_stack $Int +// CHECK: [[RESULT:%[0-9]+]] = mark_uninitialized [var] [[RESULT_STORAGE]] : $*Int +// CHECK: cond_br {{%[0-9]+}}, [[TRUEBB:bb[0-9]+]], [[FALSEBB:bb[0-9]+]] +// +// CHECK: [[FALSEBB]]: +// CHECK: try_apply {{%[0-9]+}}() : $@convention(thin) () -> (Int, @error any Error), normal [[NORMALBB:bb[0-9]+]], error [[ERRORBB:bb[0-9]+]] +// +// CHECK: [[NORMALBB]]([[BAZVAL:%[0-9]+]] : $Int): +// CHECK: store [[BAZVAL]] to [trivial] [[RESULT]] : $*Int +// CHECK: br [[EXITBB:bb[0-9]+]] +// +// CHECK: [[EXITBB]]: +// CHECK: [[VAL:%[0-9]+]] = load [trivial] [[RESULT]] : $*Int +// CHECK: dealloc_stack [[RESULT_STORAGE]] : $*Int +// CHECK: return [[VAL]] : $Int +// +// CHECK: [[ERRORBB]]([[ERR:%[0-9]+]] : @owned $any Error): +// CHECK: dealloc_stack [[RESULT_STORAGE]] : $*Int +// CHECK: throw [[ERR]] : $any Error + +func testNested() throws -> Int { + if .random() { + 0 + } else { + if .random() { + throw Err() + } else { + 2 + } + } +} + +// CHECK-LABEL: sil hidden [ossa] @$s7if_expr10testNestedSiyKF : $@convention(thin) () -> (Int, @error any Error) +// CHECK: [[RESULT_STORAGE:%[0-9]+]] = alloc_stack $Int +// CHECK: [[RESULT:%[0-9]+]] = mark_uninitialized [var] [[RESULT_STORAGE]] : $*Int +// CHECK: cond_br {{%[0-9]+}}, [[TRUEBB:bb[0-9]+]], [[FALSEBB:bb[0-9]+]] +// +// CHECK: [[FALSEBB]]: +// CHECK: cond_br {{%[0-9]+}}, [[FALSETRUEBB:bb[0-9]+]], [[FALSEFALSEBB:bb[0-9]+]] +// +// CHECK: [[FALSETRUEBB]]: +// CHECK: throw {{%[0-9]+}} : $any Error +// +// CHECK: [[FALSEFALSEBB]]: +// CHECK: br [[EXITBB:bb[0-9]+]] +// +// CHECK: [[EXITBB]]: +// CHECK: [[VAL:%[0-9]+]] = load [trivial] [[RESULT]] : $*Int +// CHECK: dealloc_stack [[RESULT_STORAGE]] : $*Int +// CHECK: return [[VAL]] : $Int + +func testVar() -> Int { + let x = if .random() { 1 } else { 2 } + return x +} + +func testCatch() -> Int { + do { + let x = if .random() { + 0 + } else { + throw Err() + } + return x + } catch { + return 0 + } +} + +struct TestPropertyInit { + var x = if .random() { 1 } else { 0 } + lazy var y = if .random() { 1 } else { 0 } +} + +func testAssignment() { + var x = 0 + x = if .random() { 0 } else { 1 } + let fn = { + x = if .random() { 0 } else { 1 } + } +} + +func nestedType() throws -> Int { + if .random() { + struct S: Error {} + throw S() + } else { + 0 + } +} diff --git a/test/SILGen/switch_expr.swift b/test/SILGen/switch_expr.swift new file mode 100644 index 0000000000000..fa28eac13d0b4 --- /dev/null +++ b/test/SILGen/switch_expr.swift @@ -0,0 +1,325 @@ +// RUN: %target-swift-emit-silgen -enable-experimental-feature StatementExpressions %s | %FileCheck %s +// RUN: %target-swift-emit-ir -enable-experimental-feature StatementExpressions %s + +func foo() -> Int { + switch Bool.random() { + case true: + 1 + case false: + 2 + } +} + +// CHECK-LABEL: sil hidden [ossa] @$s11switch_expr3fooSiyF : $@convention(thin) () -> Int +// CHECK: [[RESULT_STORAGE:%[0-9]+]] = alloc_stack $Int +// CHECK: [[RESULT:%[0-9]+]] = mark_uninitialized [var] [[RESULT_STORAGE]] : $*Int +// CHECK: switch_value {{%[0-9]+}} : $Builtin.Int1, case {{%[0-9]+}}: [[TRUEBB:bb[0-9]+]], case {{%[0-9]+}}: [[FALSEBB:bb[0-9]+]] +// +// CHECK: [[TRUEBB]]: +// CHECK: [[ONE_BUILTIN:%[0-9]+]] = integer_literal $Builtin.IntLiteral, 1 +// CHECK: [[ONE:%[0-9]+]] = apply {{%[0-9]+}}([[ONE_BUILTIN]], {{%[0-9]+}}) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int +// CHECK: store [[ONE]] to [trivial] [[RESULT]] : $*Int +// CHECK: br [[EXITBB:bb[0-9]+]] +// +// CHECK: [[FALSEBB]]: +// CHECK: [[TWO_BUILTIN:%[0-9]+]] = integer_literal $Builtin.IntLiteral, 2 +// CHECK: [[TWO:%[0-9]+]] = apply {{%[0-9]+}}([[TWO_BUILTIN]], {{%[0-9]+}}) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int +// CHECK: store [[TWO]] to [trivial] [[RESULT]] : $*Int +// CHECK: br [[EXITBB]] +// +// CHECK: [[EXITBB]]: +// CHECK: [[VAL:%[0-9]+]] = load [trivial] [[RESULT]] : $*Int +// CHECK: dealloc_stack [[RESULT_STORAGE]] : $*Int +// CHECK: return [[VAL]] : $Int + +class C {} + +func bar(_ x: C) -> C { + switch Bool.random() { + case true: + x + case false: + C() + } +} + +// CHECK-LABEL: sil hidden [ossa] @$s11switch_expr3baryAA1CCADF : $@convention(thin) (@guaranteed C) -> @owned C +// CHECK: bb0([[CPARAM:%[0-9]+]] : @guaranteed $C): +// CHECK: [[RESULT_STORAGE:%[0-9]+]] = alloc_stack $C +// CHECK: [[RESULT:%[0-9]+]] = mark_uninitialized [var] [[RESULT_STORAGE]] : $*C +// CHECK: switch_value {{%[0-9]+}} : $Builtin.Int1, case {{%[0-9]+}}: [[TRUEBB:bb[0-9]+]], case {{%[0-9]+}}: [[FALSEBB:bb[0-9]+]] +// +// CHECK: [[TRUEBB]]: +// CHECK: [[C:%[0-9]+]] = copy_value [[CPARAM]] : $C +// CHECK: store [[C]] to [init] [[RESULT]] : $*C +// CHECK: br [[EXITBB:bb[0-9]+]] +// +// CHECK: [[FALSEBB]]: +// CHECK: [[CTOR:%[0-9]+]] = function_ref @$s11switch_expr1CCACycfC : $@convention(method) (@thick C.Type) -> @owned C +// CHECK: [[C:%[0-9]+]] = apply [[CTOR]]({{%[0-9]+}}) : $@convention(method) (@thick C.Type) -> @owned C +// CHECK: store [[C]] to [init] [[RESULT]] : $*C +// CHECK: br [[EXITBB]] +// +// CHECK: [[EXITBB]]: +// CHECK: [[VAL:%[0-9]+]] = load [take] [[RESULT]] : $*C +// CHECK: dealloc_stack [[RESULT_STORAGE]] : $*C +// CHECK: return [[VAL]] : $C + +struct Err: Error {} + +enum E { case a, b, c } + +func baz(_ e: E) throws -> Int { + switch e { + case .a: + 1 + case .b: + throw Err() + default: + 2 + } +} + +// CHECK-LABEL: sil hidden [ossa] @$s11switch_expr3bazySiAA1EOKF : $@convention(thin) (E) -> (Int, @error any Error) +// CHECK: [[RESULT_STORAGE:%[0-9]+]] = alloc_stack $Int +// CHECK: [[RESULT:%[0-9]+]] = mark_uninitialized [var] [[RESULT_STORAGE]] : $*Int +// CHECK: switch_enum %0 : $E, case #E.a!enumelt: [[ABB:bb[0-9]+]], case #E.b!enumelt: [[BBB:bb[0-9]+]], default [[DEFBB:bb[0-9]+]] +// +// CHECK: [[ABB]]: +// CHECK: [[ONE_BUILTIN:%[0-9]+]] = integer_literal $Builtin.IntLiteral, 1 +// CHECK: [[ONE:%[0-9]+]] = apply {{%[0-9]+}}([[ONE_BUILTIN]], {{%[0-9]+}}) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int +// CHECK: store [[ONE]] to [trivial] [[RESULT]] : $*Int +// CHECK: br [[EXITBB:bb[0-9]+]] +// +// CHECK: [[BBB]]: +// CHECK: throw {{%[0-9]+}} : $any Error +// +// CHECK: [[DEFBB]]: +// CHECK: [[TWO_BUILTIN:%[0-9]+]] = integer_literal $Builtin.IntLiteral, 2 +// CHECK: [[TWO:%[0-9]+]] = apply {{%[0-9]+}}([[TWO_BUILTIN]], {{%[0-9]+}}) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int +// CHECK: store [[TWO]] to [trivial] [[RESULT]] : $*Int +// CHECK: br [[EXITBB:bb[0-9]+]] +// +// CHECK: [[EXITBB]]: +// CHECK: [[VAL:%[0-9]+]] = load [trivial] [[RESULT]] : $*Int +// CHECK: dealloc_stack [[RESULT_STORAGE]] : $*Int +// CHECK: return [[VAL]] : $Int + +func qux() throws -> Int { + switch Bool.random() { + case true: + 0 + case false: + try baz(.a) + } +} + +// CHECK-LABEL: sil hidden [ossa] @$s11switch_expr3quxSiyKF : $@convention(thin) () -> (Int, @error any Error) +// CHECK: [[RESULT_STORAGE:%[0-9]+]] = alloc_stack $Int +// CHECK: [[RESULT:%[0-9]+]] = mark_uninitialized [var] [[RESULT_STORAGE]] : $*Int +// CHECK: switch_value {{%[0-9]+}} : $Builtin.Int1, case {{%[0-9]+}}: [[TRUEBB:bb[0-9]+]], case {{%[0-9]+}}: [[FALSEBB:bb[0-9]+]] +// +// CHECK: [[FALSEBB]]: +// CHECK: try_apply {{%[0-9]+}}({{%[0-9]+}}) : $@convention(thin) (E) -> (Int, @error any Error), normal [[NORMALBB:bb[0-9]+]], error [[ERRORBB:bb[0-9]+]] +// +// CHECK: [[NORMALBB]]([[BAZVAL:%[0-9]+]] : $Int): +// CHECK: store [[BAZVAL]] to [trivial] [[RESULT]] : $*Int +// CHECK: br [[EXITBB:bb[0-9]+]] +// +// CHECK: [[EXITBB]]: +// CHECK: [[VAL:%[0-9]+]] = load [trivial] [[RESULT]] : $*Int +// CHECK: dealloc_stack [[RESULT_STORAGE]] : $*Int +// CHECK: return [[VAL]] : $Int +// +// CHECK: [[ERRORBB]]([[ERR:%[0-9]+]] : @owned $any Error): +// CHECK: dealloc_stack [[RESULT_STORAGE]] : $*Int +// CHECK: throw [[ERR]] : $any Error + +func testFallthrough() throws -> Int { + switch Bool.random() { + case true: + if .random() { fallthrough } + throw Err() + case false: + 1 + } +} + +// CHECK-LABEL: sil hidden [ossa] @$s11switch_expr15testFallthroughSiyKF : $@convention(thin) () -> (Int, @error any Error) +// CHECK: [[RESULT_STORAGE:%[0-9]+]] = alloc_stack $Int +// CHECK: [[RESULT:%[0-9]+]] = mark_uninitialized [var] [[RESULT_STORAGE]] : $*Int +// CHECK: switch_value {{%[0-9]+}} : $Builtin.Int1, case {{%[0-9]+}}: [[TRUEBB:bb[0-9]+]], case {{%[0-9]+}}: [[FALSEBB:bb[0-9]+]] +// +// CHECK: [[TRUEBB]]: +// CHECK: cond_br {{.*}}, [[IFTRUEBB:bb[0-9]+]], [[IFFALSEBB:bb[0-9]+]] +// +// CHECK: [[IFTRUEBB]]: +// CHECK: br [[ACTUALFALSEBB:bb[0-9]+]] +// +// CHECK: [[IFFALSEBB]]: +// CHECK: throw {{%[0-9]+}} : $any Error +// +// CHECK: [[FALSEBB]]: +// CHECK: br [[ACTUALFALSEBB]] +// +// CHECK: [[ACTUALFALSEBB]]: +// CHECK: [[ONE_BUILTIN:%[0-9]+]] = integer_literal $Builtin.IntLiteral, 1 +// CHECK: [[ONE:%[0-9]+]] = apply {{%[0-9]+}}([[ONE_BUILTIN]], {{%[0-9]+}}) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int +// CHECK: store [[ONE]] to [trivial] [[RESULT]] : $*Int +// CHECK: [[VAL:%[0-9]+]] = load [trivial] [[RESULT]] : $*Int +// CHECK: dealloc_stack [[RESULT_STORAGE]] : $*Int +// CHECK: return [[VAL]] : $Int + +func testClosure() throws -> Int { + let fn = { + switch Bool.random() { + case true: + 0 + case false: + try baz(.a) + } + } + return try fn() +} + +// CHECK-LABEL: sil private [ossa] @$s11switch_expr11testClosureSiyKFSiyKcfU_ : $@convention(thin) () -> (Int, @error any Error) +// CHECK: [[RESULT_STORAGE:%[0-9]+]] = alloc_stack $Int +// CHECK: [[RESULT:%[0-9]+]] = mark_uninitialized [var] [[RESULT_STORAGE]] : $*Int +// CHECK: switch_value {{%[0-9]+}} : $Builtin.Int1, case {{%[0-9]+}}: [[TRUEBB:bb[0-9]+]], case {{%[0-9]+}}: [[FALSEBB:bb[0-9]+]] +// +// CHECK: [[FALSEBB]]: +// CHECK: try_apply {{%[0-9]+}}({{%[0-9]+}}) : $@convention(thin) (E) -> (Int, @error any Error), normal [[NORMALBB:bb[0-9]+]], error [[ERRORBB:bb[0-9]+]] +// +// CHECK: [[NORMALBB]]([[BAZVAL:%[0-9]+]] : $Int): +// CHECK: store [[BAZVAL]] to [trivial] [[RESULT]] : $*Int +// CHECK: br [[EXITBB:bb[0-9]+]] +// +// CHECK: [[EXITBB]]: +// CHECK: [[VAL:%[0-9]+]] = load [trivial] [[RESULT]] : $*Int +// CHECK: dealloc_stack [[RESULT_STORAGE]] : $*Int +// CHECK: return [[VAL]] : $Int +// +// CHECK: [[ERRORBB]]([[ERR:%[0-9]+]] : @owned $any Error): +// CHECK: dealloc_stack [[RESULT_STORAGE]] : $*Int +// CHECK: throw [[ERR]] : $any Error + +func testVar1() -> Int { + let x = switch Bool.random() { + case let b where b == true: + 1 + case false: + 0 + default: + 2 + } + return x +} + +func testVar2() -> Int { + let x = switch Bool.random() { + case let b where b == true: + 1 + case false: + 0 + default: + 2 + } + return x +} + +func testCatch() -> Int { + do { + let x = switch Bool.random() { + case true: + 0 + case false: + throw Err() + } + return x + } catch { + return 0 + } +} + +struct TestPropertyInit { + var x = switch Bool.random() { + case let b where b == true: + 1 + case false: + 0 + default: + 2 + } + lazy var y = switch Bool.random() { + case let b where b == true: + 1 + case false: + 0 + default: + 2 + } +} + +func testNested(_ e: E) throws -> Int { + switch e { + case .a: + 1 + default: + switch e { + case .b: + throw Err() + default: + 2 + } + } +} + +// CHECK-LABEL: sil hidden [ossa] @$s11switch_expr10testNestedySiAA1EOKF : $@convention(thin) (E) -> (Int, @error any Error) +// CHECK: [[RESULT_STORAGE:%[0-9]+]] = alloc_stack $Int +// CHECK: [[RESULT:%[0-9]+]] = mark_uninitialized [var] [[RESULT_STORAGE]] : $*Int +// CHECK: switch_enum %0 : $E, case #E.a!enumelt: [[ABB:bb[0-9]+]], default [[DEFBB:bb[0-9]+]] +// +// CHECK: [[ABB]]: +// CHECK: [[ONE_BUILTIN:%[0-9]+]] = integer_literal $Builtin.IntLiteral, 1 +// CHECK: [[ONE:%[0-9]+]] = apply {{%[0-9]+}}([[ONE_BUILTIN]], {{%[0-9]+}}) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int +// CHECK: store [[ONE]] to [trivial] [[RESULT]] : $*Int +// CHECK: br [[EXITBB:bb[0-9]+]] +// +// CHECK: [[DEFBB]]({{.*}}): +// CHECK: [[NESTEDRESULT_STORAGE:%[0-9]+]] = alloc_stack $Int +// CHECK: [[NESTEDRESULT:%[0-9]+]] = mark_uninitialized [var] [[NESTEDRESULT_STORAGE]] : $*Int +// CHECK: switch_enum %0 : $E, case #E.b!enumelt: [[BBB:bb[0-9]+]], default [[NESTEDDEFBB:bb[0-9]+]] + +// CHECK: [[BBB]]: +// CHECK: throw {{%[0-9]+}} : $any Error +// +// CHECK: [[NESTEDDEFBB]]({{.*}}): +// CHECK: [[TWO_BUILTIN:%[0-9]+]] = integer_literal $Builtin.IntLiteral, 2 +// CHECK: [[TWO:%[0-9]+]] = apply {{%[0-9]+}}([[TWO_BUILTIN]], {{%[0-9]+}}) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int +// CHECK: store [[TWO]] to [trivial] [[NESTEDRESULT]] : $*Int +// CHECK: [[TMP:%[0-9]+]] = load [trivial] [[NESTEDRESULT]] : $*Int +// CHECK: store [[TMP]] to [trivial] [[RESULT]] : $*Int +// CHECK: br [[EXITBB:bb[0-9]+]] +// +// CHECK: [[EXITBB]]: +// CHECK: [[VAL:%[0-9]+]] = load [trivial] [[RESULT]] : $*Int +// CHECK: dealloc_stack [[RESULT_STORAGE]] : $*Int +// CHECK: return [[VAL]] : $Int + +func testAssignment() { + var x = 0 + x = switch Bool.random() { case true: 0 case false: 1 } + let fn = { + x = switch Bool.random() { case true: 0 case false: 1 } + } +} + +func nestedType() throws -> Int { + switch Bool.random() { + case true: + struct S: Error {} + throw S() + case false: + 0 + } +} diff --git a/test/SILOptimizer/generalized_accessors.swift b/test/SILOptimizer/generalized_accessors.swift index dc6e60ffe0c09..dd343f76f171a 100644 --- a/test/SILOptimizer/generalized_accessors.swift +++ b/test/SILOptimizer/generalized_accessors.swift @@ -358,7 +358,7 @@ struct TestExplicitReturn { if flag { stored = 2 } - return // expected-error {{accessor must yield before returning}} + return; // expected-error {{accessor must yield before returning}} if !flag { // expected-warning {{code after 'return' will never be executed}} stored = 3 diff --git a/test/SILOptimizer/if_expr.swift b/test/SILOptimizer/if_expr.swift new file mode 100644 index 0000000000000..bc2c845dc9ff5 --- /dev/null +++ b/test/SILOptimizer/if_expr.swift @@ -0,0 +1,23 @@ +// RUN: %target-swift-emit-sil -verify -enable-experimental-feature StatementExpressions %s -o /dev/null + +func takesGenericReturningFn(_ fn: () -> R) {} + +func testOuterClosureReturn() { + takesGenericReturningFn { + if .random() { + return + } else { + () + } + } +} + +func testNeverToVoid() { + takesGenericReturningFn { + if .random() { // This does not turn into an expression due to the 'do'. + fatalError() + } else { + do {} + } + } +} diff --git a/test/SILOptimizer/switch_expr.swift b/test/SILOptimizer/switch_expr.swift new file mode 100644 index 0000000000000..7da4b0ddfe59e --- /dev/null +++ b/test/SILOptimizer/switch_expr.swift @@ -0,0 +1,23 @@ +// RUN: %target-swift-emit-sil -verify -enable-experimental-feature StatementExpressions %s -o /dev/null + +func foo() -> Int { + switch Bool.random() { + case true: + 0 // expected-warning {{integer literal is unused}} + case false: + do {} + } +} // expected-error {{missing return in global function expected to return 'Int'}} + +enum E { + case x(T), y + + func foo() -> E { + switch self { + case .x: + while true {} + case .y: + fatalError() + } + } +} diff --git a/test/expr/unary/if_expr.swift b/test/expr/unary/if_expr.swift new file mode 100644 index 0000000000000..615557818152a --- /dev/null +++ b/test/expr/unary/if_expr.swift @@ -0,0 +1,703 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-feature StatementExpressions -disable-availability-checking + +// MARK: Functions + +func foo() -> Int { + if .random() { 1 } else { 2 } +} + +func foo2() -> Int { + return if .random() { 1 } else { 2 } +} + +func foo3() -> Int { + if .random() { 1 } else { 2 } as Int +} + +func foo4() -> Int { + return if .random() { 1 } else { 2 } as Int +} + +func foo5() -> String { + if .random() { 1 } else { 2 } as String + // expected-error@-1 {{cannot convert value of type 'Int' to specified type 'String'}} +} + +func foo6() -> Int { + return (if .random() { 1 } else { 2 } as Int) + // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} +} + +func bar() -> Int { + if .random() { + fatalError() + } else { + 2 + } +} + +func baz() -> Int { + if .random() { + "" // expected-error {{cannot convert value of type 'String' to specified type 'Int'}} + } else { + 0 + } +} + +func qux(_ x: Int?) -> Int { + if let x = x { x } else { 0 } +} + +func quux(_ x: Int?) -> Int { + if case let x? = x { x } else { 0 } +} + +func elseIf(_ x: Int?) -> Int { + if .random() { + 0 + } else if let x = x { + x + } else if .random() { + 1 + } else { + 7 + 8 + } +} + +func takesValue(_ x: T) {} + +// expected-error@+1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} +takesValue(if .random() { + 0 +} else { + 1 +}) +takesValue(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}} + +// 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}} + + takesValue(_: x: if .random() { 0 } else { 1 }) + // expected-error@-1 {{expected expression in list of expressions}} + // expected-error@-2 {{expected ',' separator}} + // expected-error@-3 {{cannot find 'x' in scope}} +} +func takesValueWithLabel(x: T) {} +do { + takesValueWithLabel(x: if .random() { 1 } else { 2 }) + // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + + takesValueWithLabel(x: y: if .random() { 1 } else { 2 }) + // expected-error@-1 {{expected expression in list of expressions}} + // expected-error@-2 {{expected ',' separator}} + // expected-error@-3 {{cannot find 'y' in scope}} +} +func takesValueAndTrailingClosure(_ x: T, _ fn: () -> Int) {} +takesValueAndTrailingClosure(if .random() { 0 } else { 1 }) { 2 } +// expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + +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 { () } + + subscript(x: Int...) -> Void { () } +} +HasSubscript[if .random() { 1 } else { 2 }] +// expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + +func proposalExample1(isRoot: Bool, count: Int, willExpand: Bool, maxDepth: Int) -> String { + let bullet = + if isRoot && (count == 0 || !willExpand) { "" } + else if count == 0 { "- " } + else if maxDepth <= 0 { "▹ " } + else { "▿ " } + return bullet +} + +var inAComputedVar: String { + if .random() { "a" } else { "b" } +} + +// MARK: Explicit returns + +func explicitReturn1() -> Int { + print("hello") + return if .random() { 0 } else { 1 } +} + +func explicitReturn2() -> Int { + return + if .random() { 0 } else { 1 } + // expected-warning@-1 {{expression following 'return' is treated as an argument of the 'return'}} + // expected-note@-2 {{indent the expression to silence this warning}} +} + +func explicitReturn3() -> Int { + return + if .random() { 0 } else { 1 } +} + +func explicitReturn4() { + // This used to be legal, but is now treated as a return of the if expression. + return + if .random() { 0 } else { 1 } // expected-error {{cannot convert value of type 'Int' to specified type '()'}} +} + +func explicitReturn5() { + return; + if .random() { 0 } else { 1 } // expected-warning 2{{integer literal is unused}} +} + +func explicitReturn6() { + return () + if .random() { 0 } else { 1 } // expected-warning 2{{integer literal is unused}} +} + +var explicitReturn7: String { + return if .random() { "a" } else { "b" } +} + +struct AsPropertyInit { + var x: Int = if Bool.random() { 1 } else { 0 } + var y = if .random() { 1 } else { 0 } +} + +func testNestedAssignment() { + var x = 0 + x = if .random() { 0 } else { 1 } // Okay + let fn = { + x = if .random() { 0 } else { 1 } // Also okay + } + + // We don't allow in a nested assignment. + // TODO: We could improve this error. + print(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}} + + _ = x; _ = fn +} + +struct TestFailableInit { + init?(_ x: Bool) { + let y = if x { + 0 + } else { + return nil // expected-error {{cannot 'return' in 'if' when used as expression}} + } + _ = y + } +} + +struct TestFailableInitFatalError { + init?() { + // In this case, the if does not become an expression. + if .random() { + fatalError() + } else { + return nil + } + } +} + +// MARK: Expressions + +let a = if .random() { + 0 +} else { + 1 +} + +let b = (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 c = (if .random() { 1 } else { 2 }, k: 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}} + +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 f = { if .random() { 1 } else { 2 } } + +func throwsError() throws { + struct E: Error {} + throw if .random() { E() } else { E() } +} + +let g = [if .random() { "a" } else { "b" }] // expected-error {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + +let h = [if .random() { 1 } else { 2 } : if .random() { "a" } else { "b" }] // expected-error 2{{'if' may only be used as expression in return, throw, or as the source of an assignment}} +let i = [if .random() { 1 } else { 2 }: if .random() { "a" } else { "b" }] // expected-error 2{{'if' may only be used as expression in return, throw, or as the source of an assignment}} +let j = [if .random() { 1 } else { 2 }:if .random() { "a" } else { "b" }] // expected-error 2{{'if' may only be used as expression in return, throw, or as the source of an assignment}} +let k = [if .random() { 1 } else { 2 } :if .random() { "a" } else { "b" }] // expected-error 2{{'if' may only be used as expression in return, throw, or as the source of an assignment}} + +let l = if .random() { 1 } else { 2 } as Any + +let _ = type(of: 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 _ = (if .random() { () } else { () }) // expected-error {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + +let _ = if .random() { 0 } // expected-error {{'if' must have an unconditional 'else' to be used as expression}} +let _ = if .random() { 0 } else if .random() { 1 } // expected-error {{'if' must have an unconditional 'else' to be used as expression}} + +func testNonExhaustiveInFunction() { + if .random() { 0 } // expected-warning {{integer literal is unused}} +} + +func testLabelRejection1() -> Int { + // This was legal before, so remains legal. + x: if .random() { 0 } else { 1 } + // expected-warning@-1 2{{integer literal is unused}} +} + +func testLabelRejection2() -> Int { + // This was never legal, so reject. + x: if .random() { 0 } else { 1 } as Int + // expected-error@-1 {{'if' cannot have a jump label when used as expression}} +} + +do { + if .random() { 1 } else { 2 } = 3 + // expected-error@-1 {{consecutive statements on a line must be separated by ';'}} + // expected-error@-2 {{expected expression}} + // expected-warning@-3 2{{integer literal is unused}}} +} +let m: Void = if .random() { 1 } else { 2 } // expected-error {{cannot convert value of type 'Int' to specified type 'Void'}} +let n: Never = if .random() { 1 } else { 2 } // expected-error {{cannot convert value of type 'Int' to specified type 'Never'}} + +func testConditionalBinding1(_ x: Int?) -> Int { + if let x = if .random() { 0 } else { Int?.none } { // expected-error {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + x + } else { + 0 + } +} + +func testConditionalBinding2(_ x: Int?) -> Int { + if case let x? = if .random() { 0 } else { Int?.none } { // expected-error {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + x + } else { + 0 + } +} + +// MARK: Operators + +let o = !if .random() { true } else { false } // expected-error {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + +// FIXME: Shouldn't be ambiguous +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 q = .random() ? if .random() { 1 } else { 2 } + : if .random() { 3 } else { 4 } +// expected-error@-2 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} +// expected-error@-2 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + +let r = if .random() { 1 } else { 2 }...if .random() { 1 } else { 2 } +// expected-error@-1 2{{'if' may only be used as expression in return, throw, or as the source of an assignment}} + +let s = if .random() { 1 } else { 2 } ... if .random() { 1 } else { 2 } +// expected-error@-1 2{{'if' may only be used as expression in return, throw, or as the source of an assignment}} + +// MARK: Lookup + +do { + let t = if .random() { t } else { 0 } + // expected-error@-1 {{use of local variable 't' before its declaration}} + // expected-note@-2 {{'t' declared here}} +} + +// MARK: Postfix + +// We don't allow postfix parsing. +do { + let _ = if .random() { [1] } else { [1, 2] }.count + // expected-error@-1 {{consecutive statements on a line must be separated by ';'}} + // expected-error@-2 {{reference to member 'count' cannot be resolved without a contextual type}} + + let _ = (if .random() { [1] } else { [1, 2] }).count + // expected-error@-1 {{'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? }?.bitWidth + // expected-error@-1 {{consecutive statements on a line must be separated by ';'}} + // expected-error@-2 {{expected expression}} + + 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}} +} +do { + let _ = if .random() { Int?.none } else { 1 as Int? }! + // expected-error@-1 {{consecutive statements on a line must be separated by ';'}} + // expected-error@-2 {{expected expression}} + + let _ = (if .random() { Int?.none } else { 1 as Int? })! + // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} +} +do { + func takesInts(_ x: Int...) {} + let _ = if .random() { takesInts } else { takesInts }(1, 2, 3) + // expected-error@-1 {{consecutive statements on a line must be separated by ';'}} + // expected-warning@-2 {{expression of type '(Int, Int, Int)' is unused}} + + let _ = (if .random() { takesInts } else { takesInts })(1, 2, 3) + // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} +} +func testSubscriptPostfix(_ x: HasSubscript) { + if .random() { x } else { x }[1, 2, 3] + // expected-error@-1 {{consecutive statements on a line must be separated by ';'}} + // expected-warning@-2 {{expression of type 'HasSubscript' is unused}} + // expected-warning@-3 {{expression of type '[Int]' is unused}} + // expected-warning@-4 {{expression of type 'HasSubscript' is unused}} + + (if .random() { x } else { x })[1, 2, 3] + // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} +} +do { + func takesClosure(_ fn: () -> Int) {} + + let _ = if .random() { takesClosure } else { takesClosure } { 3 } + // expected-error@-1 {{getter/setter can only be defined for a single variable}} + + let _ = (if .random() { takesClosure } else { takesClosure }) { 3 } + // expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} +} + +// MARK: Statements + +func stmts() { + if 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}} + + if try 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}} + // expected-warning@-2 {{'try' on an 'if' expression has no effect}} + + // expected-error@+1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + guard if .random() { true } else { false } else { + return + } + + switch if .random() { true } else { false } { // expected-error {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + case _ where if .random() { true } else { false }: // expected-error {{'if' may only be used as expression in return, throw, or as the source of an assignment}} + break + default: + break + } + + for b in [true] where if b { 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 { + case x: if .random() { 1 } else { 2 } + // expected-warning@-1 2{{integer literal is unused}} + default: + break + } +} + +// MARK: Non-expression branches + +func noElse() -> Int { + // Not an expression because no else. + if .random() { + 0 // expected-warning {{integer literal is unused}} + } + 1 // expected-warning {{integer literal is unused}} +} + +func returnBranches() -> Int { + // This is not an expression because the branches are not expressions. + if .random() { + return 0 + } else { + return 1 + } +} + +func returnBranches1() -> Int { + return if .random() { // expected-error {{cannot convert return expression of type 'Void' to return type 'Int'}} + 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}} + } +} + +func returnBranches2() -> Int { + // We don't allow multiple expressions. + if .random() { + print("hello") + 0 // expected-warning {{integer literal is unused}} + } else { + 1 // expected-warning {{integer literal is unused}} + } +} + +func returnBranches3() -> Int { + if .random() { + print("hello") + return 0 + } else { + 1 // expected-warning {{integer literal is unused}} + } +} + +func returnBranches4() -> Int { + if .random() { return 1 } else { 0 } // expected-warning {{integer literal is unused}} +} + +struct Err: Error {} + +func returnBranches5() throws -> Int { + let i = if .random() { + // expected-warning@-1 {{constant 'i' 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}} + } + let j = if .random() { + // expected-warning@-1 {{constant 'j' inferred to have type 'Void', which may be unexpected}} + // expected-note@-2 {{add an explicit type annotation to silence this warning}} + throw Err() + } else { + throw Err() + } + return i // expected-error {{cannot convert return expression of type 'Void' to return type 'Int'}} +} + +func returnBranches6() -> Int { + // We don't allow multiple expressions. + let i = if .random() { + print("hello") + 0 // expected-warning {{integer literal is unused}} + } else { // expected-error {{non-expression branch of 'if' expression may only end with a 'throw'}} + 1 + } + return i +} + +func returnBranches7() -> Int { + let i = if .random() { + print("hello") + return 0 // expected-error {{cannot 'return' in 'if' when used as expression}} + } else { + 1 + } + return i +} + +func returnBranches8() -> Int { + let i = if .random() { return 1 } else { 0 } // expected-error {{cannot 'return' in 'if' when used as expression}} + return i +} + +func returnBranches9() -> Int { + let i = if .random() { + print("hello") + if .random() {} + } else { // expected-error {{non-expression branch of 'if' expression may only end with a 'throw'}} + 1 + } + return i +} + +func returnBranches10() -> Int { + let i = if .random() { + print("hello") + if .random() { + 0 // expected-warning {{integer literal is unused}} + } else { + 2 // expected-warning {{integer literal is unused}} + } + } else { // expected-error {{non-expression branch of 'if' expression may only end with a 'throw'}} + 1 + } + return i +} + +func returnBranches11() -> Int { + let i = if .random() { + print("hello") + if .random() { + "" // expected-warning {{string literal is unused}} + } else { + 0 // expected-warning {{integer literal is unused}} + } + } else { // expected-error {{non-expression branch of 'if' expression may only end with a 'throw'}} + 1 + } + return i +} + +func returnBranches12() -> Int { + if .random() { + print("hello") + if .random() {} + } else { + 1 // expected-warning {{integer literal is unused}} + } +} + +func returnBranches13() -> Int { + if .random() { + print("hello") + if .random() { + 0 // expected-warning {{integer literal is unused}} + } else { + 2 // expected-warning {{integer literal is unused}} + } + } else { + 1 // expected-warning {{integer literal is unused}} + } +} + +func returnBranches14() -> Int { + if .random() { + print("hello") + if .random() { + "" // expected-warning {{string literal is unused}} + } else { + 0 // expected-warning {{integer literal is unused}} + } + } else { + 1 // expected-warning {{integer literal is unused}} + } +} + +func nestedType() -> Int { + if .random() { + struct S { + var x: Int + } + return S(x: 0).x + } else { + 0 // expected-warning {{integer literal is unused}} + } +} + +// MARK: Jumping + +func break1() -> Int { + switch true { + case true: + let j = if .random() { + break // expected-error {{cannot 'break' in 'if' when used as expression}} + } else { + 0 + } + return j + case false: + return 0 + } +} + +func continue1() -> Int { + for _ in 0 ... 5 { + let i = if true { continue } else { 1 } + // expected-error@-1 {{cannot 'continue' in 'if' when used as expression}} + return i + } +} + +func return1() -> Int { + // Make sure we always reject a return. + let i = if .random() { + do { + for _ in [0] { + while true { + switch 0 { + default: + return 0 // expected-error {{cannot 'return' in 'if' when used as expression}} + } + } + } + } + } else { + 0 + } + return i +} + +func return2() throws -> Int { + // In a nested function is okay though. + let i = if .random() { + func foo() { return } + throw Err() + } else { + 0 + } + return i +} + +func return3() throws -> Int { + // A nested type is also okay. + let i = if .random() { + struct Nested { + func foo() { return } + } + throw Err() + } else { + 0 + } + return i +} + +func return4() throws -> Int { + // A nested closure is also okay. + let i = if .random() { + let _ = { return } + throw Err() + } else { + 0 + } + return i +} + +// MARK: Effect specifiers + +func tryIf1() -> Int { + try if .random() { 0 } else { 1 } + // expected-warning@-1 {{'try' on an 'if' expression has no effect}} +} + +func tryIf2() -> Int { + let x = try if .random() { 0 } else { 1 } + // expected-warning@-1 {{'try' on an 'if' expression has no effect}} + return x +} + +func tryIf3() -> Int { + return try if .random() { 0 } else { 1 } + // expected-warning@-1 {{'try' on an 'if' expression has no effect}} +} + +func awaitIf1() async -> Int { + await if .random() { 0 } else { 1 } + // expected-warning@-1 {{'await' on an 'if' expression has no effect}} +} + +func awaitIf2() async -> Int { + let x = await if .random() { 0 } else { 1 } + // expected-warning@-1 {{'await' on an 'if' expression has no effect}} + return x +} + +func awaitIf3() async -> Int { + return await if .random() { 0 } else { 1 } + // expected-warning@-1 {{'await' on an 'if' expression has no effect}} +} diff --git a/test/expr/unary/if_expr_global_self_ref.swift b/test/expr/unary/if_expr_global_self_ref.swift new file mode 100644 index 0000000000000..62aa936a1c42a --- /dev/null +++ b/test/expr/unary/if_expr_global_self_ref.swift @@ -0,0 +1,5 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-feature StatementExpressions + +let x = if .random() { x } else { 0 } +// expected-error@-1 {{cannot reference invalid declaration 'x'}} +// expected-note@-2 {{'x' declared here}} diff --git a/test/expr/unary/single_value_stmt_expr_source_breaks.swift b/test/expr/unary/single_value_stmt_expr_source_breaks.swift new file mode 100644 index 0000000000000..a1035ed241a31 --- /dev/null +++ b/test/expr/unary/single_value_stmt_expr_source_breaks.swift @@ -0,0 +1,21 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-feature StatementExpressions + +struct Err: Error {} + +func foo() throws -> Int { + // We actually do some constant evaluation before unreachability checking, + // so this used to be legal. + switch true { + case true: + throw Err() + case false: + () // expected-error {{cannot convert value of type '()' to specified type 'Int}} + } +} + +func bar() { + // This used to be an unreachable 'if' after a return. + return + if .random() { 0 } else { 1 } + // expected-error@-1 {{cannot convert value of type 'Int' to specified type '()'}} +} diff --git a/test/expr/unary/switch_expr.swift b/test/expr/unary/switch_expr.swift new file mode 100644 index 0000000000000..6b40d6c8f5f06 --- /dev/null +++ b/test/expr/unary/switch_expr.swift @@ -0,0 +1,913 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-feature StatementExpressions -disable-availability-checking + +// MARK: Functions + +func foo() -> Int { + switch Bool.random() { + case true: + 1 + case false: + 2 + } +} + +func foo2() -> Int { + return switch Bool.random() { + case true: + 1 + case false: + 2 + } +} + +func foo3() -> Int { + switch Bool.random() { + case true: + 1 + case false: + 2 + } as Int +} + +func foo4() -> Int { + return switch Bool.random() { + case true: + 1 + case false: + 2 + } as Int +} + +func foo5() -> String { + switch Bool.random() { + case true: + 1 // expected-error {{cannot convert value of type 'Int' to specified type 'String'}} + case false: + 2 + } as String +} + +func foo6() -> Int { + return (switch Bool.random() { case true: 1 case false: 2 } as Int) + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} +} + +func bar() -> Int { + switch Bool.random() { + case true: + fatalError() + case false: + 2 + } +} + +func baz() -> Int { + switch Bool.random() { + case true where switch Bool.random() { case true: false case false: true }: + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + 1 + case false where 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}} + 2 + default: + 3 + } +} + +func qux() -> Int { + switch Bool.random() { + case true: + "" // expected-error {{cannot convert value of type 'String' to specified type 'Int'}} + case false: + 0 + } +} + +func takesValue(_ x: T) {} + +// expected-error@+1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} +takesValue(switch Bool.random() { +case true: + 1 +case false: + 2 +}) +takesValue(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}} + +// Cannot parse labeled switch as expression. +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}} + // expected-error@-2 {{expected ',' separator}} + // expected-error@-3 {{cannot find 'x' in scope}} +} +func takesValueWithLabel(x: T) {} +do { + takesValueWithLabel(x: 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}} + + takesValueWithLabel(x: y: switch Bool.random() { case true: 1 case false: 2 }) + // expected-error@-1 {{expected expression in list of expressions}} + // expected-error@-2 {{expected ',' separator}} + // expected-error@-3 {{cannot find 'y' in scope}} +} +func takesValueAndTrailingClosure(_ x: T, _ fn: () -> Int) {} +takesValueAndTrailingClosure(switch Bool.random() { case true: 0 case false: 1 }) { 2 } +// expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + +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 { () } + subscript(x: Int...) -> Void { () } +} +HasSubscript[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}} + +func proposalExample1(_ x: Unicode.Scalar) -> Int { + switch x.value { + case 0..<0x80: 1 + case 0x80..<0x0800: 2 + case 0x0800..<0x1_0000: 3 + default: 4 + } +} + +// MARK: Explicit returns + +func explicitReturn1() -> Int { + print("hello") + return switch Bool.random() { case true: 1 case false: 2 } +} + +func explicitReturn2() -> Int { + return + switch Bool.random() { case true: 1 case false: 2 } + // expected-warning@-1 {{expression following 'return' is treated as an argument of the 'return'}} + // expected-note@-2 {{indent the expression to silence this warning}} +} + +func explicitReturn3() -> Int { + return + switch Bool.random() { case true: 1 case false: 2 } +} + +func explicitReturn4() { + // This used to be legal, but is now treated as a return of the if expression. + return + switch Bool.random() { case true: 1 case false: 2 } // expected-error {{cannot convert value of type 'Int' to specified type '()'}} +} + +func explicitReturn5() { + return; + switch Bool.random() { case true: 1 case false: 2 } // expected-warning 2{{integer literal is unused}} +} + +func explicitReturn6() { + return () + switch Bool.random() { case true: 1 case false: 2 } // expected-warning 2{{integer literal is unused}} +} + +struct AsPropertyInit { + var x: Int = switch Bool.random() { case true: 1 case false: 0 } + var y = switch Bool.random() { case true: 1 case false: 0 } +} + +func testNestedAssignment() { + var x = 0 + x = switch Bool.random() { case true: 0 case false: 1 } // Okay + let fn = { + x = switch Bool.random() { case true: 0 case false: 1 } // Also okay + } + + // We don't allow in a nested assignment. + // TODO: We could improve this error. + print(x = 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}} + + _ = x; _ = fn +} + +struct TestFailableInit { + init?(_ x: Bool) { + let y = switch x { + case true: + 0 + case false: + return nil // expected-error {{cannot 'return' in 'switch' when used as expression}} + } + _ = y + } +} + +struct TestFailableInitFatalError { + init?(_ x: Int) { + // In this case, the switch does not become an expression. + switch x { + case 0: + fatalError() + default: + return nil + } + } +} + +// MARK: Expressions + +let a = switch Bool.random() { +case true: + 0 +case false: + 1 +} + +let b = (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}} + +let c: (Int, k: Int) = (switch Bool.random() { case true: 1 case false: 2 }, + k: switch Bool.random() { case true: 1 case false: 2 }) +// expected-error@-2 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} +// expected-error@-2 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + +let d = switch Bool.random() { +case true: + switch Bool.random() { + case true: + 1 + case false: + 2 + } +case false: + 3 +} + +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}} + +let f = { switch Bool.random() { case true: 1 case false: 2 } } + +func throwsError() throws { + struct E: Error {} + throw switch Bool.random() { case true: E() case false: E() } +} + +let g = [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}} + +let h = [switch Bool.random() { case true: 1 case false: 2 } : switch Bool.random() { case true: "a" case false: "b" }] +// expected-error@-1 2{{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + +let i = [switch Bool.random() { case true: 1 case false: 2 }: switch Bool.random() { case true: "a" case false: "b" }] +// expected-error@-1 2{{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + +let j = [switch Bool.random() { case true: 1 case false: 2 }:switch Bool.random() { case true: "a" case false: "b" }] +// expected-error@-1 2{{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + +let k = [switch Bool.random() { case true: 1 case false: 2 } :switch Bool.random() { case true: "a" case false: "b" }] +// expected-error@-1 2{{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + + +let l = switch Bool.random() { case true: 1 case false: 2 } as Any + +let _ = type(of: 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}} + +do { + switch Bool.random() { case true: 1 case false: 2 } = 3 + // expected-error@-1 {{consecutive statements on a line must be separated by ';'}} + // expected-error@-2 {{expected expression}} + // expected-warning@-3 2{{integer literal is unused}} +} + +// We currently prefer to parse these as trailing closures. We may want to tell +// 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}} + + _ = (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}} + 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}} + 2 + #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}} + 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}} + 2 + #else + 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}} + 2 + #endif + }, 0) // expected-error {{expected '{' after 'switch' subject expression}} +} + +// These are syntatically okay because the #if starts on a newline. This seems +// like the common case. +_ = (switch Bool.random() { + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + #if FOO + #else +case true: + 2 +case false: + 3 + #endif +}, 0) + +_ = (switch Bool.random() { + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + // expected-error@-2 {{switch must be exhaustive}} + // expected-note@-3 {{add missing case: 'true'}} + // expected-note@-4 {{add missing case: 'false'}} + #if FOO +case true: + 0 +case false: + 1 + #else + #endif +}, 0) + +_ = (switch Bool.random() { + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + // expected-error@-2 {{switch must be exhaustive}} + // expected-note@-3 {{add missing case: 'true'}} + // expected-note@-4 {{add missing case: 'false'}} + #if FOO +case true: + 0 +case false: + 1 + #endif +}, 0) + +_ = (switch fatalError() { + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + #if FOO + #endif +}, 0) + +func testEmptySwitch() { + let x = switch fatalError() {} + // 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}} + + func takesClosure(_ fn: () -> T) -> T { fn() } + let y = takesClosure { switch fatalError() {} } + // expected-warning@-1 {{constant 'y' inferred to have type '()', which may be unexpected}} + // expected-note@-2 {{add an explicit type annotation to silence this warning}} + + _ = x; _ = y +} + +func testConditionalBinding1(_ x: Int?) -> Int { + if let x = switch Bool.random() { case true: 0 case false: Int?.none } { // expected-error {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + x + } else { + 0 + } +} + +func testConditionalBinding2(_ x: Int?) -> Int { + if case let x? = switch Bool.random() { case true: 0 case false: Int?.none } { // expected-error {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + x + } else { + 0 + } +} + +// MARK: Operators + +let m = !switch Bool.random() { case true: true case false: true } +// expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + +// FIXME: Shouldn't be ambiguous +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 p = .random() ? switch Bool.random() { case true: 1 case false: 2 } + : switch Bool.random() { case true: 3 case false: 4 } +// expected-error@-2 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} +// expected-error@-2 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + +let q = switch Bool.random() { case true: 1 case false: 2 }...switch Bool.random() { case true: 1 case false: 2 } +// expected-error@-1 2{{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + +let r = switch Bool.random() { case true: 1 case false: 2 } ... switch Bool.random() { case true: 1 case false: 2 } +// expected-error@-1 2{{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + +// MARK: Lookup + +do { + let s = switch Bool.random() { case true: s case false: 0 } + // expected-error@-1 {{use of local variable 's' before its declaration}} + // expected-note@-2 {{'s' declared here}} +} + +// MARK: Postfix + +// We don't allow postfix parsing. +do { + let _ = switch Bool.random() { case true: [1] case false: [1, 2] }.count + // expected-error@-1 {{consecutive statements on a line must be separated by ';'}} + // expected-error@-2 {{reference to member 'count' cannot be resolved without a contextual type}} + + let _ = (switch Bool.random() { case true: [1] case false: [1, 2] }).count + // expected-error@-1 {{'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 }?.bitWidth + // expected-error@-1 {{consecutive statements on a line must be separated by ';'}} + // expected-error@-2 {{expected expression}} + + 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}} +} +do { + let _ = switch Bool.random() { case true: Int?.none case false: 1 }! + // expected-error@-1 {{consecutive statements on a line must be separated by ';'}} + // expected-error@-2 {{expected expression}} + + let _ = (switch Bool.random() { case true: Int?.none case false: 1 })! + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} +} +do { + func fn(_ x: Int...) {} + + let _ = switch Bool.random() { case true: fn case false: fn }(1, 2, 3) + // expected-error@-1 {{consecutive statements on a line must be separated by ';'}} + // expected-warning@-2 {{expression of type '(Int, Int, Int)' is unused}} + + let _ = (switch Bool.random() { case true: fn case false: fn })(1, 2, 3) + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} +} +func takesSubscript(_ x: HasSubscript) { + let _ = switch Bool.random() { case true: x case false: x }[1, 2, 3] + // expected-error@-1 {{consecutive statements on a line must be separated by ';'}} + // expected-warning@-2 {{expression of type '[Int]' is unused}} + + let _ = (switch Bool.random() { case true: x case false: x })[1, 2, 3] + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} +} +do { + func fn(_ fn: () -> Int) {} + + let _ = switch Bool.random() { case true: fn case false: fn } { 3 } + // expected-error@-1 {{getter/setter can only be defined for a single variable}} + + let _ = (switch Bool.random() { case true: fn case false: fn }) { 3 } + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} +} + +// MARK: Statements + +func stmts() { + if switch Bool.random() { case true: true case false: true } {} + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + + if try switch Bool.random() { case true: true case false: true } {} + // expected-warning@-1 {{'try' on an 'switch' expression has no effect}} + // expected-error@-2 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + + // expected-error@+1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + guard switch Bool.random() { case true: true case false: true } else { + return + } + + switch switch Bool.random() { case true: true case false: true } { + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + case _ where switch Bool.random() { case true: true case false: true }: + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + break + default: + break + } + + for b in [true] where switch b { case true: true case false: false } {} + // expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}} + + // Make sure this doesn't parse as a switch expr pattern with a label. + let x = 0 + switch 0 { + case x: switch Bool.random() { case true: 1 case false: 2 } + // expected-warning@-1 2{{integer literal is unused}} + default: + break + } +} + +// MARK: Non-expression branches + +func returnBranches() -> Int { + // This is not an expression because the branches are not expressions. + switch Bool.random() { + case true: + return 0 + case false: + return 1 + } +} + +func returnBranches1() -> Int { + return switch Bool.random() { // expected-error {{cannot convert return expression of type 'Void' to return type 'Int'}} + 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}} + } +} + +func returnBranches2() -> Int { + // We don't allow multiple expressions. + switch Bool.random() { + case true: + print("hello") + 0 // expected-warning {{integer literal is unused}} + case false: + 1 // expected-warning {{integer literal is unused}} + } +} + +func returnBranches3() -> Int { + switch Bool.random() { + case true: + print("hello") + return 0 + case false: + 1 // expected-warning {{integer literal is unused}} + } +} + +func returnBranches4() -> Int { + switch Bool.random() { + case true: + return 1 + case false: + 0 // expected-warning {{integer literal is unused}} + } +} + +func returnBranches5() -> Int { + let i = switch Bool.random() { + // expected-warning@-1 {{constant 'i' 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 i // expected-error {{cannot convert return expression of type 'Void' to return type 'Int'}} +} + +func returnBranches6() -> Int { + // We don't allow multiple expressions. + let i = switch Bool.random() { + case true: + print("hello") + 0 // expected-warning {{integer literal is unused}} + // expected-error@-1 {{non-expression branch of 'switch' expression may only end with a 'throw'}} + case false: + 1 + } + return i +} + +func returnBranches7() -> Int { + let i = switch Bool.random() { + case true: + print("hello") + return 0 // expected-error {{cannot 'return' in 'switch' when used as expression}} + case false: + 1 + } + return i +} + +func returnBranches8() -> Int { + let i = switch Bool.random() { + case true: + return 1 // expected-error {{cannot 'return' in 'switch' when used as expression}} + case false: + 0 + } + return i +} + +func returnBranches9() -> Int { + let i = switch Bool.random() { + case true: + print("hello") + if .random() {} // expected-error {{non-expression branch of 'switch' expression may only end with a 'throw'}} + case false: + 1 + } + return i +} + +func returnBranches10() -> Int { + let i = switch Bool.random() { + case true: + print("hello") + switch Bool.random() { + case true: + 0 // expected-warning {{integer literal is unused}} + case false: + 2 // expected-warning {{integer literal is unused}} + } // expected-error {{non-expression branch of 'switch' expression may only end with a 'throw'}} + case false: + 1 + } + return i +} + +func returnBranches11() -> Int { + let i = switch Bool.random() { + case true: + print("hello") + switch Bool.random() { + case true: + "" // expected-warning {{string literal is unused}} + case false: + 2 // expected-warning {{integer literal is unused}} + } // expected-error {{non-expression branch of 'switch' expression may only end with a 'throw'}} + case false: + 1 + } + return i +} + +func returnBranches12() -> Int { + switch Bool.random() { + case true: + print("hello") + if .random() {} + case false: + 1 // expected-warning {{integer literal is unused}} + } +} + +func returnBranches13() -> Int { + switch Bool.random() { + case true: + print("hello") + switch Bool.random() { + case true: + 0 // expected-warning {{integer literal is unused}} + case false: + 2 // expected-warning {{integer literal is unused}} + } + case false: + 1 // expected-warning {{integer literal is unused}} + } +} + +func returnBranches14() -> Int { + switch Bool.random() { + case true: + print("hello") + switch Bool.random() { + case true: + "" // expected-warning {{string literal is unused}} + case false: + 2 // expected-warning {{integer literal is unused}} + } + case false: + 1 // expected-warning {{integer literal is unused}} + } +} + +func doStatementBranch() -> Int { + switch Bool.random() { + case true: + 0 // expected-warning {{integer literal is unused}} + case false: + do {} + } +} + +func genericReturnWhileTrueBranch() { + enum E { + case x(T), y + + func foo() -> E { + switch self { + case .x: + while true {} + case .y: + fatalError() + } + } + } +} + +struct NestedInFailableInit { + init?(_ b: Bool) { + // This is okay, it's treated as a statement. + switch b { + case true: + switch b { + case true: + return nil + case false: + fatalError() + } + case false: + fatalError() + } + } +} + +func nestedType() -> Int { + switch Bool.random() { + case true: + struct S { + var x: Int + } + return S(x: 0).x + case false: + 0 // expected-warning {{integer literal is unused}} + } +} + +// MARK: Jumping + +func break1() -> Int { + switch true { + case true: + break + case false: + 0 // expected-warning {{integer literal is unused}} + } + return 1 +} + +func fallthrough1() -> Int { + switch true { + case true: + if .random() { + fallthrough + } + return 1 + case false: + 0 // expected-warning {{integer literal is unused}} + } +} + +func fallthrough2() -> Int { + let x = switch true { + case true: + if .random() { + fallthrough + } + return 1 // expected-error {{cannot 'return' in 'switch' when used as expression}} + case false: + 0 + } + return x +} + +func breakAfterNeverExpr() -> String { + // We avoid turning this into a switch expression because of the 'break'. + switch Bool.random() { + case true: + if .random() { + fatalError() // or while true {} + break + } + return "" + case false: + fatalError() + } +} + +func nonExhaustive() -> String { + switch Bool.random() { + // expected-error@-1 {{switch must be exhaustive}} + // expected-note@-2 {{add missing case: 'false'}} + case true: + "hello" + } +} + +func nonExhaustiveInClosure() -> String { + let fn = { + switch Bool.random() { + // expected-error@-1 {{switch must be exhaustive}} + // expected-note@-2 {{add missing case: 'false'}} + case true: + "hello" + } + } + return fn() +} + +func breakToInner() -> Int { + switch Bool.random() { + case true: + // These are fine, they're inner breaks. + y: switch Bool.random() { + case true: + break + case false: + switch Bool.random() { + case true: break y + case false: break + } + } + return 0 + case false: + 1 // expected-warning {{integer literal is unused}} + } +} + +func continueToInner() -> Int { + switch Bool.random() { + case true: + // These are fine, they're inner breaks/continues. + y: for x in [0] { + for y in [""] { + if x == 0 { + break y + } else if y == "hello" { + continue y + } else if .random() { + continue + } else { + break + } + } + } + return 0 + case false: + 1 // expected-warning {{integer literal is unused}} + } +} + +// MARK: Effect specifiers + +func trySwitch1() -> Int { + try switch Bool.random() { case true: 0 case false: 1 } + // expected-warning@-1 {{'try' on an 'switch' expression has no effect}} +} + +func trySwitch2() -> Int { + let x = try switch Bool.random() { case true: 0 case false: 1 } + // expected-warning@-1 {{'try' on an 'switch' expression has no effect}} + return x +} + +func trySwitch3() -> Int { + return try switch Bool.random() { case true: 0 case false: 1 } + // expected-warning@-1 {{'try' on an 'switch' expression has no effect}} +} + +func awaitSwitch1() async -> Int { + await switch Bool.random() { case true: 0 case false: 1 } + // expected-warning@-1 {{'await' on an 'switch' expression has no effect}} +} + +func awaitSwitch2() async -> Int { + let x = await switch Bool.random() { case true: 0 case false: 1 } + // expected-warning@-1 {{'await' on an 'switch' expression has no effect}} + return x +} + +func awaitSwitch3() async -> Int { + return await switch Bool.random() { case true: 0 case false: 1 } + // expected-warning@-1 {{'await' on an 'switch' expression has no effect}} +} diff --git a/test/expr/unary/switch_expr_global_self_ref.swift b/test/expr/unary/switch_expr_global_self_ref.swift new file mode 100644 index 0000000000000..1adefd470f07e --- /dev/null +++ b/test/expr/unary/switch_expr_global_self_ref.swift @@ -0,0 +1,5 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-feature StatementExpressions + +let x = switch Bool.random() { case true: x case false: 0 } +// expected-error@-1 {{cannot reference invalid declaration 'x'}} +// expected-note@-2 {{'x' declared here}} diff --git a/utils/swift_build_support/swift_build_support/products/cmake_product.py b/utils/swift_build_support/swift_build_support/products/cmake_product.py index 7c4acb35d2470..5e119e366c28e 100644 --- a/utils/swift_build_support/swift_build_support/products/cmake_product.py +++ b/utils/swift_build_support/swift_build_support/products/cmake_product.py @@ -408,9 +408,9 @@ def host_cmake_options(self, host_target): swift_cmake_options.define('SWIFT_HOST_VARIANT_ARCH', swift_host_variant_arch) llvm_cmake_options.define('LLVM_LIT_ARGS', '{} -j {}'.format( - self.args.lit_args, self.args.build_jobs)) + self.args.lit_args, self.args.lit_jobs)) swift_cmake_options.define('LLVM_LIT_ARGS', '{} -j {}'.format( - self.args.lit_args, self.args.build_jobs)) + self.args.lit_args, self.args.lit_jobs)) if self.args.clang_profile_instr_use: llvm_cmake_options.define('LLVM_PROFDATA_FILE', diff --git a/validation-test/Sema/type_checker_perf/fast/if_expr.swift.gyb b/validation-test/Sema/type_checker_perf/fast/if_expr.swift.gyb new file mode 100644 index 0000000000000..b96c8df6cdf5b --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/if_expr.swift.gyb @@ -0,0 +1,35 @@ +// RUN: %scale-test --begin 10 --end 100 --step 15 --select NumLeafScopes %s +// REQUIRES: asserts,no_asan + +precedencegroup P { + associativity: right +} + +infix operator ^^^ : P +func ^^^ (lhs: Int, rhs: Int) -> Int { 0 } +func ^^^ (lhs: Int8, rhs: Int8) -> Int8 { 0 } +func ^^^ (lhs: Int16, rhs: Int16) -> Int16 { 0 } +func ^^^ (lhs: Int32, rhs: Int32) -> Int32 { 0 } +func ^^^ (lhs: Int64, rhs: Int64) -> Int64 { 0 } + +func ^^^ (lhs: UInt, rhs: UInt) -> UInt { 0 } +func ^^^ (lhs: UInt8, rhs: UInt8) -> UInt8 { 0 } +func ^^^ (lhs: UInt16, rhs: UInt16) -> UInt16 { 0 } +func ^^^ (lhs: UInt32, rhs: UInt32) -> UInt32 { 0 } +func ^^^ (lhs: UInt64, rhs: UInt64) -> UInt64 { 0 } + +func ^^^ (lhs: Double, rhs: Double) -> Double { 0 } +func ^^^ (lhs: Float, rhs: Float) -> Float { 0 } + +// Make sure solving scales linearly with the number of branches. +let _ = if .random() { + 0 ^^^ 1 ^^^ 2 +} +%for i in range(0, N): +else if .random() { + 3 ^^^ 4 ^^^ 5 +} +%end +else { + 1 +} diff --git a/validation-test/Sema/type_checker_perf/fast/switch_expr.swift.gyb b/validation-test/Sema/type_checker_perf/fast/switch_expr.swift.gyb new file mode 100644 index 0000000000000..d56626f428419 --- /dev/null +++ b/validation-test/Sema/type_checker_perf/fast/switch_expr.swift.gyb @@ -0,0 +1,34 @@ +// RUN: %scale-test --begin 10 --end 100 --step 15 --select NumLeafScopes %s +// REQUIRES: asserts,no_asan + +precedencegroup P { + associativity: right +} + +infix operator ^^^ : P +func ^^^ (lhs: Int, rhs: Int) -> Int { 0 } +func ^^^ (lhs: Int8, rhs: Int8) -> Int8 { 0 } +func ^^^ (lhs: Int16, rhs: Int16) -> Int16 { 0 } +func ^^^ (lhs: Int32, rhs: Int32) -> Int32 { 0 } +func ^^^ (lhs: Int64, rhs: Int64) -> Int64 { 0 } + +func ^^^ (lhs: UInt, rhs: UInt) -> UInt { 0 } +func ^^^ (lhs: UInt8, rhs: UInt8) -> UInt8 { 0 } +func ^^^ (lhs: UInt16, rhs: UInt16) -> UInt16 { 0 } +func ^^^ (lhs: UInt32, rhs: UInt32) -> UInt32 { 0 } +func ^^^ (lhs: UInt64, rhs: UInt64) -> UInt64 { 0 } + +func ^^^ (lhs: Double, rhs: Double) -> Double { 0 } +func ^^^ (lhs: Float, rhs: Float) -> Float { 0 } + +// Make sure solving scales linearly with the number of branches. +let _ = switch 0 { +case -1: + 0 ^^^ 1 ^^^ 2 +%for i in range(0, N): +case ${i}: + 3 ^^^ 4 ^^^ 5 +%end +default: + 1 +}