go/ssa: emit short-circuit control flow for boolean switch cases

Previously, switch { case x && y: ... } would materialize the
condition as a boolean. In this case, because it contains &&,
it would be a phi node. This extra phi caused the yield analyzer
to emit false positives compared to the equivalent if/else chain.

Now, the SSA builder emits short-circuit control flow for boolean
switch cases. Happily, this crosses off a TODO that has been there
since day 1: https://go.dev/issue/77681#issuecomment-3969760732A

For example, this input:

	func f(x, y bool) {
		switch {
		case x && y:
			print("hello")
		}
	}

formerly generated this SSA code:

	0:
		if x goto 3 else 4
	1:
		return
	2:
		t0 = print("hello":string)
		jump 1
	3:
		jump 4
	4:
		t1 = phi [0: false:bool, 3: y
		if t1 goto 2 else 1

but now generates this code:

	0:
		if x goto 3 else 1
	1:
		return
	2:
		t0 = print("hello":string)
		jump 1
	3:
		if y goto 2 else 1

+ regression test for gopls' yield analyzer

Fixes golang/go#77681

Change-Id: Ife9dee8443c0f2c09e95cacbfcefd5baacc97d99
Reviewed-on: https://go-review.googlesource.com/c/tools/+/749580
Reviewed-by: Robert Griesemer <gri@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
Commit-Queue: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/go/ssa/builder.go b/go/ssa/builder.go
index a75257c..3336203 100644
--- a/go/ssa/builder.go
+++ b/go/ssa/builder.go
@@ -1467,13 +1467,14 @@
 		var nextCond *BasicBlock
 		for _, cond := range cc.List {
 			nextCond = fn.newBasicBlock("switch.next")
-			// TODO(adonovan): opt: when tag==vTrue, we'd
-			// get better code if we use b.cond(cond)
-			// instead of BinOp(EQL, tag, b.expr(cond))
-			// followed by If.  Don't forget conversions
-			// though.
-			cond := emitCompare(fn, token.EQL, tag, b.expr(fn, cond), cond.Pos())
-			emitIf(fn, cond, body, nextCond)
+			// For boolean switches, emit short-circuit control flow,
+			// just like an if/else-chain.
+			if tag == vTrue && !isNonTypeParamInterface(fn.info.Types[cond].Type) {
+				b.cond(fn, cond, body, nextCond)
+			} else {
+				c := emitCompare(fn, token.EQL, tag, b.expr(fn, cond), cond.Pos())
+				emitIf(fn, c, body, nextCond)
+			}
 			fn.currentBlock = nextCond
 		}
 		fn.currentBlock = body
diff --git a/gopls/internal/analysis/yield/testdata/src/a/a.go b/gopls/internal/analysis/yield/testdata/src/a/a.go
index 820cabd..15ee3f5 100644
--- a/gopls/internal/analysis/yield/testdata/src/a/a.go
+++ b/gopls/internal/analysis/yield/testdata/src/a/a.go
@@ -145,3 +145,21 @@
 		}
 	}
 }
+
+// Regression test for issue #77681. A boolean switch is now
+// handled just like an if/else chain in the SSA builder.
+func switchShortCircuit(seq iter.Seq[int]) iter.Seq[int] {
+	return func(yield func(int) bool) {
+		for item := range seq {
+			isZero := item == 0
+			switch {
+			case !isZero && !yield(item): // ok
+				return
+			case !isZero:
+				continue
+			case !yield(0): // ok
+				return
+			}
+		}
+	}
+}