Skip to content

Commit b8d793c

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 0785cc8 commit b8d793c

Some content is hidden

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

45 files changed

+987
-185
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,8 +1248,9 @@ ERROR(case_stmt_without_body,none,
12481248
// 'try' on statements
12491249
ERROR(try_on_stmt,none,
12501250
"'try' cannot be used with '%0'", (StringRef))
1251-
ERROR(try_on_return_throw_yield,none,
1252-
"'try' must be placed on the %select{returned|thrown|yielded}0 expression",
1251+
ERROR(try_must_come_after_stmt,none,
1252+
"'try' must be placed on the %select{returned|thrown|yielded|produced}0 "
1253+
"expression",
12531254
(unsigned))
12541255
ERROR(try_on_var_let,none,
12551256
"'try' must be placed on the initial value expression", ())
@@ -1403,6 +1404,12 @@ ERROR(expected_expr_after_each, none,
14031404
"expected expression after 'each'", ())
14041405
ERROR(expected_expr_after_repeat, none,
14051406
"expected expression after 'repeat'", ())
1407+
ERROR(expected_expr_after_then,none,
1408+
"expected expression after 'then'", ())
1409+
1410+
WARNING(unindented_code_after_then,none,
1411+
"expression following 'then' is treated as an argument of "
1412+
"the 'then'", ())
14061413

14071414
WARNING(move_consume_final_spelling, none,
14081415
"'_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,
@@ -6125,10 +6126,10 @@ class SingleValueStmtExpr : public Expr {
61256126
SingleValueStmtExpr(Stmt *S, DeclContext *DC)
61266127
: Expr(ExprKind::SingleValueStmt, /*isImplicit*/ true), S(S), DC(DC) {}
61276128

6128-
public:
61296129
/// Creates a new SingleValueStmtExpr wrapping a statement.
61306130
static SingleValueStmtExpr *create(ASTContext &ctx, Stmt *S, DeclContext *DC);
61316131

6132+
public:
61326133
/// Creates a new SingleValueStmtExpr wrapping a statement, and recursively
61336134
/// attempts to wrap any branches of that statement that can become single
61346135
/// value statement expressions.
@@ -6144,6 +6145,16 @@ class SingleValueStmtExpr : public Expr {
61446145
/// SingleValueStmtExpr.
61456146
static SingleValueStmtExpr *tryDigOutSingleValueStmtExpr(Expr *E);
61466147

6148+
/// Retrieves a resulting ThenStmt from the given BraceStmt, or \c nullptr if
6149+
/// the brace does not have a resulting ThenStmt.
6150+
static ThenStmt *getResultStmtFrom(BraceStmt *BS);
6151+
6152+
/// Whether the given BraceStmt has a result to be produced from a parent
6153+
/// SingleValueStmtExpr.
6154+
static bool hasResult(BraceStmt *BS) {
6155+
return getResultStmtFrom(BS);
6156+
}
6157+
61476158
/// Retrieve the wrapped statement.
61486159
Stmt *getStmt() const { return S; }
61496160
void setStmt(Stmt *newS) { S = newS; }
@@ -6154,10 +6165,15 @@ class SingleValueStmtExpr : public Expr {
61546165
/// Retrieve the complete set of branches for the underlying statement.
61556166
ArrayRef<Stmt *> getBranches(SmallVectorImpl<Stmt *> &scratch) const;
61566167

6157-
/// Retrieve the single expression branches of the statement, excluding
6158-
/// branches that either have multiple expressions, or have statements.
6168+
/// Retrieve the result statements from each branch of the
6169+
/// SingleValueStmtExpr.
6170+
ArrayRef<ThenStmt *>
6171+
getResultStmts(SmallVectorImpl<ThenStmt *> &scratch) const;
6172+
6173+
/// Retrieve the result expressions from each branch of the
6174+
/// SingleValueStmtExpr.
61596175
ArrayRef<Expr *>
6160-
getSingleExprBranches(SmallVectorImpl<Expr *> &scratch) const;
6176+
getResultExprs(SmallVectorImpl<Expr *> &scratch) const;
61616177

61626178
DeclContext *getDeclContext() const { return DC; }
61636179

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/TypeCheckRequests.h

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

3909-
/// There are no single-expression branches.
3910-
NoExpressionBranches,
3909+
/// There is no branch that produces a resulting value.
3910+
NoResult,
39113911

39123912
/// There is an unhandled statement branch. This should only be the case
39133913
/// for invalid AST.
@@ -3962,8 +3962,8 @@ class IsSingleValueStmtResult {
39623962
static IsSingleValueStmtResult nonExhaustiveIf() {
39633963
return IsSingleValueStmtResult(Kind::NonExhaustiveIf);
39643964
}
3965-
static IsSingleValueStmtResult noExpressionBranches() {
3966-
return IsSingleValueStmtResult(Kind::NoExpressionBranches);
3965+
static IsSingleValueStmtResult noResult() {
3966+
return IsSingleValueStmtResult(Kind::NoResult);
39673967
}
39683968
static IsSingleValueStmtResult unhandledStmt() {
39693969
return IsSingleValueStmtResult(Kind::UnhandledStmt);

include/swift/Basic/Features.def

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

227+
/// Enable 'then' statements.
228+
EXPERIMENTAL_FEATURE(ThenStatements, false)
229+
227230
/// Enable the `@_rawLayout` attribute.
228231
EXPERIMENTAL_FEATURE(RawLayout, true)
229232

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: 11 additions & 1 deletion
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 = false);
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.
@@ -1924,7 +1928,12 @@ class Parser {
19241928
//===--------------------------------------------------------------------===//
19251929
// Statement Parsing
19261930

1927-
bool isStartOfStmt();
1931+
/// Whether we are at the start of a statement.
1932+
///
1933+
/// \param preferExpr If \c true, if either a statement or expression
1934+
/// could be parsed, prefer to return \c false such that an expr is parsed.
1935+
bool isStartOfStmt(bool preferExpr = false);
1936+
19281937
bool isTerminatorForBraceItemListKind(BraceItemListKind Kind,
19291938
ArrayRef<ASTNode> ParsedDecls);
19301939
ParserResult<Stmt> parseStmt();
@@ -1933,6 +1942,7 @@ class Parser {
19331942
ParserResult<Stmt> parseStmtContinue();
19341943
ParserResult<Stmt> parseStmtReturn(SourceLoc tryLoc);
19351944
ParserResult<Stmt> parseStmtYield(SourceLoc tryLoc);
1945+
ParserResult<Stmt> parseStmtThen(SourceLoc tryLoc);
19361946
ParserResult<Stmt> parseStmtThrow(SourceLoc tryLoc);
19371947
ParserResult<Stmt> parseStmtDiscard();
19381948
ParserResult<Stmt> parseStmtDefer();

include/swift/Parse/Token.h

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,39 @@ 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::period:
120+
case tok::equal:
121+
case tok::arrow:
122+
case tok::question_infix:
123+
return true;
124+
default:
125+
return false;
126+
}
127+
llvm_unreachable("Unhandled case in switch!");
128+
}
129+
130+
/// Checks whether the token is either a postfix operator, or is a token that
131+
/// acts like a postfix operator (e.g postfix '!' and '?').
132+
bool isPostfixOperatorLike() const {
133+
switch (Kind) {
134+
case tok::oper_postfix:
135+
case tok::exclaim_postfix:
136+
case tok::question_postfix:
137+
return true;
138+
default:
139+
return false;
140+
}
141+
llvm_unreachable("Unhandled case in switch!");
142+
}
143+
112144
bool isAnyOperator() const {
113145
return isBinaryOperator() || Kind == tok::oper_postfix ||
114146
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
@@ -935,19 +941,19 @@ class LocatorPathElt::TernaryBranch final : public StoredIntegerElement<1> {
935941
}
936942
};
937943

938-
/// The branch of a SingleValueStmtExpr. Note the stored index corresponds to
939-
/// the expression branches, i.e it skips statement branch indices.
940-
class LocatorPathElt::SingleValueStmtBranch final
944+
/// A particular result of a ThenStmt at a given index in a SingleValueStmtExpr.
945+
/// The stored index corresponds to the \c getResultExprs array.
946+
class LocatorPathElt::SingleValueStmtResult final
941947
: public StoredIntegerElement<1> {
942948
public:
943-
SingleValueStmtBranch(unsigned exprIdx)
944-
: StoredIntegerElement(ConstraintLocator::SingleValueStmtBranch,
949+
SingleValueStmtResult(unsigned exprIdx)
950+
: StoredIntegerElement(ConstraintLocator::SingleValueStmtResult,
945951
exprIdx) {}
946952

947-
unsigned getExprBranchIndex() const { return getValue(); }
953+
unsigned getIndex() const { return getValue(); }
948954

949955
static bool classof(const LocatorPathElt *elt) {
950-
return elt->getKind() == ConstraintLocator::SingleValueStmtBranch;
956+
return elt->getKind() == ConstraintLocator::SingleValueStmtResult;
951957
}
952958
};
953959

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
@@ -1640,6 +1640,13 @@ class PrintStmt : public StmtVisitor<PrintStmt> {
16401640
PrintWithColorRAII(OS, ParenthesisColor) << ')';
16411641
}
16421642

1643+
void visitThenStmt(ThenStmt *S) {
1644+
printCommon(S, "then_stmt");
1645+
OS << '\n';
1646+
printRec(S->getResult());
1647+
PrintWithColorRAII(OS, ParenthesisColor) << ')';
1648+
}
1649+
16431650
void visitDeferStmt(DeferStmt *S) {
16441651
printCommon(S, "defer_stmt") << '\n';
16451652
printRec(S->getTempDecl());

lib/AST/ASTPrinter.cpp

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

3511+
static bool usesFeatureThenStatements(Decl *decl) {
3512+
return false;
3513+
}
3514+
35113515
static bool usesFeatureNewCxxMethodSafetyHeuristics(Decl *decl) {
35123516
return decl->hasClangNode();
35133517
}
@@ -5520,6 +5524,16 @@ void PrintAST::visitYieldStmt(YieldStmt *stmt) {
55205524
if (parens) Printer << ")";
55215525
}
55225526

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

lib/AST/ASTScopeCreation.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,12 @@ class NodeAdder
437437
return p;
438438
}
439439

440+
ASTScopeImpl *visitThenStmt(ThenStmt *ts, ASTScopeImpl *p,
441+
ScopeCreator &scopeCreator) {
442+
visitExpr(ts->getResult(), p, scopeCreator);
443+
return p;
444+
}
445+
440446
ASTScopeImpl *visitDeferStmt(DeferStmt *ds, ASTScopeImpl *p,
441447
ScopeCreator &scopeCreator) {
442448
visitFuncDecl(ds->getTempDecl(), p, scopeCreator);

lib/AST/ASTVerifier.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1214,7 +1214,7 @@ class Verifier : public ASTWalker {
12141214
void verifyChecked(SingleValueStmtExpr *E) {
12151215
using Kind = IsSingleValueStmtResult::Kind;
12161216
switch (E->getStmt()->mayProduceSingleValue(Ctx).getKind()) {
1217-
case Kind::NoExpressionBranches:
1217+
case Kind::NoResult:
12181218
// These are allowed as long as the type is Void.
12191219
checkSameType(
12201220
E->getType(), Ctx.getVoidType(),

0 commit comments

Comments
 (0)