Skip to content

Commit b3cdf2e

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 5008ee5 commit b3cdf2e

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
@@ -1100,6 +1100,8 @@ ERROR(single_value_stmt_branch_must_end_in_throw,none,
11001100
ERROR(cannot_jump_in_single_value_stmt,none,
11011101
"cannot '%0' in '%1' when used as expression",
11021102
(StmtKind, StmtKind))
1103+
ERROR(effect_marker_on_single_value_stmt,none,
1104+
"'%0' may not be used on '%1' expression", (StringRef, StmtKind))
11031105

11041106
ERROR(did_not_call_function_value,none,
11051107
"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
@@ -2471,6 +2471,16 @@ SingleValueStmtExpr::tryDigOutSingleValueStmtExpr(Expr *E) {
24712471
E = CE->getSubExpr();
24722472
continue;
24732473
}
2474+
// Look through try/await (this is invalid, but we'll error on it in
2475+
// effect checking).
2476+
if (auto *TE = dyn_cast<AnyTryExpr>(E)) {
2477+
E = TE->getSubExpr();
2478+
continue;
2479+
}
2480+
if (auto *AE = dyn_cast<AwaitExpr>(E)) {
2481+
E = AE->getSubExpr();
2482+
continue;
2483+
}
24742484
break;
24752485
}
24762486
return dyn_cast<SingleValueStmtExpr>(E);

lib/Sema/TypeCheckEffects.cpp

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2709,11 +2709,12 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
27092709
// course we're in a context that could never handle an 'async'. Then, we
27102710
// produce an error.
27112711
if (!Flags.has(ContextFlags::HasAnyAsyncSite)) {
2712-
if (CurContext.handlesAsync(ConditionalEffectKind::Conditional))
2713-
Ctx.Diags.diagnose(E->getAwaitLoc(), diag::no_async_in_await);
2714-
else
2712+
if (CurContext.handlesAsync(ConditionalEffectKind::Conditional)) {
2713+
diagnoseRedundantAwait(E);
2714+
} else {
27152715
CurContext.diagnoseUnhandledAsyncSite(Ctx.Diags, E, None,
27162716
/*forAwait=*/ true);
2717+
}
27172718
}
27182719

27192720
// Inform the parent of the walk that an 'await' exists here.
@@ -2731,7 +2732,7 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
27312732
// Warn about 'try' expressions that weren't actually needed.
27322733
if (!Flags.has(ContextFlags::HasTryThrowSite)) {
27332734
if (!E->isImplicit())
2734-
Ctx.Diags.diagnose(E->getTryLoc(), diag::no_throw_in_try);
2735+
diagnoseRedundantTry(E);
27352736

27362737
// Diagnose all the call sites within a single unhandled 'try'
27372738
// at the same time.
@@ -2751,9 +2752,8 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
27512752
E->getSubExpr()->walk(*this);
27522753

27532754
// Warn about 'try' expressions that weren't actually needed.
2754-
if (!Flags.has(ContextFlags::HasTryThrowSite)) {
2755-
Ctx.Diags.diagnose(E->getLoc(), diag::no_throw_in_try);
2756-
}
2755+
if (!Flags.has(ContextFlags::HasTryThrowSite))
2756+
diagnoseRedundantTry(E);
27572757

27582758
scope.preserveCoverageFromOptionalOrForcedTryOperand();
27592759
return ShouldNotRecurse;
@@ -2767,9 +2767,8 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
27672767
E->getSubExpr()->walk(*this);
27682768

27692769
// Warn about 'try' expressions that weren't actually needed.
2770-
if (!Flags.has(ContextFlags::HasTryThrowSite)) {
2771-
Ctx.Diags.diagnose(E->getLoc(), diag::no_throw_in_try);
2772-
}
2770+
if (!Flags.has(ContextFlags::HasTryThrowSite))
2771+
diagnoseRedundantTry(E);
27732772

27742773
scope.preserveCoverageFromOptionalOrForcedTryOperand();
27752774
return ShouldNotRecurse;
@@ -2807,6 +2806,30 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
28072806
return ShouldRecurse;
28082807
}
28092808

2809+
void diagnoseRedundantTry(AnyTryExpr *E) const {
2810+
if (auto *SVE = SingleValueStmtExpr::tryDigOutSingleValueStmtExpr(E)) {
2811+
// For an if/switch expression, produce an error instead of a warning.
2812+
Ctx.Diags.diagnose(E->getTryLoc(),
2813+
diag::effect_marker_on_single_value_stmt,
2814+
"try", SVE->getStmt()->getKind())
2815+
.highlight(E->getTryLoc());
2816+
return;
2817+
}
2818+
Ctx.Diags.diagnose(E->getTryLoc(), diag::no_throw_in_try);
2819+
}
2820+
2821+
void diagnoseRedundantAwait(AwaitExpr *E) const {
2822+
if (auto *SVE = SingleValueStmtExpr::tryDigOutSingleValueStmtExpr(E)) {
2823+
// For an if/switch expression, produce an error instead of a warning.
2824+
Ctx.Diags.diagnose(E->getAwaitLoc(),
2825+
diag::effect_marker_on_single_value_stmt,
2826+
"await", SVE->getStmt()->getKind())
2827+
.highlight(E->getAwaitLoc());
2828+
return;
2829+
}
2830+
Ctx.Diags.diagnose(E->getAwaitLoc(), diag::no_async_in_await);
2831+
}
2832+
28102833
void diagnoseUncoveredAsyncSite(const Expr *anchor) const {
28112834
auto asyncPointIter = uncoveredAsync.find(anchor);
28122835
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)