Skip to content

Commit 0d55f45

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 a40f1ab commit 0d55f45

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

@@ -440,7 +440,7 @@ func stmts() {
440440

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

445445
// expected-error@+1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
446446
guard if .random() { true } else { false } else {
@@ -723,3 +723,49 @@ func return4() throws -> Int {
723723
}
724724
return i
725725
}
726+
727+
// MARK: Effect specifiers
728+
729+
func tryIf1() -> Int {
730+
try if .random() { 0 } else { 1 }
731+
// expected-error@-1 {{'try' may not be used on 'if' expression}}
732+
}
733+
734+
func tryIf2() -> Int {
735+
let x = try if .random() { 0 } else { 1 }
736+
// expected-error@-1 {{'try' may not be used on 'if' expression}}
737+
return x
738+
}
739+
740+
func tryIf3() -> Int {
741+
return try if .random() { 0 } else { 1 }
742+
// expected-error@-1 {{'try' may not be used on 'if' expression}}
743+
}
744+
745+
func awaitIf1() async -> Int {
746+
await if .random() { 0 } else { 1 }
747+
// expected-error@-1 {{'await' may not be used on 'if' expression}}
748+
}
749+
750+
func awaitIf2() async -> Int {
751+
let x = await if .random() { 0 } else { 1 }
752+
// expected-error@-1 {{'await' may not be used on 'if' expression}}
753+
return x
754+
}
755+
756+
func awaitIf3() async -> Int {
757+
return await if .random() { 0 } else { 1 }
758+
// expected-error@-1 {{'await' may not be used on 'if' expression}}
759+
}
760+
761+
func tryAwaitIf1() async throws -> Int {
762+
try await if .random() { 0 } else { 1 }
763+
// expected-error@-1 {{'try' may not be used on 'if' expression}}
764+
// expected-error@-2 {{'await' may not be used on 'if' expression}}
765+
}
766+
767+
func tryAwaitIf2() async throws -> Int {
768+
try await if .random() { 0 } else { 1 } as Int
769+
// expected-error@-1 {{'try' may not be used on 'if' expression}}
770+
// expected-error@-2 {{'await' may not be used on 'if' expression}}
771+
}

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

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

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

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

0 commit comments

Comments
 (0)