Skip to content

Commit ebfe6aa

Browse files
committed
Introduce then statements
These allow multi-statement `if`/`switch` expression branches that can produce a value at the end by saying `then <expr>`. This is gated behind `-enable-experimental-feature ThenStatements` pending evolution discussion.
1 parent c236b8d commit ebfe6aa

Some content is hidden

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

47 files changed

+1058
-196
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,8 +1244,9 @@ ERROR(case_stmt_without_body,none,
12441244
// 'try' on statements
12451245
ERROR(try_on_stmt,none,
12461246
"'try' cannot be used with '%0'", (StringRef))
1247-
ERROR(try_on_return_throw_yield,none,
1248-
"'try' must be placed on the %select{returned|thrown|yielded}0 expression",
1247+
ERROR(try_must_come_after_stmt,none,
1248+
"'try' must be placed on the %select{returned|thrown|yielded|produced}0 "
1249+
"expression",
12491250
(unsigned))
12501251
ERROR(try_on_var_let,none,
12511252
"'try' must be placed on the initial value expression", ())
@@ -1399,6 +1400,12 @@ ERROR(expected_expr_after_each, none,
13991400
"expected expression after 'each'", ())
14001401
ERROR(expected_expr_after_repeat, none,
14011402
"expected expression after 'repeat'", ())
1403+
ERROR(expected_expr_after_then,none,
1404+
"expected expression after 'then'", ())
1405+
1406+
WARNING(unindented_code_after_then,none,
1407+
"expression following 'then' is treated as an argument of "
1408+
"the 'then'", ())
14021409

14031410
WARNING(move_consume_final_spelling, none,
14041411
"'_move' has been renamed to 'consume', and the '_move' spelling will be removed shortly", ())

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,14 +1225,18 @@ ERROR(if_expr_must_be_syntactically_exhaustive,none,
12251225
ERROR(single_value_stmt_branch_empty,none,
12261226
"expected expression in branch of '%0' expression",
12271227
(StmtKind))
1228-
ERROR(single_value_stmt_branch_must_end_in_throw,none,
1229-
"non-expression branch of '%0' expression may only end with a 'throw'",
1228+
ERROR(single_value_stmt_branch_must_end_in_result,none,
1229+
"non-expression branch of '%0' expression may only end with a 'throw' "
1230+
"or 'then'",
12301231
(StmtKind))
12311232
ERROR(cannot_jump_in_single_value_stmt,none,
12321233
"cannot '%0' in '%1' when used as expression",
12331234
(StmtKind, StmtKind))
12341235
ERROR(effect_marker_on_single_value_stmt,none,
12351236
"'%0' may not be used on '%1' expression", (StringRef, StmtKind))
1237+
ERROR(out_of_place_then_stmt,none,
1238+
"'then' may only appear as the last statement in an 'if' or 'switch' "
1239+
"expression", ())
12361240

12371241
ERROR(did_not_call_function_value,none,
12381242
"function value was used as a property; add () to call it",

include/swift/AST/Expr.h

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ namespace swift {
7070
class CallExpr;
7171
class KeyPathExpr;
7272
class CaptureListExpr;
73+
class ThenStmt;
7374

7475
enum class ExprKind : uint8_t {
7576
#define EXPR(Id, Parent) Id,
@@ -6121,10 +6122,10 @@ class SingleValueStmtExpr : public Expr {
61216122
SingleValueStmtExpr(Stmt *S, DeclContext *DC)
61226123
: Expr(ExprKind::SingleValueStmt, /*isImplicit*/ true), S(S), DC(DC) {}
61236124

6124-
public:
61256125
/// Creates a new SingleValueStmtExpr wrapping a statement.
61266126
static SingleValueStmtExpr *create(ASTContext &ctx, Stmt *S, DeclContext *DC);
61276127

6128+
public:
61286129
/// Creates a new SingleValueStmtExpr wrapping a statement, and recursively
61296130
/// attempts to wrap any branches of that statement that can become single
61306131
/// value statement expressions.
@@ -6140,6 +6141,16 @@ class SingleValueStmtExpr : public Expr {
61406141
/// SingleValueStmtExpr.
61416142
static SingleValueStmtExpr *tryDigOutSingleValueStmtExpr(Expr *E);
61426143

6144+
/// Retrieves a resulting ThenStmt from the given BraceStmt, or \c nullptr if
6145+
/// the brace does not have a resulting ThenStmt.
6146+
static ThenStmt *getThenStmtFrom(BraceStmt *BS);
6147+
6148+
/// Whether the given BraceStmt has a result to be produced from a parent
6149+
/// SingleValueStmtExpr.
6150+
static bool hasResult(BraceStmt *BS) {
6151+
return getThenStmtFrom(BS);
6152+
}
6153+
61436154
/// Retrieve the wrapped statement.
61446155
Stmt *getStmt() const { return S; }
61456156
void setStmt(Stmt *newS) { S = newS; }
@@ -6150,10 +6161,15 @@ class SingleValueStmtExpr : public Expr {
61506161
/// Retrieve the complete set of branches for the underlying statement.
61516162
ArrayRef<Stmt *> getBranches(SmallVectorImpl<Stmt *> &scratch) const;
61526163

6153-
/// Retrieve the single expression branches of the statement, excluding
6154-
/// branches that either have multiple expressions, or have statements.
6164+
/// Retrieve the resulting ThenStmts from each branch of the
6165+
/// SingleValueStmtExpr.
6166+
ArrayRef<ThenStmt *>
6167+
getThenStmts(SmallVectorImpl<ThenStmt *> &scratch) const;
6168+
6169+
/// Retrieve the result expressions from each branch of the
6170+
/// SingleValueStmtExpr.
61556171
ArrayRef<Expr *>
6156-
getSingleExprBranches(SmallVectorImpl<Expr *> &scratch) const;
6172+
getResultExprs(SmallVectorImpl<Expr *> &scratch) const;
61576173

61586174
DeclContext *getDeclContext() const { return DC; }
61596175

include/swift/AST/NameLookup.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,7 @@ class FindLocalVal : public StmtVisitor<FindLocalVal> {
667667
void visitFailStmt(FailStmt *) {}
668668
void visitReturnStmt(ReturnStmt *) {}
669669
void visitYieldStmt(YieldStmt *) {}
670+
void visitThenStmt(ThenStmt *) {}
670671
void visitThrowStmt(ThrowStmt *) {}
671672
void visitDiscardStmt(DiscardStmt *) {}
672673
void visitPoundAssertStmt(PoundAssertStmt *) {}

include/swift/AST/Stmt.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,38 @@ class YieldStmt final
304304
static bool classof(const Stmt *S) { return S->getKind() == StmtKind::Yield; }
305305
};
306306

307+
/// The statement `then <expr>`. This is used within if/switch expressions to
308+
/// indicate the value being produced by a given branch.
309+
class ThenStmt : public Stmt {
310+
SourceLoc ThenLoc;
311+
Expr *Result;
312+
313+
ThenStmt(SourceLoc thenLoc, Expr *result, bool isImplicit)
314+
: Stmt(StmtKind::Then, isImplicit), ThenLoc(thenLoc), Result(result) {
315+
assert(Result && "Must have non-null result");
316+
}
317+
318+
public:
319+
/// Create a new parsed ThenStmt.
320+
static ThenStmt *createParsed(ASTContext &ctx, SourceLoc thenLoc,
321+
Expr *result);
322+
323+
/// Create an implicit ThenStmt.
324+
///
325+
/// Note that such statements will be elided during the result builder
326+
/// transform.
327+
static ThenStmt *createImplicit(ASTContext &ctx, Expr *result);
328+
329+
SourceLoc getThenLoc() const { return ThenLoc; }
330+
331+
SourceRange getSourceRange() const;
332+
333+
Expr *getResult() const { return Result; }
334+
void setResult(Expr *e) { Result = e; }
335+
336+
static bool classof(const Stmt *S) { return S->getKind() == StmtKind::Then; }
337+
};
338+
307339
/// DeferStmt - A 'defer' statement. This runs the substatement it contains
308340
/// when the enclosing scope is exited.
309341
///

include/swift/AST/StmtNodes.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646

4747
STMT(Brace, Stmt)
4848
STMT(Return, Stmt)
49+
STMT(Then, Stmt)
4950
STMT(Yield, Stmt)
5051
STMT(Defer, Stmt)
5152
ABSTRACT_STMT(Labeled, Stmt)

include/swift/AST/TokenKinds.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ MISC(string_segment)
258258
MISC(string_interpolation_anchor)
259259
MISC(kw_yield)
260260
MISC(kw_discard)
261+
MISC(kw_then)
261262

262263
// The following tokens are irrelevant for swiftSyntax and thus only declared
263264
// in this .def file

include/swift/AST/TypeCheckRequests.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3901,8 +3901,8 @@ class IsSingleValueStmtResult {
39013901
/// The statement is an 'if' statement without an unconditional 'else'.
39023902
NonExhaustiveIf,
39033903

3904-
/// There are no single-expression branches.
3905-
NoExpressionBranches,
3904+
/// There is no branch that produces a resulting value.
3905+
NoResult,
39063906

39073907
/// There is an unhandled statement branch. This should only be the case
39083908
/// for invalid AST.
@@ -3957,8 +3957,8 @@ class IsSingleValueStmtResult {
39573957
static IsSingleValueStmtResult nonExhaustiveIf() {
39583958
return IsSingleValueStmtResult(Kind::NonExhaustiveIf);
39593959
}
3960-
static IsSingleValueStmtResult noExpressionBranches() {
3961-
return IsSingleValueStmtResult(Kind::NoExpressionBranches);
3960+
static IsSingleValueStmtResult noResult() {
3961+
return IsSingleValueStmtResult(Kind::NoResult);
39623962
}
39633963
static IsSingleValueStmtResult unhandledStmt() {
39643964
return IsSingleValueStmtResult(Kind::UnhandledStmt);

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,9 @@ EXPERIMENTAL_FEATURE(GlobalConcurrency, false)
226226
/// "playground transform" is enabled.
227227
EXPERIMENTAL_FEATURE(PlaygroundExtendedCallbacks, true)
228228

229+
/// Enable 'then' statements.
230+
EXPERIMENTAL_FEATURE(ThenStatements, false)
231+
229232
/// Enable the `@_rawLayout` attribute.
230233
EXPERIMENTAL_FEATURE(RawLayout, true)
231234

include/swift/IDE/CodeCompletionResult.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ enum class CompletionKind : uint8_t {
213213
CallArg,
214214
LabeledTrailingClosure,
215215
ReturnStmtExpr,
216+
ThenStmtExpr,
216217
YieldStmtExpr,
217218
ForEachSequence,
218219

include/swift/Parse/IDEInspectionCallbacks.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,8 @@ class CodeCompletionCallbacks {
235235

236236
virtual void completeReturnStmt(CodeCompletionExpr *E) {};
237237

238+
virtual void completeThenStmt(CodeCompletionExpr *E) {};
239+
238240
/// Complete a yield statement. A missing yield index means that the
239241
/// completion immediately follows the 'yield' keyword; it may be either
240242
/// an expression or a parenthesized expression list. A present yield

include/swift/Parse/Parser.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,10 @@ class Parser {
602602
cast<AccessorDecl>(CurDeclContext)->isCoroutine());
603603
}
604604

605+
/// Whether the current token is the contextual keyword for a \c then
606+
/// statement.
607+
bool isContextualThenKeyword(bool preferExpr);
608+
605609
/// `discard self` is the only valid phrase, but we peek ahead for just any
606610
/// identifier after `discard` to determine if it's the statement. This helps
607611
/// us avoid interpreting `discard(self)` as the statement and not a call.
@@ -641,7 +645,7 @@ class Parser {
641645
while (Tok.isNot(K..., tok::eof, tok::r_brace, tok::pound_endif,
642646
tok::pound_else, tok::pound_elseif,
643647
tok::code_complete) &&
644-
!isStartOfStmt() &&
648+
!isStartOfStmt(/*preferExpr*/ false) &&
645649
!isStartOfSwiftDecl(/*allowPoundIfAttributes=*/true)) {
646650
skipSingle();
647651
}
@@ -1919,7 +1923,13 @@ class Parser {
19191923
//===--------------------------------------------------------------------===//
19201924
// Statement Parsing
19211925

1922-
bool isStartOfStmt();
1926+
/// Whether we are at the start of a statement.
1927+
///
1928+
/// \param preferExpr If either an expression or statement could be parsed and
1929+
/// this parameter is \c true, the function returns \c false such that an
1930+
/// expression can be parsed.
1931+
bool isStartOfStmt(bool preferExpr);
1932+
19231933
bool isTerminatorForBraceItemListKind(BraceItemListKind Kind,
19241934
ArrayRef<ASTNode> ParsedDecls);
19251935
ParserResult<Stmt> parseStmt();
@@ -1928,6 +1938,7 @@ class Parser {
19281938
ParserResult<Stmt> parseStmtContinue();
19291939
ParserResult<Stmt> parseStmtReturn(SourceLoc tryLoc);
19301940
ParserResult<Stmt> parseStmtYield(SourceLoc tryLoc);
1941+
ParserResult<Stmt> parseStmtThen(SourceLoc tryLoc);
19311942
ParserResult<Stmt> parseStmtThrow(SourceLoc tryLoc);
19321943
ParserResult<Stmt> parseStmtDiscard();
19331944
ParserResult<Stmt> parseStmtDefer();

include/swift/Parse/Token.h

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,38 @@ class Token {
108108
bool isBinaryOperator() const {
109109
return Kind == tok::oper_binary_spaced || Kind == tok::oper_binary_unspaced;
110110
}
111-
111+
112+
/// Checks whether the token is either a binary operator, or is a token that
113+
/// acts like a binary operator (e.g infix '=', '?', '->').
114+
bool isBinaryOperatorLike() const {
115+
if (isBinaryOperator())
116+
return true;
117+
118+
switch (Kind) {
119+
case tok::equal:
120+
case tok::arrow:
121+
case tok::question_infix:
122+
return true;
123+
default:
124+
return false;
125+
}
126+
llvm_unreachable("Unhandled case in switch!");
127+
}
128+
129+
/// Checks whether the token is either a postfix operator, or is a token that
130+
/// acts like a postfix operator (e.g postfix '!' and '?').
131+
bool isPostfixOperatorLike() const {
132+
switch (Kind) {
133+
case tok::oper_postfix:
134+
case tok::exclaim_postfix:
135+
case tok::question_postfix:
136+
return true;
137+
default:
138+
return false;
139+
}
140+
llvm_unreachable("Unhandled case in switch!");
141+
}
142+
112143
bool isAnyOperator() const {
113144
return isBinaryOperator() || Kind == tok::oper_postfix ||
114145
Kind == tok::oper_prefix;

include/swift/Sema/ConstraintLocator.h

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,14 @@ enum class ConversionRestrictionKind;
103103

104104
/// The kind of SingleValueStmtExpr branch the locator identifies.
105105
enum class SingleValueStmtBranchKind {
106-
Regular,
107-
InSingleExprClosure
106+
/// Explicitly written 'then <expr>'.
107+
Explicit,
108+
109+
/// Implicitly written '<expr>'.
110+
Implicit,
111+
112+
/// Implicitly written '<expr>' in a single expr closure body.
113+
ImplicitInInSingleExprClosure
108114
};
109115

110116
/// Locates a given constraint within the expression being
@@ -938,19 +944,19 @@ class LocatorPathElt::TernaryBranch final : public StoredIntegerElement<1> {
938944
}
939945
};
940946

941-
/// The branch of a SingleValueStmtExpr. Note the stored index corresponds to
942-
/// the expression branches, i.e it skips statement branch indices.
943-
class LocatorPathElt::SingleValueStmtBranch final
947+
/// A particular result of a ThenStmt at a given index in a SingleValueStmtExpr.
948+
/// The stored index corresponds to the \c getResultExprs array.
949+
class LocatorPathElt::SingleValueStmtResult final
944950
: public StoredIntegerElement<1> {
945951
public:
946-
SingleValueStmtBranch(unsigned exprIdx)
947-
: StoredIntegerElement(ConstraintLocator::SingleValueStmtBranch,
952+
SingleValueStmtResult(unsigned exprIdx)
953+
: StoredIntegerElement(ConstraintLocator::SingleValueStmtResult,
948954
exprIdx) {}
949955

950-
unsigned getExprBranchIndex() const { return getValue(); }
956+
unsigned getIndex() const { return getValue(); }
951957

952958
static bool classof(const LocatorPathElt *elt) {
953-
return elt->getKind() == ConstraintLocator::SingleValueStmtBranch;
959+
return elt->getKind() == ConstraintLocator::SingleValueStmtResult;
954960
}
955961
};
956962

include/swift/Sema/ConstraintLocatorPathElts.def

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,8 @@ CUSTOM_LOCATOR_PATH_ELT(SyntacticElement)
259259
/// The element of the pattern binding declaration.
260260
CUSTOM_LOCATOR_PATH_ELT(PatternBindingElement)
261261

262-
/// A branch of a SingleValueStmtExpr.
263-
CUSTOM_LOCATOR_PATH_ELT(SingleValueStmtBranch)
262+
/// A particular result of a ThenStmt at a given index in a SingleValueStmtExpr.
263+
CUSTOM_LOCATOR_PATH_ELT(SingleValueStmtResult)
264264

265265
/// A declaration introduced by a pattern: name (i.e. `x`) or `_`
266266
ABSTRACT_LOCATOR_PATH_ELT(PatternDecl)

lib/AST/ASTDumper.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1625,6 +1625,13 @@ class PrintStmt : public StmtVisitor<PrintStmt> {
16251625
PrintWithColorRAII(OS, ParenthesisColor) << ')';
16261626
}
16271627

1628+
void visitThenStmt(ThenStmt *S) {
1629+
printCommon(S, "then_stmt");
1630+
OS << '\n';
1631+
printRec(S->getResult());
1632+
PrintWithColorRAII(OS, ParenthesisColor) << ')';
1633+
}
1634+
16281635
void visitDeferStmt(DeferStmt *S) {
16291636
printCommon(S, "defer_stmt") << '\n';
16301637
printRec(S->getTempDecl());

lib/AST/ASTPrinter.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3504,6 +3504,10 @@ static bool usesFeaturePlaygroundExtendedCallbacks(Decl *decl) {
35043504
return false;
35053505
}
35063506

3507+
static bool usesFeatureThenStatements(Decl *decl) {
3508+
return false;
3509+
}
3510+
35073511
static bool usesFeatureNewCxxMethodSafetyHeuristics(Decl *decl) {
35083512
return decl->hasClangNode();
35093513
}
@@ -5516,6 +5520,16 @@ void PrintAST::visitYieldStmt(YieldStmt *stmt) {
55165520
if (parens) Printer << ")";
55175521
}
55185522

5523+
void PrintAST::visitThenStmt(ThenStmt *stmt) {
5524+
// For now, don't print implicit 'then' statements, since they can be
5525+
// present when the feature is disabled.
5526+
// TODO: Once we enable the feature, we can remove this.
5527+
if (!stmt->isImplicit())
5528+
Printer.printKeyword("then", Options, " ");
5529+
5530+
visit(stmt->getResult());
5531+
}
5532+
55195533
void PrintAST::visitThrowStmt(ThrowStmt *stmt) {
55205534
Printer << tok::kw_throw << " ";
55215535
visit(stmt->getSubExpr());

0 commit comments

Comments
 (0)