Skip to content

Commit d73e394

Browse files
committed
Allow implicit last expression results for if/switch expressions
Allow implicitly treating the last expression of the branch as the result, behind the experimental feature `ImplicitLastExprResults`.
1 parent 747cbfc commit d73e394

13 files changed

+743
-56
lines changed

include/swift/AST/Expr.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6204,12 +6204,19 @@ class SingleValueStmtExpr : public Expr {
62046204
/// SingleValueStmtExpr.
62056205
static SingleValueStmtExpr *tryDigOutSingleValueStmtExpr(Expr *E);
62066206

6207+
/// Whether the last ASTNode in the given BraceStmt can potentially be used as
6208+
/// the implicit result for a SingleValueStmtExpr. If \p mustBeSingleValueStmt
6209+
/// is \c true, a result will be considered even if it may not be valid.
6210+
static bool isLastElementImplicitResult(BraceStmt *BS, ASTContext &ctx,
6211+
bool mustBeSingleValueStmt);
6212+
62076213
/// Retrieves a resulting ThenStmt from the given BraceStmt, or \c nullptr if
62086214
/// the brace does not have a resulting ThenStmt.
62096215
static ThenStmt *getThenStmtFrom(BraceStmt *BS);
62106216

62116217
/// Whether the given BraceStmt has a result to be produced from a parent
6212-
/// SingleValueStmtExpr.
6218+
/// SingleValueStmtExpr. Note this does not consider elements that may
6219+
/// implicitly become results, check \c isLastElementImplicitResult for that.
62136220
static bool hasResult(BraceStmt *BS) {
62146221
return getThenStmtFrom(BS);
62156222
}

include/swift/AST/TypeCheckRequests.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4258,9 +4258,13 @@ class IsSingleValueStmtResult {
42584258
return InvalidJumps;
42594259
}
42604260

4261-
explicit operator bool() const {
4261+
bool isValid() const {
42624262
return TheKind == Kind::Valid;
42634263
}
4264+
4265+
explicit operator bool() const {
4266+
return isValid();
4267+
}
42644268
};
42654269

42664270
/// Computes whether a given statement can be treated as a SingleValueStmtExpr.

include/swift/Basic/Features.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ EXPERIMENTAL_FEATURE(ThenStatements, false)
231231
/// Enable 'do' expressions.
232232
EXPERIMENTAL_FEATURE(DoExpressions, false)
233233

234+
/// Enable implicitly treating the last expression in an 'if'/'switch'
235+
/// expression as the result
236+
EXPERIMENTAL_FEATURE(ImplicitLastExprResults, false)
237+
234238
/// Enable the `@_rawLayout` attribute.
235239
EXPERIMENTAL_FEATURE(RawLayout, true)
236240

lib/AST/ASTPrinter.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3846,6 +3846,10 @@ static bool usesFeatureDoExpressions(Decl *decl) {
38463846
return false;
38473847
}
38483848

3849+
static bool usesFeatureImplicitLastExprResults(Decl *decl) {
3850+
return false;
3851+
}
3852+
38493853
static bool usesFeatureNewCxxMethodSafetyHeuristics(Decl *decl) {
38503854
return decl->hasClangNode();
38513855
}

lib/AST/Expr.cpp

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2550,37 +2550,21 @@ SingleValueStmtExpr *SingleValueStmtExpr::createWithWrappedBranches(
25502550
if (!BS)
25512551
continue;
25522552

2553-
if (auto *S = BS->getSingleActiveStatement()) {
2554-
if (mustBeExpr) {
2555-
// If this must be an expression, we can eagerly wrap any exhaustive if,
2556-
// switch, and do statement.
2557-
if (auto *IS = dyn_cast<IfStmt>(S)) {
2558-
if (!IS->isSyntacticallyExhaustive())
2559-
continue;
2560-
} else if (auto *DCS = dyn_cast<DoCatchStmt>(S)) {
2561-
if (!ctx.LangOpts.hasFeature(Feature::DoExpressions))
2562-
continue;
2563-
if (!DCS->isSyntacticallyExhaustive())
2564-
continue;
2565-
} else if (isa<DoStmt>(S)) {
2566-
if (!ctx.LangOpts.hasFeature(Feature::DoExpressions))
2567-
continue;
2568-
} else if (!isa<SwitchStmt>(S)) {
2569-
continue;
2570-
}
2571-
} else {
2572-
// Otherwise do the semantic checking to verify that we can wrap the
2573-
// branch.
2574-
if (!S->mayProduceSingleValue(ctx))
2575-
continue;
2576-
}
2577-
BS->setLastElement(SingleValueStmtExpr::createWithWrappedBranches(
2578-
ctx, S, DC, mustBeExpr));
2579-
}
2553+
// Check to see if we can wrap an implicit last element of the brace.
2554+
if (!isLastElementImplicitResult(BS, ctx, mustBeExpr))
2555+
continue;
2556+
2557+
auto &result = BS->getElements().back();
2558+
assert(result.is<Expr *>() || result.is<Stmt *>());
25802559

2581-
// Wrap single expression branches in an implicit 'then <expr>'.
2582-
if (auto *E = BS->getSingleActiveExpression())
2583-
BS->setLastElement(ThenStmt::createImplicit(ctx, E));
2560+
// Wrap a statement in a SingleValueStmtExpr.
2561+
if (auto *S = result.dyn_cast<Stmt *>()) {
2562+
result = SingleValueStmtExpr::createWithWrappedBranches(ctx, S, DC,
2563+
mustBeExpr);
2564+
}
2565+
// Wrap an expression in an implicit 'then <expr>'.
2566+
if (auto *E = result.dyn_cast<Expr *>())
2567+
result = ThenStmt::createImplicit(ctx, E);
25842568
}
25852569
return SVE;
25862570
}
@@ -2630,6 +2614,50 @@ SingleValueStmtExpr::tryDigOutSingleValueStmtExpr(Expr *E) {
26302614
return finder.FoundSVE;
26312615
}
26322616

2617+
bool SingleValueStmtExpr::isLastElementImplicitResult(
2618+
BraceStmt *BS, ASTContext &ctx, bool mustBeSingleValueStmt) {
2619+
if (BS->empty())
2620+
return false;
2621+
2622+
// We must either be allowing implicit last expressions, or we must have a
2623+
// single active element, which is guaranteed to be the last element.
2624+
if (!ctx.LangOpts.hasFeature(Feature::ImplicitLastExprResults) &&
2625+
!BS->getSingleActiveElement()) {
2626+
return false;
2627+
}
2628+
auto elt = BS->getLastElement();
2629+
2630+
// Expressions are always valid.
2631+
if (elt.is<Expr *>())
2632+
return true;
2633+
2634+
if (auto *S = elt.dyn_cast<Stmt *>()) {
2635+
if (mustBeSingleValueStmt) {
2636+
// If this must be a SingleValueStmtExpr, we can eagerly take any
2637+
// exhaustive if, switch, and do statement.
2638+
if (auto *IS = dyn_cast<IfStmt>(S))
2639+
return IS->isSyntacticallyExhaustive();
2640+
2641+
// Guaranteed to be exhaustive.
2642+
if (isa<SwitchStmt>(S))
2643+
return true;
2644+
2645+
if (ctx.LangOpts.hasFeature(Feature::DoExpressions)) {
2646+
if (auto *DCS = dyn_cast<DoCatchStmt>(S))
2647+
return DCS->isSyntacticallyExhaustive();
2648+
2649+
if (isa<DoStmt>(S))
2650+
return true;
2651+
}
2652+
return false;
2653+
}
2654+
// Otherwise do the semantic checking to verify the statement produces
2655+
// a single value.
2656+
return S->mayProduceSingleValue(ctx).isValid();
2657+
}
2658+
return false;
2659+
}
2660+
26332661
ThenStmt *SingleValueStmtExpr::getThenStmtFrom(BraceStmt *BS) {
26342662
if (BS->empty())
26352663
return nullptr;

lib/Sema/MiscDiagnostics.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3922,8 +3922,9 @@ class SingleValueStmtUsageChecker final : public ASTWalker {
39223922
continue;
39233923
}
39243924
}
3925-
// TODO: If 'then' statements are enabled by default, the wording of
3926-
// this diagnostic should be tweaked.
3925+
// TODO: The wording of this diagnostic will need tweaking if either
3926+
// implicit last expressions or 'then' statements are enabled by
3927+
// default.
39273928
Diags.diagnose(branch->getEndLoc(),
39283929
diag::single_value_stmt_branch_must_end_in_result,
39293930
S->getKind());

lib/Sema/TypeCheckStmt.cpp

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3057,26 +3057,6 @@ static bool doesBraceEndWithThrow(BraceStmt *BS) {
30573057
return isa<ThrowStmt>(S);
30583058
}
30593059

3060-
/// Whether the given brace statement is considered to produce a result for
3061-
/// an if/switch expression.
3062-
static bool doesBraceProduceResult(BraceStmt *BS, ASTContext &ctx) {
3063-
if (BS->empty())
3064-
return false;
3065-
3066-
// We consider the branch as having a result if there is:
3067-
// - A single active expression or statement that can be turned into an
3068-
// expression.
3069-
// - 'then <expr>' as the last statement.
3070-
if (BS->getSingleActiveExpression())
3071-
return true;
3072-
3073-
if (auto *S = BS->getSingleActiveStatement()) {
3074-
if (S->mayProduceSingleValue(ctx))
3075-
return true;
3076-
}
3077-
return SingleValueStmtExpr::hasResult(BS);
3078-
}
3079-
30803060
IsSingleValueStmtResult
30813061
areBranchesValidForSingleValueStmt(ASTContext &ctx, ArrayRef<Stmt *> branches) {
30823062
TinyPtrVector<Stmt *> invalidJumps;
@@ -3094,8 +3074,10 @@ areBranchesValidForSingleValueStmt(ASTContext &ctx, ArrayRef<Stmt *> branches) {
30943074
// Check to see if there are any invalid jumps.
30953075
BS->walk(jumpFinder);
30963076

3097-
// Check to see if a result is produced from the branch.
3098-
if (doesBraceProduceResult(BS, ctx)) {
3077+
// Must either have an explicit or implicit result for the branch.
3078+
if (SingleValueStmtExpr::hasResult(BS) ||
3079+
SingleValueStmtExpr::isLastElementImplicitResult(
3080+
BS, ctx, /*mustBeSingleValueStmt*/ false)) {
30993081
hadResult = true;
31003082
continue;
31013083
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-feature ImplicitLastExprResults
2+
3+
// Required for experimental features
4+
// REQUIRES: asserts
5+
6+
func testMismatch1() -> Int {
7+
if .random() {
8+
print("hello")
9+
0
10+
} else {
11+
print("hi")
12+
"" // expected-error {{cannot convert value of type 'String' to specified type 'Int'}}
13+
}
14+
}
15+
16+
func testMismatch2() -> Int {
17+
let x = if .random() {
18+
print("hello")
19+
0 // expected-error {{branches have mismatching types 'Int' and 'String'}}
20+
} else {
21+
print("hi")
22+
""
23+
}
24+
return x
25+
}
26+
27+
func testVoidNeverConversion() {
28+
// We allow the T -> Void and Never -> T conversions, same as the single
29+
// expression case.
30+
let _: () -> Void = {
31+
if .random() {
32+
print("hello")
33+
1 // expected-warning {{integer literal is unused}}
34+
} else {
35+
print("there")
36+
2 // expected-warning {{integer literal is unused}}
37+
}
38+
}
39+
let _: () -> Void = {
40+
if .random() {
41+
print("hello")
42+
fatalError()
43+
} else {
44+
0 // expected-warning {{integer literal is unused}}
45+
}
46+
}
47+
// We fall back to Void if we have a mismatch.
48+
let _ = {
49+
if .random() {
50+
()
51+
"" // expected-warning {{string literal is unused}}
52+
} else {
53+
()
54+
0 // expected-warning {{integer literal is unused}}
55+
}
56+
}
57+
// Unless there's a contextual type.
58+
let _ = { () -> String in
59+
if .random() {
60+
()
61+
""
62+
} else {
63+
()
64+
0 // expected-error {{cannot convert value of type 'Int' to specified type 'String'}}
65+
}
66+
}
67+
let _: () -> String = {
68+
if .random() {
69+
()
70+
""
71+
} else {
72+
()
73+
0 // expected-error {{cannot convert value of type 'Int' to specified type 'String'}}
74+
}
75+
}
76+
let _ = {
77+
if .random() {
78+
()
79+
()
80+
} else {
81+
0 // expected-warning {{integer literal is unused}}
82+
}
83+
}
84+
let _ = {
85+
switch Bool.random() {
86+
case true:
87+
print("hello")
88+
""
89+
case false:
90+
fatalError()
91+
}
92+
}
93+
func foo() -> Int {
94+
switch Bool.random() {
95+
case true:
96+
fatalError()
97+
case false:
98+
()
99+
fatalError()
100+
}
101+
}
102+
func bar() -> Int {
103+
switch Bool.random() {
104+
case true:
105+
()
106+
fatalError()
107+
case false:
108+
()
109+
0
110+
}
111+
}
112+
}
113+
114+
enum Either<T, U> {
115+
case first(T), second(U)
116+
}
117+
118+
@resultBuilder
119+
struct Builder {
120+
static func buildBlock<T>(_ x: T) -> T { x }
121+
static func buildBlock<T, U>(_ x: T, _ y: U) -> (T, U) { (x, y) }
122+
123+
static func buildEither<T, U>(first x: T) -> Either<T, U> { .first(x) }
124+
static func buildEither<T, U>(second x: U) -> Either<T, U> { .second(x) }
125+
126+
static func buildExpression(_ x: Double) -> Double { x }
127+
static func buildExpression<T>(_ x: T) -> T { x }
128+
}
129+
130+
// Implicit 'then' statements are transparent to the result builder transform.
131+
@Builder
132+
func testBuilder1() -> Either<(Void, Int), (Void, Int)> {
133+
if .random() {
134+
()
135+
0
136+
} else {
137+
()
138+
1
139+
}
140+
}
141+
142+
@Builder
143+
func testBuilder2() -> (Either<(Void, Int), String>, Void) {
144+
if .random() {
145+
()
146+
0
147+
} else {
148+
""
149+
}
150+
()
151+
}
152+
153+
@Builder
154+
func testBuilder3() -> Either<Int, String> {
155+
// Bindings should still work though.
156+
let x = if .random() {
157+
print("hello")
158+
0
159+
} else {
160+
()
161+
1
162+
}
163+
if .random() {
164+
x
165+
} else {
166+
""
167+
}
168+
}

0 commit comments

Comments
 (0)