Skip to content

Commit 6641fa4

Browse files
committed
[Sema] Allow if/switch expressions in optional chain assignments
Make sure we look through a wrapping `OptionalEvaluationExpr` and its injections when looking for an assignment to mark a valid if/switch source expression. rdar://109305454
1 parent 6be7085 commit 6641fa4

File tree

3 files changed

+304
-7
lines changed

3 files changed

+304
-7
lines changed

lib/Sema/MiscDiagnostics.cpp

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3842,6 +3842,23 @@ class SingleValueStmtUsageChecker final : public ASTWalker {
38423842
return MacroWalking::Expansion;
38433843
}
38443844

3845+
AssignExpr *findAssignment(Expr *E) const {
3846+
// Don't consider assignments if we have a parent expression (as otherwise
3847+
// this would be effectively allowing it in an arbitrary expression
3848+
// position).
3849+
if (Parent.getAsExpr())
3850+
return nullptr;
3851+
3852+
// Look through optional exprs, which are present for e.g x?.y = z, as
3853+
// we wrap the entire assign in the optional evaluation of the destination.
3854+
if (auto *OEE = dyn_cast<OptionalEvaluationExpr>(E)) {
3855+
E = OEE->getSubExpr();
3856+
while (auto *IIO = dyn_cast<InjectIntoOptionalExpr>(E))
3857+
E = IIO->getSubExpr();
3858+
}
3859+
return dyn_cast<AssignExpr>(E);
3860+
}
3861+
38453862
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
38463863
if (auto *SVE = dyn_cast<SingleValueStmtExpr>(E)) {
38473864
// Diagnose a SingleValueStmtExpr in a context that we do not currently
@@ -3917,13 +3934,9 @@ class SingleValueStmtUsageChecker final : public ASTWalker {
39173934
return Action::Continue(E);
39183935
}
39193936

3920-
// Valid as the source of an assignment, as long as it's not a nested
3921-
// expression (as otherwise this would be effectively allowing it in an
3922-
// arbitrary expression position).
3923-
if (auto *AE = dyn_cast<AssignExpr>(E)) {
3924-
if (!Parent.getAsExpr())
3925-
markValidSingleValueStmt(AE->getSrc());
3926-
}
3937+
// Valid as the source of an assignment.
3938+
if (auto *AE = findAssignment(E))
3939+
markValidSingleValueStmt(AE->getSrc());
39273940

39283941
// Valid as a single expression body of a closure. This is needed in
39293942
// addition to ReturnStmt checking, as we will remove the return if the

test/SILGen/if_expr.swift

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,21 @@ enum E {
272272

273273
struct S {
274274
var i: Int
275+
var opt: Int?
276+
277+
var computed: Int {
278+
get { i }
279+
set { i = newValue }
280+
}
281+
var coroutined: Int {
282+
_read { yield i }
283+
_modify { yield &i }
284+
}
285+
286+
subscript(x: Int) -> Int {
287+
get { i }
288+
set { i = newValue }
289+
}
275290

276291
mutating func testAssign1(_ x: E) {
277292
i = if case .e(let y) = x { y } else { 0 }
@@ -358,3 +373,130 @@ struct S {
358373
return i
359374
}
360375
}
376+
377+
enum G {
378+
case e(Int)
379+
case f
380+
}
381+
382+
struct TestLValues {
383+
var s: S
384+
var opt: S?
385+
var optopt: S??
386+
387+
mutating func testOptPromote1() {
388+
opt = if .random() { s } else { s }
389+
}
390+
391+
mutating func testOptPromote2() {
392+
optopt = if .random() { s } else { s }
393+
}
394+
395+
mutating func testStored1() {
396+
s.i = if .random() { 1 } else { 0 }
397+
}
398+
399+
mutating func testStored2() throws {
400+
s.i = if .random() { 1 } else { throw Err() }
401+
}
402+
403+
mutating func testComputed1() {
404+
s.computed = if .random() { 1 } else { 0 }
405+
}
406+
407+
mutating func testComputed2() throws {
408+
s.computed = if .random() { 1 } else { throw Err() }
409+
}
410+
411+
mutating func testCoroutined1() {
412+
s.coroutined = if .random() { 1 } else { 0 }
413+
}
414+
415+
mutating func testCoroutined2() throws {
416+
s.coroutined = if .random() { 1 } else { throw Err() }
417+
}
418+
419+
mutating func testOptionalChain1() {
420+
opt?.i = if .random() { 1 } else { 0 }
421+
}
422+
423+
mutating func testOptionalChain2() throws {
424+
opt?.i = if .random() { throw Err() } else { 0 }
425+
}
426+
427+
mutating func testOptionalChain3(_ g: G) {
428+
opt?.i = if case .e(let i) = g { i } else { 0 }
429+
}
430+
431+
mutating func testOptionalChain4(_ g: G) throws {
432+
opt?.i = if case .e(let i) = g { i } else { throw Err() }
433+
}
434+
435+
mutating func testOptionalChain5(_ g: G) throws {
436+
opt?.computed = if case .e(let i) = g { i } else { throw Err() }
437+
}
438+
439+
mutating func testOptionalChain6(_ g: G) throws {
440+
opt?.coroutined = if case .e(let i) = g { i } else { throw Err() }
441+
}
442+
443+
mutating func testOptionalChain7() throws {
444+
optopt??.i = if .random() { 1 } else { throw Err() }
445+
}
446+
447+
mutating func testOptionalChain8() throws {
448+
optopt??.opt = if .random() { 1 } else { throw Err() }
449+
}
450+
451+
mutating func testOptionalChain9() throws {
452+
optopt??.opt? = if .random() { 1 } else { throw Err() }
453+
}
454+
455+
mutating func testOptionalForce1() throws {
456+
opt!.i = if .random() { throw Err() } else { 0 }
457+
}
458+
459+
mutating func testOptionalForce2() throws {
460+
opt!.computed = if .random() { throw Err() } else { 0 }
461+
}
462+
463+
mutating func testOptionalForce3(_ g: G) throws {
464+
opt!.coroutined = if case .e(let i) = g { i } else { throw Err() }
465+
}
466+
467+
mutating func testOptionalForce4() throws {
468+
optopt!!.i = if .random() { 1 } else { throw Err() }
469+
}
470+
471+
mutating func testOptionalForce5() throws {
472+
optopt!!.opt = if .random() { 1 } else { throw Err() }
473+
}
474+
475+
mutating func testOptionalForce6() throws {
476+
optopt!!.opt! = if .random() { 1 } else { throw Err() }
477+
}
478+
479+
mutating func testSubscript1() throws {
480+
s[5] = if .random() { 1 } else { throw Err() }
481+
}
482+
483+
mutating func testSubscript2() throws {
484+
opt?[5] = if .random() { 1 } else { throw Err() }
485+
}
486+
487+
mutating func testSubscript3() throws {
488+
opt![5] = if .random() { 1 } else { throw Err() }
489+
}
490+
491+
mutating func testKeyPath1(_ kp: WritableKeyPath<S, Int>) throws {
492+
s[keyPath: kp] = if .random() { 1 } else { throw Err() }
493+
}
494+
495+
mutating func testKeyPath2(_ kp: WritableKeyPath<S, Int>) throws {
496+
opt?[keyPath: kp] = if .random() { 1 } else { throw Err() }
497+
}
498+
499+
mutating func testKeyPath3(_ kp: WritableKeyPath<S, Int>) throws {
500+
opt![keyPath: kp] = if .random() { 1 } else { throw Err() }
501+
}
502+
}

test/SILGen/switch_expr.swift

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,21 @@ enum F {
367367

368368
struct S {
369369
var i: Int
370+
var opt: Int?
371+
372+
var computed: Int {
373+
get { i }
374+
set { i = newValue }
375+
}
376+
var coroutined: Int {
377+
_read { yield i }
378+
_modify { yield &i }
379+
}
380+
381+
subscript(x: Int) -> Int {
382+
get { i }
383+
set { i = newValue }
384+
}
370385

371386
mutating func testAssign1(_ x: F) {
372387
i = switch x {
@@ -463,3 +478,130 @@ struct S {
463478
return i
464479
}
465480
}
481+
482+
enum G {
483+
case e(Int)
484+
case f
485+
}
486+
487+
struct TestLValues {
488+
var s: S
489+
var opt: S?
490+
var optopt: S??
491+
492+
mutating func testOptPromote1() {
493+
opt = switch Bool.random() { case true: s case false: s }
494+
}
495+
496+
mutating func testOptPromote2() {
497+
optopt = switch Bool.random() { case true: s case false: s }
498+
}
499+
500+
mutating func testStored1() {
501+
s.i = switch Bool.random() { case true: 1 case false: 0 }
502+
}
503+
504+
mutating func testStored2() throws {
505+
s.i = switch Bool.random() { case true: 1 case false: throw Err() }
506+
}
507+
508+
mutating func testComputed1() {
509+
s.computed = switch Bool.random() { case true: 1 case false: 0 }
510+
}
511+
512+
mutating func testComputed2() throws {
513+
s.computed = switch Bool.random() { case true: 1 case false: throw Err() }
514+
}
515+
516+
mutating func testCoroutined1() {
517+
s.coroutined = switch Bool.random() { case true: 1 case false: 0 }
518+
}
519+
520+
mutating func testCoroutined2() throws {
521+
s.coroutined = switch Bool.random() { case true: 1 case false: throw Err() }
522+
}
523+
524+
mutating func testOptionalChain1() {
525+
opt?.i = switch Bool.random() { case true: 1 case false: 0 }
526+
}
527+
528+
mutating func testOptionalChain2() throws {
529+
opt?.i = switch Bool.random() { case true: throw Err() case false: 0 }
530+
}
531+
532+
mutating func testOptionalChain3(_ g: G) {
533+
opt?.i = switch g { case .e(let i): i default: 0 }
534+
}
535+
536+
mutating func testOptionalChain4(_ g: G) throws {
537+
opt?.i = switch g { case .e(let i): i default: throw Err() }
538+
}
539+
540+
mutating func testOptionalChain5(_ g: G) throws {
541+
opt?.computed = switch g { case .e(let i): i default: throw Err() }
542+
}
543+
544+
mutating func testOptionalChain6(_ g: G) throws {
545+
opt?.coroutined = switch g { case .e(let i): i default: throw Err() }
546+
}
547+
548+
mutating func testOptionalChain7() throws {
549+
optopt??.i = switch Bool.random() { case true: 1 case false: throw Err() }
550+
}
551+
552+
mutating func testOptionalChain8() throws {
553+
optopt??.opt = switch Bool.random() { case true: 1 case false: throw Err() }
554+
}
555+
556+
mutating func testOptionalChain9() throws {
557+
optopt??.opt? = switch Bool.random() { case true: 1 case false: throw Err() }
558+
}
559+
560+
mutating func testOptionalForce1() throws {
561+
opt!.i = switch Bool.random() { case true: throw Err() case false: 0 }
562+
}
563+
564+
mutating func testOptionalForce2() throws {
565+
opt!.computed = switch Bool.random() { case true: throw Err() case false: 0 }
566+
}
567+
568+
mutating func testOptionalForce3(_ g: G) throws {
569+
opt!.coroutined = switch g { case .e(let i): i default: throw Err() }
570+
}
571+
572+
mutating func testOptionalForce4() throws {
573+
optopt!!.i = switch Bool.random() { case true: 1 case false: throw Err() }
574+
}
575+
576+
mutating func testOptionalForce5() throws {
577+
optopt!!.opt = switch Bool.random() { case true: 1 case false: throw Err() }
578+
}
579+
580+
mutating func testOptionalForce6() throws {
581+
optopt!!.opt! = switch Bool.random() { case true: 1 case false: throw Err() }
582+
}
583+
584+
mutating func testSubscript1() throws {
585+
s[5] = switch Bool.random() { case true: 1 case false: throw Err() }
586+
}
587+
588+
mutating func testSubscript2() throws {
589+
opt?[5] = switch Bool.random() { case true: 1 case false: throw Err() }
590+
}
591+
592+
mutating func testSubscript3() throws {
593+
opt![5] = switch Bool.random() { case true: 1 case false: throw Err() }
594+
}
595+
596+
mutating func testKeyPath1(_ kp: WritableKeyPath<S, Int>) throws {
597+
s[keyPath: kp] = switch Bool.random() { case true: 1 case false: throw Err() }
598+
}
599+
600+
mutating func testKeyPath2(_ kp: WritableKeyPath<S, Int>) throws {
601+
opt?[keyPath: kp] = switch Bool.random() { case true: 1 case false: throw Err() }
602+
}
603+
604+
mutating func testKeyPath3(_ kp: WritableKeyPath<S, Int>) throws {
605+
opt![keyPath: kp] = switch Bool.random() { case true: 1 case false: throw Err() }
606+
}
607+
}

0 commit comments

Comments
 (0)