Skip to content

Commit 5e70912

Browse files
committed
Introduce if/switch expressions
Introduce SingleValueStmtExpr, which allows the embedding of a statement in an expression context. This then allows us to parse and type-check `if` and `switch` statements as expressions, gated behind the `IfSwitchExpression` experimental feature for now. In the future, SingleValueStmtExpr could also be used for e.g `do` expressions. For now, only single expression branches are supported for producing a value from an `if`/`switch` expression, and each branch is type-checked independently. A multi-statement branch may only appear if it ends with a `throw`, and it may not `break`, `continue`, or `return`. The placement of `if`/`switch` expressions is also currently limited by a syntactic use diagnostic. Currently they're only allowed in bindings, assignments, throws, and returns. But this could be lifted in the future if desired.
1 parent 4e81847 commit 5e70912

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+4782
-137
lines changed

include/swift/AST/ASTScope.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -955,6 +955,7 @@ class PatternEntryInitializerScope final : public AbstractPatternEntryScope {
955955

956956
protected:
957957
bool lookupLocalsOrMembers(DeclConsumer) const override;
958+
bool isLabeledStmtLookupTerminator() const override;
958959
};
959960

960961
/// The scope introduced by a conditional clause initializer in an

include/swift/AST/ASTTypeIDZone.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ SWIFT_TYPEID(Fingerprint)
2626
SWIFT_TYPEID(GenericSignature)
2727
SWIFT_TYPEID(ImplicitImportList)
2828
SWIFT_TYPEID(ImplicitMemberAction)
29+
SWIFT_TYPEID(IsSingleValueStmtResult)
2930
SWIFT_TYPEID(ParamSpecifier)
3031
SWIFT_TYPEID(PropertyWrapperAuxiliaryVariables)
3132
SWIFT_TYPEID(PropertyWrapperInitializerInfo)

include/swift/AST/ASTTypeIDs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class GenericParamList;
4141
class GenericSignature;
4242
class GenericTypeParamType;
4343
class InfixOperatorDecl;
44+
class IsSingleValueStmtResult;
4445
class IterableDeclContext;
4546
class ModuleDecl;
4647
struct ImplicitImportList;

include/swift/AST/CASTBridging.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ void *SwiftVarDecl_create(void *ctx, BridgedIdentifier _Nullable name,
169169
void *initExpr, void *loc, _Bool isStatic,
170170
_Bool isLet, void *dc);
171171

172+
void *SingleValueStmtExpr_createWithWrappedBranches(void *ctx, void *S,
173+
void *DC, _Bool mustBeExpr);
174+
172175
void *IfStmt_create(void *ctx, void *ifLoc, void *cond, void *_Nullable then,
173176
void *_Nullable elseLoc, void *_Nullable elseStmt);
174177

include/swift/AST/DiagnosticsSema.def

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,27 @@ ERROR(ternary_expr_cases_mismatch,none,
10801080
"result values in '? :' expression have mismatching types %0 and %1",
10811081
(Type, Type))
10821082

1083+
// Statements as expressions
1084+
ERROR(single_value_stmt_branches_mismatch,none,
1085+
"branches have mismatching types %0 and %1",
1086+
(Type, Type))
1087+
ERROR(single_value_stmt_out_of_place,none,
1088+
"'%0' may only be used as expression in return, throw, or as the source "
1089+
"of an assignment",
1090+
(StmtKind))
1091+
ERROR(single_value_stmt_must_be_unlabeled,none,
1092+
"'%0' cannot have a jump label when used as expression",
1093+
(StmtKind))
1094+
ERROR(if_expr_must_be_syntactically_exhaustive,none,
1095+
"'if' must have an unconditional 'else' to be used as expression",
1096+
())
1097+
ERROR(single_value_stmt_branch_must_end_in_throw,none,
1098+
"non-expression branch of '%0' expression may only end with a 'throw'",
1099+
(StmtKind))
1100+
ERROR(cannot_jump_in_single_value_stmt,none,
1101+
"cannot '%0' in '%1' when used as expression",
1102+
(StmtKind, StmtKind))
1103+
10831104
ERROR(did_not_call_function_value,none,
10841105
"function value was used as a property; add () to call it",
10851106
())

include/swift/AST/Expr.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5971,6 +5971,58 @@ class KeyPathDotExpr : public Expr {
59715971
}
59725972
};
59735973

5974+
/// An expression that may wrap a statement which produces a single value.
5975+
class SingleValueStmtExpr : public Expr {
5976+
public:
5977+
enum class Kind {
5978+
If, Switch
5979+
};
5980+
5981+
private:
5982+
Stmt *S;
5983+
DeclContext *DC;
5984+
5985+
SingleValueStmtExpr(Stmt *S, DeclContext *DC)
5986+
: Expr(ExprKind::SingleValueStmt, /*isImplicit*/ true), S(S), DC(DC) {}
5987+
5988+
public:
5989+
/// Creates a new SingleValueStmtExpr wrapping a statement.
5990+
static SingleValueStmtExpr *create(ASTContext &ctx, Stmt *S, DeclContext *DC);
5991+
5992+
/// Creates a new SingleValueStmtExpr wrapping a statement, and recursively
5993+
/// attempts to wrap any branches of that statement that can become single
5994+
/// value statement expressions.
5995+
///
5996+
/// If \p mustBeExpr is true, branches will be eagerly wrapped even if they
5997+
/// may not be valid SingleValueStmtExprs (which Sema will later diagnose).
5998+
static SingleValueStmtExpr *createWithWrappedBranches(ASTContext &ctx,
5999+
Stmt *S,
6000+
DeclContext *DC,
6001+
bool mustBeExpr);
6002+
/// Retrieve the wrapped statement.
6003+
Stmt *getStmt() const { return S; }
6004+
void setStmt(Stmt *newS) { S = newS; }
6005+
6006+
/// Retrieve the kind of statement being wrapped.
6007+
Kind getStmtKind() const;
6008+
6009+
/// Retrieve the complete set of branches for the underlying statement.
6010+
ArrayRef<Stmt *> getBranches(SmallVectorImpl<Stmt *> &scratch) const;
6011+
6012+
/// Retrieve the single expression branches of the statement, excluding
6013+
/// branches that either have multiple expressions, or have statements.
6014+
ArrayRef<Expr *>
6015+
getSingleExprBranches(SmallVectorImpl<Expr *> &scratch) const;
6016+
6017+
DeclContext *getDeclContext() const { return DC; }
6018+
6019+
SourceRange getSourceRange() const;
6020+
6021+
static bool classof(const Expr *E) {
6022+
return E->getKind() == ExprKind::SingleValueStmt;
6023+
}
6024+
};
6025+
59746026
/// Expression node that effects a "one-way" constraint in
59756027
/// the constraint system, allowing type information to flow from the
59766028
/// subexpression outward but not the other way.

include/swift/AST/ExprNodes.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ EXPR(LazyInitializer, Expr)
205205
EXPR(EditorPlaceholder, Expr)
206206
EXPR(ObjCSelector, Expr)
207207
EXPR(KeyPath, Expr)
208+
EXPR(SingleValueStmt, Expr)
208209
UNCHECKED_EXPR(KeyPathDot, Expr)
209210
UNCHECKED_EXPR(OneWay, Expr)
210211
EXPR(Tap, Expr)

include/swift/AST/Stmt.h

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,15 @@ class ASTContext;
3535
class ASTWalker;
3636
class Decl;
3737
class DeclContext;
38+
class Evaluator;
3839
class Expr;
3940
class FuncDecl;
4041
class Pattern;
4142
class PatternBindingDecl;
4243
class VarDecl;
4344
class CaseStmt;
4445
class DoCatchStmt;
46+
class IsSingleValueStmtResult;
4547
class SwitchStmt;
4648

4749
enum class StmtKind {
@@ -133,7 +135,12 @@ class alignas(8) Stmt : public ASTAllocated<Stmt> {
133135

134136
SourceRange getSourceRange() const;
135137
SourceLoc TrailingSemiLoc;
136-
138+
139+
/// Whether the statement can produce a single value, and as such may be
140+
/// treated as an expression.
141+
IsSingleValueStmtResult mayProduceSingleValue(Evaluator &eval) const;
142+
IsSingleValueStmtResult mayProduceSingleValue(ASTContext &ctx) const;
143+
137144
/// isImplicit - Determines whether this statement was implicitly-generated,
138145
/// rather than explicitly written in the AST.
139146
bool isImplicit() const { return Bits.Stmt.Implicit; }
@@ -204,6 +211,10 @@ class BraceStmt final : public Stmt,
204211

205212
ASTNode findAsyncNode();
206213

214+
/// If this brace is wrapping a single expression, returns it. Otherwise
215+
/// returns \c nullptr.
216+
Expr *getSingleExpressionElement() const;
217+
207218
static bool classof(const Stmt *S) { return S->getKind() == StmtKind::Brace; }
208219
};
209220

@@ -711,7 +722,14 @@ class IfStmt : public LabeledConditionalStmt {
711722

712723
Stmt *getElseStmt() const { return Else; }
713724
void setElseStmt(Stmt *s) { Else = s; }
714-
725+
726+
/// Retrieve the complete set of branches for this if statement, including
727+
/// else if statements.
728+
ArrayRef<Stmt *> getBranches(SmallVectorImpl<Stmt *> &scratch) const;
729+
730+
/// Whether the if statement has an unconditional \c else.
731+
bool isSyntacticallyExhaustive() const;
732+
715733
// Implement isa/cast/dyncast/etc.
716734
static bool classof(const Stmt *S) { return S->getKind() == StmtKind::If; }
717735
};
@@ -1283,7 +1301,10 @@ class SwitchStmt final : public LabeledStmt,
12831301
AsCaseStmtRange getCases() const {
12841302
return AsCaseStmtRange(getRawCases(), AsCaseStmtWithSkippingNonCaseStmts());
12851303
}
1286-
1304+
1305+
/// Retrieve the complete set of branches for this switch statement.
1306+
ArrayRef<Stmt *> getBranches(SmallVectorImpl<Stmt *> &scratch) const;
1307+
12871308
static bool classof(const Stmt *S) {
12881309
return S->getKind() == StmtKind::Switch;
12891310
}

include/swift/AST/TypeCheckRequests.h

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3768,6 +3768,128 @@ class PreCheckReturnStmtRequest
37683768
bool isCached() const { return true; }
37693769
};
37703770

3771+
/// The result of the query for whether a statement can produce a single value.
3772+
class IsSingleValueStmtResult {
3773+
public:
3774+
enum class Kind {
3775+
/// The statement may become a SingleValueStmtExpr.
3776+
Valid,
3777+
3778+
/// There are non-single-expression branches that do not end in a throw.
3779+
UnterminatedBranches,
3780+
3781+
/// The statement is an 'if' statement without an unconditional 'else'.
3782+
NonExhaustiveIf,
3783+
3784+
/// There are no single-expression branches.
3785+
NoExpressionBranches,
3786+
3787+
/// There is an unhandled statement branch. This should only be the case
3788+
/// for invalid AST.
3789+
UnhandledStmt,
3790+
3791+
/// There was a circular reference when evaluating the request. This can be
3792+
/// ignored, as we will have already diagnosed it.
3793+
CircularReference,
3794+
3795+
/// There is a 'break' or 'continue' within the statement that prevents it
3796+
/// from being treated as an expression.
3797+
InvalidJumps,
3798+
3799+
/// The statement has a jump label, which is invalid for an expression.
3800+
HasLabel
3801+
};
3802+
3803+
private:
3804+
Kind TheKind;
3805+
TinyPtrVector<Stmt *> InvalidJumps;
3806+
TinyPtrVector<Stmt *> UnterminatedBranches;
3807+
3808+
IsSingleValueStmtResult(Kind kind) : TheKind(kind) {
3809+
assert(kind != Kind::UnterminatedBranches && kind != Kind::InvalidJumps);
3810+
}
3811+
3812+
IsSingleValueStmtResult(Kind kind, TinyPtrVector<Stmt *> stmts)
3813+
: TheKind(kind) {
3814+
switch (kind) {
3815+
case Kind::UnterminatedBranches: {
3816+
UnterminatedBranches = std::move(stmts);
3817+
break;
3818+
}
3819+
case Kind::InvalidJumps: {
3820+
InvalidJumps = std::move(stmts);
3821+
break;
3822+
}
3823+
default:
3824+
llvm_unreachable("Unhandled case in switch!");
3825+
}
3826+
}
3827+
3828+
public:
3829+
static IsSingleValueStmtResult valid() {
3830+
return IsSingleValueStmtResult(Kind::Valid);
3831+
}
3832+
static IsSingleValueStmtResult
3833+
unterminatedBranches(TinyPtrVector<Stmt *> branches) {
3834+
return IsSingleValueStmtResult(Kind::UnterminatedBranches,
3835+
std::move(branches));
3836+
}
3837+
static IsSingleValueStmtResult nonExhaustiveIf() {
3838+
return IsSingleValueStmtResult(Kind::NonExhaustiveIf);
3839+
}
3840+
static IsSingleValueStmtResult noExpressionBranches() {
3841+
return IsSingleValueStmtResult(Kind::NoExpressionBranches);
3842+
}
3843+
static IsSingleValueStmtResult unhandledStmt() {
3844+
return IsSingleValueStmtResult(Kind::UnhandledStmt);
3845+
}
3846+
static IsSingleValueStmtResult circularReference() {
3847+
return IsSingleValueStmtResult(Kind::CircularReference);
3848+
}
3849+
static IsSingleValueStmtResult invalidJumps(TinyPtrVector<Stmt *> jumps) {
3850+
return IsSingleValueStmtResult(Kind::InvalidJumps, std::move(jumps));
3851+
}
3852+
static IsSingleValueStmtResult hasLabel() {
3853+
return IsSingleValueStmtResult(Kind::HasLabel);
3854+
}
3855+
3856+
Kind getKind() const { return TheKind; }
3857+
3858+
/// For an unterminated branch kind, retrieves the branch.
3859+
const TinyPtrVector<Stmt *> &getUnterminatedBranches() const {
3860+
assert(TheKind == Kind::UnterminatedBranches);
3861+
return UnterminatedBranches;
3862+
}
3863+
3864+
/// For an invalid jump kind, retrieves the list of invalid jumps.
3865+
const TinyPtrVector<Stmt *> &getInvalidJumps() const {
3866+
assert(TheKind == Kind::InvalidJumps);
3867+
return InvalidJumps;
3868+
}
3869+
3870+
explicit operator bool() const {
3871+
return TheKind == Kind::Valid;
3872+
}
3873+
};
3874+
3875+
/// Computes whether a given statement can be treated as a SingleValueStmtExpr.
3876+
class IsSingleValueStmtRequest
3877+
: public SimpleRequest<IsSingleValueStmtRequest,
3878+
IsSingleValueStmtResult(const Stmt *),
3879+
RequestFlags::Cached> {
3880+
public:
3881+
using SimpleRequest::SimpleRequest;
3882+
3883+
private:
3884+
friend SimpleRequest;
3885+
3886+
IsSingleValueStmtResult
3887+
evaluate(Evaluator &evaluator, const Stmt *stmt) const;
3888+
3889+
public:
3890+
bool isCached() const { return true; }
3891+
};
3892+
37713893
class GetTypeWrapperInitializer
37723894
: public SimpleRequest<GetTypeWrapperInitializer,
37733895
ConstructorDecl *(NominalTypeDecl *),

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,9 @@ SWIFT_REQUEST(TypeChecker, ContinueTargetRequest,
443443
SWIFT_REQUEST(TypeChecker, PreCheckReturnStmtRequest,
444444
Stmt *(ReturnStmt *, DeclContext *),
445445
Cached, NoLocationInfo)
446+
SWIFT_REQUEST(TypeChecker, IsSingleValueStmtRequest,
447+
IsSingleValueStmtResult(const Stmt *),
448+
Cached, NoLocationInfo)
446449
SWIFT_REQUEST(TypeChecker, GetTypeWrapperInitializer,
447450
ConstructorDecl *(NominalTypeDecl *),
448451
Cached, NoLocationInfo)

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ EXPERIMENTAL_FEATURE(ParserValidation, false)
143143
/// parser.
144144
EXPERIMENTAL_FEATURE(ParserDiagnostics, false)
145145

146+
/// Whether to allow 'if' and 'switch' statements to be treated as expressions.
147+
EXPERIMENTAL_FEATURE(IfSwitchExpressions, false)
148+
146149
/// Enables implicit some while also enabling existential `any`
147150
EXPERIMENTAL_FEATURE(ImplicitSome, false)
148151

include/swift/Parse/Parser.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ namespace swift {
4949
class RequirementRepr;
5050
class SILParserStateBase;
5151
class ScopeInfo;
52+
class SingleValueStmtExpr;
5253
class SourceManager;
5354
class TupleType;
5455
class TypeLoc;

include/swift/Sema/CompletionContextFinder.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace swift {
2323
class CompletionContextFinder : public ASTWalker {
2424
enum class ContextKind {
2525
FallbackExpression,
26+
SingleValueStmtExpr,
2627
StringInterpolation,
2728
SingleStmtClosure,
2829
MultiStmtClosure,
@@ -78,6 +79,10 @@ class CompletionContextFinder : public ASTWalker {
7879
return hasContext(ContextKind::StringInterpolation);
7980
}
8081

82+
bool locatedInSingleValueStmtExpr() const {
83+
return hasContext(ContextKind::SingleValueStmtExpr);
84+
}
85+
8186
bool hasCompletionExpr() const {
8287
return CompletionNode.dyn_cast<CodeCompletionExpr *>() != nullptr;
8388
}

0 commit comments

Comments
 (0)