blob: 6e338cd68bfbb86c379d11a23f2463079f174ea3 [file]
// run
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// A recover called in the body of a range-over-func loop takes effect,
// just as it would anywhere else in the function containing the loop.
// The body runs in a frame of its own, called from the iterator, but in
// the source it is part of that function, and the frames the compiler
// uses to run the loop must not change what recover means.
package main
import "fmt"
func iter(yield func(int) bool) { yield(0) }
var fails int
func check(name string, got, want any) {
if got != want {
fmt.Printf("%s = %v, want %v\n", name, got, want)
fails++
}
}
// recovered reports what a call of f, deferred by a panicking function,
// recovered, or nil if the panic went unrecovered.
func recovered(f func(*any)) (got any) {
defer func() {
if recover() != nil {
got = nil // the panic escaped f
}
}()
var r any
func() {
defer f(&r)
panic("boom")
}()
return r
}
// A recover in a loop body recovers, and returns the panic value.
func body(r *any) {
for range iter {
*r = recover()
}
}
// So does one in the body of a loop nested in another loop's body.
func nested(r *any) {
for range iter {
for range iter {
*r = recover()
}
}
}
// A recover in a func literal in the body does not: it is two calls away
// from the deferred function, not one, exactly as outside a loop.
func inClosure(r *any) {
for range iter {
func() { *r = recover() }()
}
}
// A recover in a loop in a function that was not the one deferred does
// not recover either.
func inCallee(r *any) {
helper(r)
}
func helper(r *any) {
for range iter {
*r = recover()
}
}
// Ranging over something other than a function must behave the same way.
func overInt(r *any) {
for range 1 {
*r = recover()
}
}
func overSlice(r *any) {
for range []int{0} {
*r = recover()
}
}
func overChan(r *any) {
ch := make(chan int, 1)
ch <- 0
close(ch)
for range ch {
*r = recover()
}
}
// A defer in the body defers to the containing function, and a recover in
// the body still works alongside it.
func withDefer(r *any) {
for range iter {
defer func() { *r = "deferred ran" }()
if recover() == nil {
*r = "did not recover"
return
}
}
}
// Recovering twice: the second call reports no panic left to recover.
func twice(r *any) {
for range iter {
first := recover()
second := recover()
*r = fmt.Sprint(first, " ", second)
}
}
// A whole panic and recover inside the body, before recovering the panic
// the body was deferred for.
func inner(r *any) {
for range iter {
func() {
defer func() { recover() }()
panic("inner")
}()
*r = recover()
}
}
func main() {
check("body", recovered(body), "boom")
check("nested", recovered(nested), "boom")
check("inClosure", recovered(inClosure), nil)
check("inCallee", recovered(inCallee), nil)
check("overInt", recovered(overInt), "boom")
check("overSlice", recovered(overSlice), "boom")
check("overChan", recovered(overChan), "boom")
check("withDefer", recovered(withDefer), "deferred ran")
check("twice", recovered(twice), "boom <nil>")
check("inner", recovered(inner), "boom")
// With nothing panicking, a recover in a loop body returns nil and the
// loop runs as usual.
n := 0
var got any = "not nil"
for range iter {
got = recover()
n++
}
check("no panic: recover()", got, nil)
check("no panic: iterations", n, 1)
if fails > 0 {
panic("failed")
}
}