| // errorcheck -0 -m -d=closure | |
| //go:build !goexperiment.newinliner | |
| // 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. | |
| // Test that closures do not capture variables that hold a constant, and | |
| // stop being closures if they capture nothing else. | |
| package main | |
| var sink func() | |
| // The closure captures s only, which holds a constant. | |
| func constOnly() { | |
| s := "" | |
| f := func() { println(s) } // ERROR "can inline constOnly.func1" "func literal does not escape" "closure converted to global" | |
| g(f) | |
| } | |
| // Same, but the closure escapes, so its closure record would be heap | |
| // allocated if it still captured anything. | |
| func constOnlyEscapes() { | |
| i := 42 | |
| sink = func() { println(i) } // ERROR "can inline constOnlyEscapes.func1" "func literal escapes to heap" "closure converted to global" | |
| g(nil) | |
| } | |
| // Same as constOnlyEscapes, but without any call, so that no | |
| // ReassignOracle has been built for the enclosing function yet. | |
| func constOnlyNoCall() { // ERROR "can inline constOnlyNoCall" | |
| i := 42 | |
| sink = func() { println(i) } // ERROR "can inline constOnlyNoCall.func1" "func literal escapes to heap" "closure converted to global" | |
| } | |
| // Variables that do not hold a constant are still captured. | |
| func mixed(n int) { | |
| s := "" | |
| r := "" | |
| f := func() { // ERROR "can inline mixed.func1" "func literal does not escape" "stack closure, captured vars = \[n r\]" | |
| println(s) | |
| println(n) | |
| println(&r) | |
| } | |
| g(f) | |
| } | |
| // Nested closures capturing the same variable. | |
| func nested() { // ERROR "can inline nested" | |
| s := "" | |
| g(func() { // ERROR "can inline nested.func1" "func literal does not escape" "closure converted to global" | |
| g(func() { println(s) }) // ERROR "can inline nested.func1.1" "func literal does not escape" "closure converted to global" | |
| }) | |
| } | |
| // The constant is still visible to the interface conversion below, which | |
| // therefore does not need to allocate. See also test/fixedbugs/issue4085b.go, | |
| // where replacing the captured variable with a literal instead would turn a | |
| // run time panic into a compile time error. | |
| func iface() { | |
| i := 42 | |
| f := func() { h(i) } // ERROR "can inline iface.func1" "func literal does not escape" "closure converted to global" "42 does not escape" | |
| g(f) | |
| } | |
| // A variable that is reassigned after being captured is captured by | |
| // reference, so its value is not a constant. | |
| func reassigned() { | |
| s := "" | |
| f := func() { println(s) } // ERROR "can inline reassigned.func1" "func literal does not escape" "stack closure, captured vars = \[s\]" | |
| s = "x" | |
| g(f) | |
| } | |
| // A variable whose address is taken is captured by reference. | |
| func addrtaken() { | |
| s := "" | |
| f := func() { println(s) } // ERROR "can inline addrtaken.func1" "func literal does not escape" "stack closure, captured vars = \[s\]" | |
| _ = &s | |
| g(f) | |
| } | |
| //go:noinline | |
| func g(func()) { | |
| } | |
| //go:noinline | |
| func h(any) { | |
| } |