Go version
go version go1.24.4 darwin/arm64
Output of go env
in your module/workspace:
AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN=''
GOCACHE='*************************'
GOCACHEPROG=''
GODEBUG=''
GOENV='*************************'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/34/__4d1gf55nx2m34qjyjc3f1m0000gn/T/go-build4095632605=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/dev/null'
GOMODCACHE='*************************'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='*************************'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='*************************'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.24.4'
GOWORK=''
PKG_CONFIG='pkg-config'
What did you do?
Link: https://go.dev/play/p/AzuM0eY_vGH
An internal compiler error is emitted when using a pointer to a recursive type alias.
Note: the non-pointer version compiles successfully. Likewise, the pointer version works if moved into the alias definition (type AB = *A[B]
).
To reproduce:
go run main.go
package main
type _ interface {
Value() AB
}
type A[T any] struct{}
type B struct {
Value *AB
}
type AB = A[B]
func main() {
}
What did you see happen?
% go run main.go
# command-line-arguments
<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
%
What did you expect to see?
% go run main.go
%
Sorry if this is a duplicate issue! There were some related entires, though I get this on the latest version, 1.24.4
.
- https://github.com/golang/go/issues/50729
- https://github.com/golang/go/issues/70230
Comment From: gabyhelp
Related Issues
- cmd/compile: \<autogenerated>:1: internal compiler error: panic: runtime error: invalid memory address or nil pointer dereference #54671 (closed)
- cmd/compile/internal/types2: invalid recursive type not detected and panics #48096 (closed)
- recursive type on pointer just hangs the compiler #48930 (closed)
- cmd/compile: invalid typeparameters code can be compiled #49059 (closed)
- cmd/compile: crash instantiating with recursive type #50259 (closed)
- cmd/compile: crash while exporting aliases (with GODEBUG=gotypesalias=1) #66550 (closed)
- cmd/compile: internal compiler error: want STRUCT, but have PTR #48056 (closed)
- cmd/compile: cannot compile with alias type used in multiple files #50925 (closed)
- cmd/compile: internal compiler error when processing invalid recursive type #49276 (closed)
- cmd/compile: type inference fails for unused type in generic type alias #70948 (closed)
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
Comment From: thepudds
Possibly related to #72887 (or maybe not).
It seems to reproduce with tip on the playground: https://go.dev/play/p/AzuM0eY_vGH?v=gotip
@adamlouis, what do you get if you add -gcflags=-h
flag to go run
or go build
, and does it seem at least superficially similar to the call stacks in #72887? (The -h
causes the compiler to panic on the first compile error encountered, which lets you see the call stack).
CC @griesemer, @findleyr
Comment From: thepudds
@adamlouis, also, I should add -- thanks for filing this, and especially thanks for including a nice minimal reproducer, which should be enough for others to investigate further (and no worries of course if you don't have time to try -gcflags=-h
or look at this more yourself).
Comment From: JunyangShao
@golang/compiler @mrkfrmn
Comment From: mrkfrmn
I've done a bit of poking — somewhat interesting to me is that the below panics:
type AB = A[B]
type _ struct {
_ AB
}
type B struct {
f *AB
}
type A[T any] struct{}
While the below does not:
type AB = A[B]
type B struct {
f *AB
}
type _ struct {
_ AB
}
type A[T any] struct{}
I added in some one-off logging to get more detail on where the panic is coming from and it looks like the issue is with B
— specifically its field f
becomes invalid:
type cmd/compile/internal/types2/testdata.A[T any] struct{}
type any = interface{}
type cmd/compile/internal/types2/testdata.AB = cmd/compile/internal/types2/testdata.A[cmd/compile/internal/types2/testdata.B]
type cmd/compile/internal/types2/testdata.B struct{f invalid type}
And a brief cycle trace:
./manual.go:20:6 : -- checking type _ (white, objPath = )
./manual.go:18:6 : . -- checking type AB (white, objPath = _)
./manual.go:28:6 : . . -- checking type A (white, objPath = _->AB)
./manual.go:28:6 : . . => type A[T any] struct{} (black)
./manual.go:24:6 : . . -- checking type B (white, objPath = _->AB)
./manual.go:18:6 : . . . ## cycle detected: objPath = AB->B->AB (len = 2)
./manual.go:18:6 : . . . ## cycle contains: 0 values, 1 type definitions
./manual.go:18:6 : . . . => cycle is valid
./manual.go:24:6 : . . => type B struct{f invalid type} (black)
./manual.go:18:6 : . => type AB = A[B] (black)
./manual.go:20:6 : => type _ struct{_ AB} (black)
While in the second example it resolves fine:
type cmd/compile/internal/types2/testdata.A[T any] struct{}
type any = interface{}
type cmd/compile/internal/types2/testdata.AB =
cmd/compile/internal/types2/testdata.A[cmd/compile/internal/types2/testdata.B]
type cmd/compile/internal/types2/testdata.B struct{f *cmd/compile/internal/types2/testdata.AB}
And a cycle trace:
./manual.go:20:6 : -- checking type B (white, objPath = )
./manual.go:18:6 : . -- checking type AB (white, objPath = B)
./manual.go:28:6 : . . -- checking type A (white, objPath = B->AB)
./manual.go:28:6 : . . => type A[T any] struct{} (black)
./manual.go:20:6 : . . ## cycle detected: objPath = B->AB->B (len = 2)
./manual.go:20:6 : . . ## cycle contains: 0 values, 1 type definitions
./manual.go:20:6 : . . => cycle is valid
./manual.go:18:6 : . => type AB = A[B] (black)
./manual.go:20:6 : => type B struct{f *AB} (black)
./manual.go:24:6 : -- checking type _ (white, objPath = )
./manual.go:24:6 : => type _ struct{_ AB} (black)
I took a deeper trace of the first case and got:
./manual.go:20:6 : -- checking type _ (white, objPath = )
./manual.go:20:8 : . -- type struct{_ AB}
./manual.go:21:4 : . . -- type AB
./manual.go:18:6 : . . . -- checking type AB (white, objPath = _)
./manual.go:18:12 : . . . . -- type A[B]
./manual.go:18:11 : . . . . . -- type A
./manual.go:28:6 : . . . . . . -- checking type A (white, objPath = _->AB)
./manual.go:28:10 : . . . . . . . -- type any
./manual.go:28:10 : . . . . . . . => any (under = any) // *Alias
./manual.go:28:15 : . . . . . . . -- type struct{}
./manual.go:28:15 : . . . . . . . => struct{} // *Struct
./manual.go:28:6 : . . . . . . => type A[T any] struct{} (black)
./manual.go:18:11 : . . . . . => A[T₁ any] (under = struct{}) // *Named
./manual.go:18:13 : . . . . . -- type B
./manual.go:24:6 : . . . . . . -- checking type B (white, objPath = _->AB)
./manual.go:24:8 : . . . . . . . -- type struct{f *AB}
./manual.go:25:4 : . . . . . . . . -- type *AB
./manual.go:25:5 : . . . . . . . . . -- type AB
./manual.go:18:6 : . . . . . . . . . . ## cycle detected: objPath = AB->B->AB (len = 2)
./manual.go:18:6 : . . . . . . . . . . ## cycle contains: 0 values, 1 type definitions
./manual.go:18:6 : . . . . . . . . . . => cycle is valid
./manual.go:25:5 : . . . . . . . . . => AB (under = invalid type) // *Alias
./manual.go:25:4 : . . . . . . . . => invalid type // *Basic
./manual.go:24:8 : . . . . . . . => struct{f invalid type} // *Struct
./manual.go:24:6 : . . . . . . => type B struct{f invalid type} (black)
./manual.go:18:13 : . . . . . => B (under = struct{f invalid type}) // *Named
./manual.go:18:12 : . . . . => A[B] (under = <nil>) // *Named
./manual.go:18:6 : . . . => type AB = A[B] (black)
./manual.go:21:4 : . . => AB (under = <nil>) // *Alias
./manual.go:20:8 : . => struct{_ AB} // *Struct
./manual.go:20:6 : => type _ struct{_ AB} (black)
The interesting part to me is that *AB
resolves to Basic
(due to the invalid type) rather than Pointer
. Seems a bit odd.
Comment From: mrkfrmn
I suspect the below (from typInternal
) is related:
case *syntax.Operation:
if e.Op == syntax.Mul && e.Y == nil {
typ := new(Pointer)
typ.base = Typ[Invalid] // avoid nil base in invalid recursive type declaration
setDefType(def, typ)
typ.base = check.varType(e.X)
// If typ.base is invalid, it's unlikely that *base is particularly
// useful - even a valid dereferenciation will lead to an invalid
// type again, and in some cases we get unexpected follow-on errors
// (e.g., go.dev/issue/49005). Return an invalid type instead.
if !isValid(typ.base) {
return Typ[Invalid]
}
return typ
}
Specifically the !isValid(typ.base) { ... }
introduced by CL 356449. FWIW, reverting that CL locally does seem to resolve this issue (I have not ran expansive tests). Though, I imagine we would be reintroducing #49005 — will think about how we can avoid opening that again.
Also cc @griesemer who worked on the related issue.
Comment From: findleyr
@mrkfrmn I suppose the question then becomes: if !isValid(typ.base)
, why were there no type checking errors?
Comment From: mrkfrmn
@findleyr Oftentimes, when we assign a type to Invalid
, we emit an error then and there and continue type checking. However, that does not always happen. In particular, aliases use Invalid
to mean that the right hand side of the alias is not yet fully set up — it's a sort of stub.
I think that !isValid(typ.base)
is assuming an error reported elsewhere and tries to just eliminate a follow-along error. The alias use case violates this assumption and results in no error at all while leaving behind an Invalid
struct field (that will never be set).
I think this is also why altering declaration order "fixes" the above — it's a coincidence that the alias is fully set up when we type the struct field.
2 considerations come to mind:
- We could consider if Invalid
is the optimal way to mark an unfinished alias. I think @adonovan may have thoughts.
- I assume that at the end of type checking, there is a bug if there exist no errors, yet types are still assigned to Invalid
. It's unfortunate that this gets down to noding before panicking.
Comment From: griesemer
I agree that using Invalid
to mark a type as incomplete is problematic because it implies that an error was reported somewhere when it wasn't. Now that we have an actual Alias
type we can probably use a flag. I actually have a TODO (in my personal log) about this: "Use bool to mark Alias as initialized". See also the TODO in Checker.typeDecl
(decl.go).
I also agree that upon successful completion of type checking, there shouldn't be any Invalid
types anywhere. That's a harder invariant to establish.
Comment From: mrkfrmn
It seems that doing this via the Alias
type is appropriate. I checked go/types
and the issue replicates there as expected. Interestingly, with gotypesalias=0
, the check added in CL 379916 is reported:
markfreeman-macbookpro:gotypes markfreeman$ GODEBUG=gotypesalias=0 go run main.go
2025/06/23 14:03:52 error type checking: example.go:10:5: invalid use of type alias AB in recursive type (see go.dev/issue/50729)
exit status 1
It's likely fine to leave the infrastructure without explicit alias types alone. Also considered removing gotypesalias
prior to this work, but we must support that until at least 1.27 (thanks @findleyr for the pointer).
I'll look at handling this via flag where gotypesalias=1
.
Comment From: gopherbot
Change https://go.dev/cl/683796 mentions this issue: go/types, types2: use nil to represent incomplete explicit aliases
Comment From: mrkfrmn
After some slight adjustment to handle the case of partial type checking, things seem to be in order. I'm going to consider this all but submitted.