Go version

go version go1.22.3 darwin/arm64

Output of go env in your module/workspace:

GO111MODULE=''
GOARCH='arm64'
GOBIN=''
GOCACHE='/Users/cameronmartin/Library/Caches/go-build'
GOENV='/Users/cameronmartin/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/cameronmartin/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/cameronmartin/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/Users/cameronmartin/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.3.darwin-arm64'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='go1.22.3+auto'
GOTOOLDIR='/Users/cameronmartin/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.3.darwin-arm64/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.22.3'
GCCGO='gccgo'
AR='ar'
CC='clang'
CXX='clang++'
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/k3/7h82b52d2gz972yl3nppjdtw0000gn/T/go-build29573479=/tmp/go-build -gno-record-gcc-switches -fno-common'

What did you do?

https://go.dev/play/p/_dEZsvc-bV-

What did you see happen?

<unknown line number>: internal compiler error: panic: unification reached recursion depth limit

What did you expect to see?

I expected a non-panic compilation error.

(But maybe I shouldn't have; with a couple of tweaks, a similar program is valid: https://go.dev/play/p/jJ8fTc7Uhie)

Comment From: griesemer

Slightly simplified reproducer:

package p

type S *S

func f[R, T *R](T) {}

func _() {
    var s S
    f(s)
}

panics as described. But adding a ~ (or swapping the type parameters) in f avoids the crash:

package p

type S *S

func f[R, T ~*R](T) {}  // <<< no crash with `~`

func _() {
    var s S
    f(s)
}

Comment From: griesemer

The handling of type element unification may not be quite right (see unify.go:566 ff.)

Too late to fix for 1.23 as this is tricky and may introduce subtle bugs. Moving to 1.24 since there's a work-around, and this is not a regression.

Comment From: griesemer

@taking and I looked at this some more. At the core of the issue we have the following situation.

1) Type inference correctly infers that T ➞ S from the parameter assignment to the function f. 2) Type inference then proceeds by trying to extract missing information for R. 3) The constraint for R is *R, the core type of the constraint is *R and *R is the only type in the typeset of the constraint, so (constraint) type inference infers R ➞ *R 4) Now inference looks at the constraint [T *R]. We know that T must be S so inference matches S against *R: S ≡ *R. This is the start of the cycle. 5) Because S is a defined type and *R is type literal, inference compares under(S) ≡ *R which is *S ≡ *R. 6) Both types are pointer types, so S ≡ R. 7) The inferred type for R is *R (step 3), so we arrive at S ≡ *R, which is where we were at step 4. (In the inference trace, the cycle starts a bit later because the LHS and RHS are swapped in the first round, but this doesn't change the behavior - the inference is symmetric with respect to LHS and RHS.)

From the inference trace:

== infer : [R₃, T₄](T₄) ➞ [<nil>, <nil>]
== function parameters: (T₄)
-- function arguments : [s (variable of type p.S)]
.  T₄ ≡ p.S     // assign
.  T₄ ➞ p.S
=> [R₃, T₄] ➞ [<nil>, p.S]

== type parameters: [R₃, T₄]
-- iteration 0
-- type parameter R₃ = <nil>: core(R₃) = *R, single = true
R₃ ➞ *R₃
-- type parameter T₄ = p.S: core(T₄) = *R, single = true
.  p.S ≡ *R₃    // inexact
.  *R₃ ≡ p.S    // swap
.  *R₃ ≡ under p.S
.  .  R₃ ≡ p.S  // inexact
.  .  .  *R₃ ≡ p.S      // inexact
.  .  .  *R₃ ≡ under p.S
.  .  .  .  R₃ ≡ p.S    // inexact
.  .  .  .  .  *R₃ ≡ p.S        // inexact
.  .  .  .  .  etc.

It's not obvious what the correct "fix" is in a case like this. We end up in a cycle because of the cyclic definition of S, but if we start with comparing T against *R instead of R against *R we avoid the endless recursion.

One approach might be to start with type parameters for which we already have a type argument inferred when doing the constraint type inference step.

Comment From: gopherbot

This issue is currently labeled as early-in-cycle for Go 1.24. That time is now, so a friendly reminder to look at it again.

Comment From: griesemer

Moving to 1.25; too late for 1.24.

Comment From: gopherbot

This issue is currently labeled as early-in-cycle for Go 1.25. That time is now, so a friendly reminder to look at it again.