Go version

go version go1.24.1 linux/amd64

Output of go env in your module/workspace:

AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/home/andrew/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/andrew/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build2188541391=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/home/andrew/git/src/github.com/meln5674/goj-compiler-bug/go.mod'
GOMODCACHE='/home/andrew/git/pkg/mod'
GONOPROXY='github.com/meln5675/*'
GONOSUMDB='github.com/meln5675/*'
GOOS='linux'
GOPATH='/home/andrew/git/'
GOPRIVATE='github.com/meln5675/*'
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go-1.24.1'
GOSUMDB='sum.golang.org'
GOTELEMETRY='off'
GOTELEMETRYDIR='/home/andrew/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go-1.24.1/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.24.1'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

https://go.dev/play/p/oCCeMP7r2Jk

This is not the original source this defect was discovered in, which is not publicly releasable. The above was derived by removing source elements until the error no longer occurred and then anonymizing names.

A few previous versions of go that I happened to have installed were spot checked across 1.23 and 1.22 and they all produced internal errors, so this is likely not a regression in 1.24.1 .

What did you see happen?

Received the following error message:

# <REDACTED: root package of module>
<unknown line number>: internal compiler error: unexpected types2.Invalid

Please file a bug report including a short program that triggers the error.
https://go.dev/issue/new

This occurs identically locally and in the linked go playground.

What did you expect to see?

The command to either succeed or produce a usable error message.

Comment From: randall77

The command to either succeed or produce a usable error message.

Any idea which one? I cannot fathom whether this is legal Go code or not.

Comment From: meln5674

I am fairly certain this is legal, as the original code base compiled and executed correctly until a single new field was added, which, unsurprisingly, ended up as the "B" field in the anonymized example. go fmt works without issue, so it is at very least syntactically legal.

Comment From: gabyhelp

Related Issues

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: meln5674

I have managed to simplify it further, along with a clearer naming scheme that disambiguates between types, fields, and type params: https://go.dev/play/p/bKryWGDZIep .

This makes it more obvious that there is a cycle in the type graph between B and D, which I originally believed to be the issue, but curiously, removing A fixes the error, as does making D a typedef for B directly or making B an alias for a pointer to D. It seems that it is this specific graph shape that triggers the bug.

Comment From: meln5674

Further progress: https://go.dev/play/p/5Pkrr97m2QT has an identical shape, but doesn't have the error. The only difference is that B is changed to a typedef of C, instead of an alias. I traced this back to what this represented in the original codebase, changed it from an alias to a typedef, and the original codebase compiles again. Something about that edge in the graph being an alias instead of a typedef must be the cause.

Comment From: ianlancetaylor

When using the alias these programs look invalid to me. They contain an invalid circular reference.

CC @griesemer @findleyr

Comment From: meln5674

Can you elaborate? B and D do not actually contain eachother in their representations, they are both empty structs by virtue of being an alias/typedef to C and E respectively, The reference is only in the type parameters. For example https://go.dev/play/p/yuQ_x9x_tA7 is valid, despite having the same circular reference in type parameters, and even having one (but not both) of the type edges be an alias.

Comment From: ianlancetaylor

A type alias means that you can substitute the name being defined with the right hand side. In the example https://go.dev/play/p/bKryWGDZIep after we substitute for TypeB we have type TypeD TypeE[TypeC[*TypeD]]. So TypeD is being defined in terms of itself. That's not the case when not using an alias: without the alias, there are different types that refer to each other.

That said, there are some cases where circular type definitions are OK. I suppose I don't really know whether this is one of them.

Comment From: findleyr

This panic was added in https://go.dev/cl/338973, in response to #25838.

Panicking stack:

runtime/debug.Stack()
    runtime/debug/stack.go:26 +0x5e
cmd/compile/internal/base.FatalfAt({0x139040?, 0xc0?}, {0xec4422, 0x19}, {0x0, 0x0, 0x0})
    cmd/compile/internal/base/print.go:230 +0x1ea
cmd/compile/internal/base.Fatalf(...)
    cmd/compile/internal/base/print.go:195
cmd/compile/internal/noder.(*pkgWriter).typIdx(0xc000139040, {0x102b790, 0x15d9440}, 0xc000116500)
    cmd/compile/internal/noder/writer.go:533 +0x597
cmd/compile/internal/noder.(*writer).typ(0xc0003f5080, {0x102b790?, 0x15d9440?})
    cmd/compile/internal/noder/writer.go:481 +0x2f
cmd/compile/internal/noder.(*writer).doObj(0xc0003f5080, 0xc0003f5130, {0x10341e0, 0xc0004009c0})
    cmd/compile/internal/noder/writer.go:883 +0x318
cmd/compile/internal/noder.(*pkgWriter).objIdx(0xc000139040, {0x10341e0, 0xc0004009c0})
    cmd/compile/internal/noder/writer.go:815 +0x84b
cmd/compile/internal/noder.(*pkgWriter).objInstIdx(0xc000139040, {0x10341e0, 0xc0004009c0}, 0x0, 0xc000116460)
    cmd/compile/internal/noder/writer.go:756 +0xf4
cmd/compile/internal/noder.(*writer).obj(0xc0003f4fd0, {0x10341e0?, 0xc0004009c0?}, 0xc0001350a0?)
    cmd/compile/internal/noder/writer.go:730 +0x33
cmd/compile/internal/noder.(*writer).namedType(0xc0003f4fd0, 0xc0004009c0, 0x0)
    cmd/compile/internal/noder/writer.go:631 +0x52
cmd/compile/internal/noder.(*pkgWriter).typIdx(0xc000139040, {0x102b6c8, 0xc0001350a0}, 0xc000116460)
    cmd/compile/internal/noder/writer.go:550 +0x8cc
cmd/compile/internal/noder.(*writer).typ(0xc0003f4f20, {0x102b6c8?, 0xc0001350a0?})
    cmd/compile/internal/noder/writer.go:481 +0x2f
cmd/compile/internal/noder.(*pkgWriter).typIdx(0xc000139040, {0x102b678, 0xc000036bb0}, 0xc000116460)
    cmd/compile/internal/noder/writer.go:578 +0x875
cmd/compile/internal/noder.(*pkgWriter).objInstIdx(0xc000139040, {0x10341e0, 0xc000400960}, 0xc0003fe3a8, 0xc000116460)
    cmd/compile/internal/noder/writer.go:754 +0x99
cmd/compile/internal/noder.(*writer).obj(0xc0003f4e70, {0x10341e0?, 0xc000400960?}, 0xc0001351f0?)
    cmd/compile/internal/noder/writer.go:730 +0x33
cmd/compile/internal/noder.(*writer).namedType(0xc0003f4e70, 0xc000400960, 0xc0003fe3a8)
    cmd/compile/internal/noder/writer.go:631 +0x52
cmd/compile/internal/noder.(*pkgWriter).typIdx(0xc000139040, {0x102b6c8, 0xc0001351f0}, 0xc000116460)
    cmd/compile/internal/noder/writer.go:550 +0x8cc
cmd/compile/internal/noder.(*writer).typ(0xc0003f4bb0, {0x102b6c8?, 0xc0001351f0?})
    cmd/compile/internal/noder/writer.go:481 +0x2f
cmd/compile/internal/noder.(*writer).doObj(0xc0003f4bb0, 0xc0003f4c60, {0x10341e0, 0xc000400900})
    cmd/compile/internal/noder/writer.go:873 +0x4c5
cmd/compile/internal/noder.(*pkgWriter).objIdx(0xc000139040, {0x10341e0, 0xc000400900})
    cmd/compile/internal/noder/writer.go:815 +0x84b
cmd/compile/internal/noder.(*pkgWriter).objInstIdx(0xc000139040, {0x10341e0, 0xc000400900}, 0x0, 0xc0001163c0)
    cmd/compile/internal/noder/writer.go:756 +0xf4
cmd/compile/internal/noder.(*writer).obj(0xc0003f4b00, {0x10341e0?, 0xc000400900?}, 0x1?)
    cmd/compile/internal/noder/writer.go:730 +0x33
cmd/compile/internal/noder.(*writer).namedType(0xc0003f4b00, 0xc000400900, 0x0)
    cmd/compile/internal/noder/writer.go:631 +0x52
cmd/compile/internal/noder.(*pkgWriter).typIdx(0xc000139040, {0x102b6f0, 0xc0003fcf00}, 0xc0001163c0)
    cmd/compile/internal/noder/writer.go:554 +0x3e5
cmd/compile/internal/noder.(*writer).typ(0xc0003f4a50, {0x102b6f0?, 0xc0003fcf00?})
    cmd/compile/internal/noder/writer.go:481 +0x2f
cmd/compile/internal/noder.(*pkgWriter).typIdx(0xc000139040, {0x102b678, 0xc000036b50}, 0xc0001163c0)
    cmd/compile/internal/noder/writer.go:578 +0x875
cmd/compile/internal/noder.(*writer).typ(0xc0003f46e0, {0x102b678?, 0xc000036b50?})
    cmd/compile/internal/noder/writer.go:481 +0x2f
cmd/compile/internal/noder.(*writer).doObj(0xc0003f46e0, 0xc0003f4790, {0x10341e0, 0xc0004008a0})
    cmd/compile/internal/noder/writer.go:883 +0x318
cmd/compile/internal/noder.(*pkgWriter).objIdx(0xc000139040, {0x10341e0, 0xc0004008a0})
    cmd/compile/internal/noder/writer.go:815 +0x84b
cmd/compile/internal/noder.(*pkgWriter).objInstIdx(0xc000139040, {0x10341e0, 0xc0004008a0}, 0x0, 0x0)
    cmd/compile/internal/noder/writer.go:756 +0xf4
cmd/compile/internal/noder.(*writer).obj(0xc0003f44d0, {0x10341e0?, 0xc0004008a0?}, 0x0?)
    cmd/compile/internal/noder/writer.go:730 +0x33
cmd/compile/internal/noder.writePkgStub({0x0?, {0x0?, 0x0?}}, {0xc0000686c0, 0x1, 0x1})
    cmd/compile/internal/noder/unified.go:343 +0x6fa
cmd/compile/internal/noder.unified({0x0?, {0x0?, 0x0?}}, {0xc0000686c0?, 0xdd9ce0?, 0x0?})
    cmd/compile/internal/noder/unified.go:195 +0xb3
cmd/compile/internal/noder.LoadPackage({0xc000020170, 0x1, 0x1})
    cmd/compile/internal/noder/noder.go:77 +0x43a
cmd/compile/internal/gc.Main(0xeeca88)
    cmd/compile/internal/gc/main.go:208 +0xcc5

Comment From: findleyr

Without digging too deeply, the problem seems to be that go/types creates a Typ[Invalid] without reporting an actual error. My guess is there is some inaccurate "error reported elsewhere" assumption.

Comment From: griesemer

This is due to buggy cycle detection. For:

package main

type (
    A        B
    B        = C[D]
    C[_ any] struct{}
    D        E[B]
    E[_ any] struct{}
)

func main() {}

we get the following type-checking trace (excerpt, using go/types):

testdata/manual.go:4:2: -- checking type A (white, objPath = )
testdata/manual.go:4:11:        .  -- type B
testdata/manual.go:5:2: .  .  -- checking type B (white, objPath = A)
testdata/manual.go:5:13:        .  .  .  -- type C[D]
testdata/manual.go:5:13:        .  .  .  .  -- instantiating type C with [D]
testdata/manual.go:5:13:        .  .  .  .  .  -- type C
testdata/manual.go:6:2: .  .  .  .  .  .  -- checking type C (white, objPath = A->B)
testdata/manual.go:6:6: .  .  .  .  .  .  .  -- type any
testdata/manual.go:6:6: .  .  .  .  .  .  .  => any (under = any) // *Alias
testdata/manual.go:6:11:        .  .  .  .  .  .  .  -- type struct{}
testdata/manual.go:6:11:        .  .  .  .  .  .  .  => struct{} // *Struct
testdata/manual.go:6:2: .  .  .  .  .  .  => type C[_ any] struct{} (black)
testdata/manual.go:5:13:        .  .  .  .  .  => C[_₁ any] (under = struct{}) // *Named
testdata/manual.go:5:15:        .  .  .  .  .  -- type D
testdata/manual.go:7:2: .  .  .  .  .  .  -- checking type D (white, objPath = A->B)
testdata/manual.go:7:11:        .  .  .  .  .  .  .  -- type E[B]
testdata/manual.go:7:11:        .  .  .  .  .  .  .  .  -- instantiating type E with [B]
testdata/manual.go:7:11:        .  .  .  .  .  .  .  .  .  -- type E
testdata/manual.go:8:2: .  .  .  .  .  .  .  .  .  .  -- checking type E (white, objPath = A->B->D)
testdata/manual.go:8:6: .  .  .  .  .  .  .  .  .  .  .  -- type any
testdata/manual.go:8:6: .  .  .  .  .  .  .  .  .  .  .  => any (under = any) // *Alias
testdata/manual.go:8:11:        .  .  .  .  .  .  .  .  .  .  .  -- type struct{}
testdata/manual.go:8:11:        .  .  .  .  .  .  .  .  .  .  .  => struct{} // *Struct
testdata/manual.go:8:2: .  .  .  .  .  .  .  .  .  .  => type E[_ any] struct{} (black)
testdata/manual.go:7:11:        .  .  .  .  .  .  .  .  .  => E[_₂ any] (under = struct{}) // *Named
testdata/manual.go:7:13:        .  .  .  .  .  .  .  .  .  -- type B
testdata/manual.go:5:2: .  .  .  .  .  .  .  .  .  .  ## cycle detected: objPath = B->D->B (len = 2)
testdata/manual.go:5:2: .  .  .  .  .  .  .  .  .  .  ## cycle contains: 0 values, 1 type definitions
testdata/manual.go:5:2: .  .  .  .  .  .  .  .  .  .  => cycle is valid
testdata/manual.go:7:13:        .  .  .  .  .  .  .  .  .  => B (under = invalid type) // *Alias
testdata/manual.go:7:11:        .  .  .  .  .  .  .  .  => invalid type
testdata/manual.go:7:11:        .  .  .  .  .  .  .  => invalid type // *Basic
testdata/manual.go:7:2: .  .  .  .  .  .  => type D invalid type (black)
testdata/manual.go:5:15:        .  .  .  .  .  => D (under = invalid type) // *Named
testdata/manual.go:5:13:        .  .  .  .  => C[D]
testdata/manual.go:5:13:        .  .  .  => C[D] (under = <nil>) // *Named
testdata/manual.go:5:2: .  .  => type B = C[D] (black)
testdata/manual.go:4:11:        .  => B (under = <nil>) // *Alias
testdata/manual.go:5:13:        -- Named.expandUnderlying C[D]
testdata/manual.go:5:13:        => C[D] (tparams = [], under = <nil>)
testdata/manual.go:4:2: => type A struct{} (black)

A cycle is detected but deemed "Valid", however, the type for B is invalid and (likely) never set later. Possibly related issues: Status: #68162, #68025, #60130.

Not sure there's an easy fix that doesn't simply push the problem around. We need to revamp/rewrite cycle detection.

Comment From: thepudds

So apparently there was some sort of blog post on go.dev/blog a couple of days ago?

In one of the external discussions about the recent blog entry, one commenter complained about an internal compiler error. After some prodding from the other commenters, they eventually shared the error message and repository.

Using that info, I was able to reproduce the error on tip:

$ git clone https://github.com/codr7/shi-go
$ cd shi-go
$ gotip build ./src/shi
<unknown line number>: internal compiler error: unexpected types2.Invalid

I did a programmatic minimization (after throwing the whole repository into a txtar file via the very handy rogpeppe/go-internal/cmd/txtar-c), which yielded these 7 types. This pared-down version can be run on the playground (where it reproduces the unexpected types2.Invalid error):

package s

type e interface{ l(Values) }
type E func(*Values)
type Stack[T any] struct{}
type T interface{ p(M) }
type V struct{ T }
type Values = Stack[V]
type M struct{ Stack[E] }

Note there is a type alias. It also seems sensitive to textual order.

A higher-level minimization is at this playground link, which includes identifier names that still match the original.

If I do gotip build -gcflags=-h with the minimized version, I get this stack trace, which seems at least superficially similar to the one from @findleyr in https://github.com/golang/go/issues/72887#issuecomment-2729948538.

goroutine 1 [running]:
cmd/compile/internal/gc.handlePanic()
        ../../../../sdk/gotip/src/cmd/compile/internal/gc/main.go:52 +0xa5
panic({0xe390c0?, 0x1084870?})
        ../../../../sdk/gotip/src/runtime/panic.go:783 +0x132
cmd/compile/internal/base.hcrash()
        ../../../../sdk/gotip/src/cmd/compile/internal/base/print.go:267 +0x45
cmd/compile/internal/base.FatalfAt({0x166ea0?, 0xc0?}, {0xf1513b, 0x19}, {0x0, 0x0, 0x0})
        ../../../../sdk/gotip/src/cmd/compile/internal/base/print.go:235 +0x22a
cmd/compile/internal/base.Fatalf(...)
        ../../../../sdk/gotip/src/cmd/compile/internal/base/print.go:195
cmd/compile/internal/noder.(*pkgWriter).typIdx(0xc000166ea0, {0x108b4f0, 0x166b120}, 0xc000478c80)
        ../../../../sdk/gotip/src/cmd/compile/internal/noder/writer.go:533 +0x2e5
cmd/compile/internal/noder.(*writer).typ(0xc00049b1f0, {0x108b4f0?, 0x166b120?})
        ../../../../sdk/gotip/src/cmd/compile/internal/noder/writer.go:481 +0x2c
cmd/compile/internal/noder.(*writer).param(0xc00049b1f0, 0xc00012f5e0)
        ../../../../sdk/gotip/src/cmd/compile/internal/noder/writer.go:719 +0x69
cmd/compile/internal/noder.(*writer).params(...)
        ../../../../sdk/gotip/src/cmd/compile/internal/noder/writer.go:711
cmd/compile/internal/noder.(*writer).signature(0xc00049b1f0, 0xc00048e480)
        ../../../../sdk/gotip/src/cmd/compile/internal/noder/writer.go:702 +0x98
cmd/compile/internal/noder.(*pkgWriter).typIdx(0xc000166ea0, {0x108b400, 0xc00048e480}, 0xc000478c80)
        ../../../../sdk/gotip/src/cmd/compile/internal/noder/writer.go:583 +0x777
cmd/compile/internal/noder.(*writer).typ(0xc0004a00f0, {0x108b400?, 0xc00048e480?})
        ../../../../sdk/gotip/src/cmd/compile/internal/noder/writer.go:481 +0x2c
cmd/compile/internal/noder.(*writer).doObj(0xc0004a00f0, 0xc0004a0140, {0x1094300, 0xc000492480})
        ../../../../sdk/gotip/src/cmd/compile/internal/noder/writer.go:883 +0x378
cmd/compile/internal/noder.(*pkgWriter).objIdx(...)
        ../../../../sdk/gotip/src/cmd/compile/internal/noder/writer.go:815
cmd/compile/internal/noder.(*pkgWriter).objInstIdx(0xc000166ea0, {0x1094300, 0xc000492480}, 0x0, 0x0)
        ../../../../sdk/gotip/src/cmd/compile/internal/noder/writer.go:756 +0x679
cmd/compile/internal/noder.(*writer).obj(0xc0004a0000, {0x1094300?, 0xc000492480?}, 0xc00012c4d0?)
        ../../../../sdk/gotip/src/cmd/compile/internal/noder/writer.go:730 +0x2c
cmd/compile/internal/noder.writePkgStub({0x0?, {0x0?, 0x0?}}, {0xc00012c4d0, 0x1, 0x1})
        ../../../../sdk/gotip/src/cmd/compile/internal/noder/unified.go:343 +0x58d
cmd/compile/internal/noder.unified({0x0?, {0x0?, 0x0?}}, {0xc00012c4d0?, 0xe232a0?, 0xc000111928?})
        ../../../../sdk/gotip/src/cmd/compile/internal/noder/unified.go:195 +0xb6
cmd/compile/internal/noder.LoadPackage({0xc000020270, 0x1, 0x1})
        ../../../../sdk/gotip/src/cmd/compile/internal/noder/noder.go:77 +0x450
cmd/compile/internal/gc.Main(0xf3e390)
        ../../../../sdk/gotip/src/cmd/compile/internal/gc/main.go:208 +0xcc5
main.main()
        ../../../../sdk/gotip/src/cmd/compile/main.go:57 +0xf9

I didn't look closely. Sorry if this is a different underlying issue. (I can open a new issue if that is the case).

Comment From: griesemer

This pared-down code ~may or may not be~ looks related. The type-checker trace shows the same problem:

testdata/manual.go:10:6  :  -- checking type e (white, objPath = )
testdata/manual.go:10:8  :  .  -- type interface{l(Values)}
testdata/manual.go:10:21 :  .  .  -- type Values
testdata/manual.go:15:6  :  .  .  .  -- checking type Values (white, objPath = e)
testdata/manual.go:15:20 :  .  .  .  .  -- type Stack[V]
testdata/manual.go:15:15 :  .  .  .  .  .  -- instantiating type Stack with [V]
testdata/manual.go:15:15 :  .  .  .  .  .  .  -- type Stack
testdata/manual.go:12:6  :  .  .  .  .  .  .  .  -- checking type Stack (white, objPath = e->Values)
testdata/manual.go:12:14 :  .  .  .  .  .  .  .  .  -- type any
testdata/manual.go:12:14 :  .  .  .  .  .  .  .  .  => any (under = any) // *Alias
testdata/manual.go:12:19 :  .  .  .  .  .  .  .  .  -- type struct{}
testdata/manual.go:12:19 :  .  .  .  .  .  .  .  .  => struct{} // *Struct
testdata/manual.go:12:6  :  .  .  .  .  .  .  .  => type Stack[T any] struct{} (black)
testdata/manual.go:15:15 :  .  .  .  .  .  .  => Stack[T₁ any] (under = struct{}) // *Named
testdata/manual.go:15:21 :  .  .  .  .  .  .  -- type V
testdata/manual.go:14:6  :  .  .  .  .  .  .  .  -- checking type V (white, objPath = e->Values)
testdata/manual.go:14:8  :  .  .  .  .  .  .  .  .  -- type struct{T}
testdata/manual.go:14:16 :  .  .  .  .  .  .  .  .  .  -- type T
testdata/manual.go:13:6  :  .  .  .  .  .  .  .  .  .  .  -- checking type T (white, objPath = e->Values->V)
testdata/manual.go:13:8  :  .  .  .  .  .  .  .  .  .  .  .  -- type interface{p(M)}
testdata/manual.go:13:21 :  .  .  .  .  .  .  .  .  .  .  .  .  -- type M
testdata/manual.go:16:6  :  .  .  .  .  .  .  .  .  .  .  .  .  .  -- checking type M (white, objPath = e->Values->V->T)
testdata/manual.go:16:8  :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  -- type struct{Stack[E]}
testdata/manual.go:16:21 :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  -- type Stack[E]
testdata/manual.go:16:16 :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  -- instantiating type Stack with [E]
testdata/manual.go:16:16 :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  -- type Stack
testdata/manual.go:16:16 :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  => Stack[T₁ any] (under = struct{}) // *Named
testdata/manual.go:16:22 :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  -- type E
testdata/manual.go:11:6  :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  -- checking type E (white, objPath = e->Values->V->T->M)
testdata/manual.go:11:12 :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  -- type func(*Values)
testdata/manual.go:11:13 :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  -- type *Values
testdata/manual.go:11:14 :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  -- type Values
testdata/manual.go:15:6  :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  ## cycle detected: objPath = Values->V->T->M->E->Values (len = 5)
testdata/manual.go:15:6  :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  ## cycle contains: 0 values, 4 type definitions
testdata/manual.go:15:6  :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  => cycle is valid
testdata/manual.go:11:14 :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  => Values (under = invalid type) // *Alias
testdata/manual.go:11:13 :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  => invalid type // *Basic
testdata/manual.go:11:12 :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  => func(invalid type) // *Signature
testdata/manual.go:11:6  :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  => type E func(invalid type) (black)
testdata/manual.go:16:22 :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  => E (under = func(invalid type)) // *Named
testdata/manual.go:16:16 :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  => Stack[E]
testdata/manual.go:16:21 :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  => Stack[E] (under = <nil>) // *Named
testdata/manual.go:16:8  :  .  .  .  .  .  .  .  .  .  .  .  .  .  .  => struct{Stack[E]} // *Struct
testdata/manual.go:16:6  :  .  .  .  .  .  .  .  .  .  .  .  .  .  => type M struct{Stack[E]} (black)
testdata/manual.go:13:21 :  .  .  .  .  .  .  .  .  .  .  .  .  => M (under = struct{Stack[E]}) // *Named
testdata/manual.go:13:8  :  .  .  .  .  .  .  .  .  .  .  .  => interface{p(M)} // *Interface
testdata/manual.go:13:6  :  .  .  .  .  .  .  .  .  .  .  => type T interface{p(M)} (black)
testdata/manual.go:14:16 :  .  .  .  .  .  .  .  .  .  => T (under = interface{p(M)}) // *Named
testdata/manual.go:14:8  :  .  .  .  .  .  .  .  .  => struct{T} // *Struct
testdata/manual.go:14:6  :  .  .  .  .  .  .  .  => type V struct{T} (black)
testdata/manual.go:15:21 :  .  .  .  .  .  .  => V (under = struct{T}) // *Named
testdata/manual.go:15:15 :  .  .  .  .  .  => Stack[V]
testdata/manual.go:15:20 :  .  .  .  .  => Stack[V] (under = <nil>) // *Named
testdata/manual.go:15:6  :  .  .  .  => type Values = Stack[V] (black)
testdata/manual.go:10:21 :  .  .  => Values (under = <nil>) // *Alias
testdata/manual.go:10:8  :  .  => interface{l(Values)} // *Interface
testdata/manual.go:10:6  :  => type e interface{l(Values)} (black)

A cycle is detected, considered "valid", but an invalid type is recorded.

The code causing a compiler panic is:

package p

type e interface{ l(Values) }
type E func(*Values)
type Stack[T any] struct{}
type T interface{ p(M) }
type V struct{ T }
type Values = Stack[V]
type M struct{ Stack[E] }

If the type definition of e (first type definition in this code) is moved to the end, the issue disappears. As mentioned earlier, this is a problem with cycle detection.

Comment From: findleyr

Possibly related to https://go.dev/issue/51244?

Comment From: mrkfrmn

Taking another look at this — I was able to distill this down to here:

type a *b
type b = t[*c]
type c t[b]
type t[_ any] struct{}

And thinking on this further, it sounded a bit similar to #74181 (ordering with a pointer to a generic alias). Interestingly, the error message shifts when I checkout my local changes:

<unknown line number>: internal compiler error: panic: nil

Which I suspect is from CL 683796. Seems we could be passing an alias as a type argument while it's still being set up and that is tripping up the instantiation logic.

Comment From: mrkfrmn

I think my assessment from yesterday was correct. Looking at the stack traces, we were seeing the instance call panic here:

targs := check.typeList(xlist)
if targs == nil {
    return Typ[Invalid]
}

ityp := check.instance(x.Pos(), gtyp, targs, nil, check.context())

Looking inside typeList, it does check if the incoming type argument is a valid type:

func (check *Checker) typeList(list []syntax.Expr) []Type {
    res := make([]Type, len(list))
    for i, x := range list {
        t := check.varType(x)
        if !isValid(t) {
            res = nil
        }
        if res != nil {
            res[i] = t
        }
    }
    return res
}

If we were using Invalid to represent a stubbed Alias, then this should return nil. We can replicate that with something simple:

        ...
        if !isValid(t) || Unalias(t) == nil {
            res = nil
        }
        ...

But there's probably a more structured way of approaching this. It's worth noting that with the above change (and those from #74181), I no longer see a compiler panic, at least when running the manual test suite.

Perhaps we implement the above as a short term solution, but we do need to rethink cycle detection.

Comment From: mrkfrmn

Just taking a look in the background again this week. On a second read of my above comments, it's worth pointing out those only get us back to the types2.Invalid situation — it only handles drift from my other changes.

The main issue seems to be that instantiation does not account for partially set up alias types. In the below example (~same as prior):

type a b
type b = t[*c]
type c t[b]
type t[s any] struct{
  f s
}

We fail on the t[b] part because b has a nil (or Invalid) type identity at that point (used for instance hashing). I think we need b to at least have type identity before we can do t[b] — but that's not how our current DFS type checking works.

That's also why removing the type a b causes no error — as a policy, we try to type check aliases last (for such issues), but that line basically forces us to do it first.

I'm also not convinced that the issue here is entirely cycle detection. It seems possible (to me at least) to lay out the types, so I don't see why this would be an invalid cycle.

This sounds more like an issue of lifecycle — it seems to be an error to instantiate using a type argument that has no identity. But I am admittedly not too versed on how type hashing works.