Skip to content

Commit 96da632

Browse files
committed
[Sema] Error on redundant try/awaits for if/switch expressions
Look through `try`/`await` markers when looking for out of place if/switch expressions, and customize the effect checking diagnostic such that we error that `try`/`await` are redundant on `if`/`switch` expressions.
1 parent e4ec137 commit 96da632

File tree

5 files changed

+141
-14
lines changed

5 files changed

+141
-14
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,8 @@ ERROR(single_value_stmt_branch_must_end_in_throw,none,
11281128
ERROR(cannot_jump_in_single_value_stmt,none,
11291129
"cannot '%0' in '%1' when used as expression",
11301130
(StmtKind, StmtKind))
1131+
ERROR(effect_marker_on_single_value_stmt,none,
1132+
"'%0' may not be used on '%1' expression", (StringRef, StmtKind))
11311133

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

lib/AST/Expr.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2530,6 +2530,16 @@ SingleValueStmtExpr::tryDigOutSingleValueStmtExpr(Expr *E) {
25302530
E = CE->getSubExpr();
25312531
continue;
25322532
}
2533+
// Look through try/await (this is invalid, but we'll error on it in
2534+
// effect checking).
2535+
if (auto *TE = dyn_cast<AnyTryExpr>(E)) {
2536+
E = TE->getSubExpr();
2537+
continue;
2538+
}
2539+
if (auto *AE = dyn_cast<AwaitExpr>(E)) {
2540+
E = AE->getSubExpr();
2541+
continue;
2542+
}
25332543
break;
25342544
}
25352545
return dyn_cast<SingleValueStmtExpr>(E);

lib/Sema/TypeCheckEffects.cpp

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2720,11 +2720,12 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
27202720
// course we're in a context that could never handle an 'async'. Then, we
27212721
// produce an error.
27222722
if (!Flags.has(ContextFlags::HasAnyAsyncSite)) {
2723-
if (CurContext.handlesAsync(ConditionalEffectKind::Conditional))
2724-
Ctx.Diags.diagnose(E->getAwaitLoc(), diag::no_async_in_await);
2725-
else
2723+
if (CurContext.handlesAsync(ConditionalEffectKind::Conditional)) {
2724+
diagnoseRedundantAwait(E);
2725+
} else {
27262726
CurContext.diagnoseUnhandledAsyncSite(Ctx.Diags, E, None,
27272727
/*forAwait=*/ true);
2728+
}
27282729
}
27292730

27302731
// Inform the parent of the walk that an 'await' exists here.
@@ -2742,7 +2743,7 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
27422743
// Warn about 'try' expressions that weren't actually needed.
27432744
if (!Flags.has(ContextFlags::HasTryThrowSite)) {
27442745
if (!E->isImplicit())
2745-
Ctx.Diags.diagnose(E->getTryLoc(), diag::no_throw_in_try);
2746+
diagnoseRedundantTry(E);
27462747

27472748
// Diagnose all the call sites within a single unhandled 'try'
27482749
// at the same time.
@@ -2762,9 +2763,8 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
27622763
E->getSubExpr()->walk(*this);
27632764

27642765
// Warn about 'try' expressions that weren't actually needed.
2765-
if (!Flags.has(ContextFlags::HasTryThrowSite)) {
2766-
Ctx.Diags.diagnose(E->getLoc(), diag::no_throw_in_try);
2767-
}
2766+
if (!Flags.has(ContextFlags::HasTryThrowSite))
2767+
diagnoseRedundantTry(E);
27682768

27692769
scope.preserveCoverageFromOptionalOrForcedTryOperand();
27702770
return ShouldNotRecurse;
@@ -2778,9 +2778,8 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
27782778
E->getSubExpr()->walk(*this);
27792779

27802780
// Warn about 'try' expressions that weren't actually needed.
2781-
if (!Flags.has(ContextFlags::HasTryThrowSite)) {
2782-
Ctx.Diags.diagnose(E->getLoc(), diag::no_throw_in_try);
2783-
}
2781+
if (!Flags.has(ContextFlags::HasTryThrowSite))
2782+
diagnoseRedundantTry(E);
27842783

27852784
scope.preserveCoverageFromOptionalOrForcedTryOperand();
27862785
return ShouldNotRecurse;
@@ -2818,6 +2817,30 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
28182817
return ShouldRecurse;
28192818
}
28202819

2820+
void diagnoseRedundantTry(AnyTryExpr *E) const {
2821+
if (auto *SVE = SingleValueStmtExpr::tryDigOutSingleValueStmtExpr(E)) {
2822+
// For an if/switch expression, produce an error instead of a warning.
2823+
Ctx.Diags.diagnose(E->getTryLoc(),
2824+
diag::effect_marker_on_single_value_stmt,
2825+
"try", SVE->getStmt()->getKind())
2826+
.highlight(E->getTryLoc());
2827+
return;
2828+
}
2829+
Ctx.Diags.diagnose(E->getTryLoc(), diag::no_throw_in_try);
2830+
}
2831+
2832+
void diagnoseRedundantAwait(AwaitExpr *E) const {
2833+
if (auto *SVE = SingleValueStmtExpr::tryDigOutSingleValueStmtExpr(E)) {
2834+
// For an if/switch expression, produce an error instead of a warning.
2835+
Ctx.Diags.diagnose(E->getAwaitLoc(),
2836+
diag::effect_marker_on_single_value_stmt,
2837+
"await", SVE->getStmt()->getKind())
2838+
.highlight(E->getAwaitLoc());
2839+
return;
2840+
}
2841+
Ctx.Diags.diagnose(E->getAwaitLoc(), diag::no_async_in_await);
2842+
}
2843+
28212844
void diagnoseUncoveredAsyncSite(const Expr *anchor) const {
28222845
auto asyncPointIter = uncoveredAsync.find(anchor);
28232846
if (asyncPointIter == uncoveredAsync.end())

test/expr/unary/if_expr.swift

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-typecheck-verify-swift
1+
// RUN: %target-typecheck-verify-swift -disable-availability-checking
22

33
// MARK: Functions
44

@@ -423,7 +423,7 @@ func stmts() {
423423

424424
if try if .random() { true } else { false } {}
425425
// expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
426-
// expected-warning@-2 {{no calls to throwing functions occur within 'try' expression}}
426+
// expected-error@-2 {{'try' may not be used on 'if' expression}}
427427

428428
// expected-error@+1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
429429
guard if .random() { true } else { false } else {
@@ -706,3 +706,49 @@ func return4() throws -> Int {
706706
}
707707
return i
708708
}
709+
710+
// MARK: Effect specifiers
711+
712+
func tryIf1() -> Int {
713+
try if .random() { 0 } else { 1 }
714+
// expected-error@-1 {{'try' may not be used on 'if' expression}}
715+
}
716+
717+
func tryIf2() -> Int {
718+
let x = try if .random() { 0 } else { 1 }
719+
// expected-error@-1 {{'try' may not be used on 'if' expression}}
720+
return x
721+
}
722+
723+
func tryIf3() -> Int {
724+
return try if .random() { 0 } else { 1 }
725+
// expected-error@-1 {{'try' may not be used on 'if' expression}}
726+
}
727+
728+
func awaitIf1() async -> Int {
729+
await if .random() { 0 } else { 1 }
730+
// expected-error@-1 {{'await' may not be used on 'if' expression}}
731+
}
732+
733+
func awaitIf2() async -> Int {
734+
let x = await if .random() { 0 } else { 1 }
735+
// expected-error@-1 {{'await' may not be used on 'if' expression}}
736+
return x
737+
}
738+
739+
func awaitIf3() async -> Int {
740+
return await if .random() { 0 } else { 1 }
741+
// expected-error@-1 {{'await' may not be used on 'if' expression}}
742+
}
743+
744+
func tryAwaitIf1() async throws -> Int {
745+
try await if .random() { 0 } else { 1 }
746+
// expected-error@-1 {{'try' may not be used on 'if' expression}}
747+
// expected-error@-2 {{'await' may not be used on 'if' expression}}
748+
}
749+
750+
func tryAwaitIf2() async throws -> Int {
751+
try await if .random() { 0 } else { 1 } as Int
752+
// expected-error@-1 {{'try' may not be used on 'if' expression}}
753+
// expected-error@-2 {{'await' may not be used on 'if' expression}}
754+
}

test/expr/unary/switch_expr.swift

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-typecheck-verify-swift
1+
// RUN: %target-typecheck-verify-swift -disable-availability-checking
22

33
// MARK: Functions
44

@@ -588,7 +588,7 @@ func stmts() {
588588
// expected-error@-1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
589589

590590
if try switch Bool.random() { case true: true case false: true } {}
591-
// expected-warning@-1 {{no calls to throwing functions occur within 'try' expression}}
591+
// expected-error@-1 {{'try' may not be used on 'switch' expression}}
592592
// expected-error@-2 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
593593

594594
// expected-error@+1 {{'switch' may only be used as expression in return, throw, or as the source of an assignment}}
@@ -964,3 +964,49 @@ func continueToInner() -> Int {
964964
1 // expected-warning {{integer literal is unused}}
965965
}
966966
}
967+
968+
// MARK: Effect specifiers
969+
970+
func trySwitch1() -> Int {
971+
try switch Bool.random() { case true: 0 case false: 1 }
972+
// expected-error@-1 {{'try' may not be used on 'switch' expression}}
973+
}
974+
975+
func trySwitch2() -> Int {
976+
let x = try switch Bool.random() { case true: 0 case false: 1 }
977+
// expected-error@-1 {{'try' may not be used on 'switch' expression}}
978+
return x
979+
}
980+
981+
func trySwitch3() -> Int {
982+
return try switch Bool.random() { case true: 0 case false: 1 }
983+
// expected-error@-1 {{'try' may not be used on 'switch' expression}}
984+
}
985+
986+
func awaitSwitch1() async -> Int {
987+
await switch Bool.random() { case true: 0 case false: 1 }
988+
// expected-error@-1 {{'await' may not be used on 'switch' expression}}
989+
}
990+
991+
func awaitSwitch2() async -> Int {
992+
let x = await switch Bool.random() { case true: 0 case false: 1 }
993+
// expected-error@-1 {{'await' may not be used on 'switch' expression}}
994+
return x
995+
}
996+
997+
func awaitSwitch3() async -> Int {
998+
return await switch Bool.random() { case true: 0 case false: 1 }
999+
// expected-error@-1 {{'await' may not be used on 'switch' expression}}
1000+
}
1001+
1002+
func tryAwaitSwitch1() async throws -> Int {
1003+
try await switch Bool.random() { case true: 0 case false: 1 }
1004+
// expected-error@-1 {{'try' may not be used on 'switch' expression}}
1005+
// expected-error@-2 {{'await' may not be used on 'switch' expression}}
1006+
}
1007+
1008+
func tryAwaitSwitch2() async throws -> Int {
1009+
try await switch Bool.random() { case true: 0 case false: 1 } as Int
1010+
// expected-error@-1 {{'try' may not be used on 'switch' expression}}
1011+
// expected-error@-2 {{'await' may not be used on 'switch' expression}}
1012+
}

0 commit comments

Comments
 (0)