go/types, types2: include type arguments in instantiated type cycle errors
When reporting layout cycles involving instantiated generic types, the
error chain omitted type arguments, making the output confusing since
the generic type itself makes no reference to the recursive type.
Fixes #75022
diff --git a/src/cmd/compile/internal/types2/decl.go b/src/cmd/compile/internal/types2/decl.go
index 97d486c..dbea193 100644
--- a/src/cmd/compile/internal/types2/decl.go
+++ b/src/cmd/compile/internal/types2/decl.go
@@ -255,6 +255,9 @@
// may refer to imported types. See go.dev/issue/50788.
// TODO(gri) This functionality is used elsewhere. Factor it out.
name := func(obj Object) string {
+ if named := asNamed(obj.Type()); named != nil && named.inst != nil {
+ return TypeString(named, check.qualifier)
+ }
return packagePrefix(obj.Pkg(), check.qualifier) + obj.Name()
}
diff --git a/src/cmd/compile/internal/types2/issues_test.go b/src/cmd/compile/internal/types2/issues_test.go
index e564fb3..fd3cb04 100644
--- a/src/cmd/compile/internal/types2/issues_test.go
+++ b/src/cmd/compile/internal/types2/issues_test.go
@@ -1187,3 +1187,43 @@
t.Fatalf("unexpected type for {x}: %s", tv.Type)
}
}
+
+func TestIssue75022(t *testing.T) {
+ // Issue 75022: error messages for instantiated layout cycles should name
+ // the instantiation (e.g. "t[a] refers to a", not "t refers to a").
+ tests := []struct {
+ name string
+ src string
+ want []string
+ }{
+ {
+ name: "direct self-instantiation",
+ src: `package p
+type t[p any] struct { f p }
+type a t[a]`,
+ want: []string{"invalid recursive type", "a refers to t[a]", "t[a] refers to a"},
+ },
+ {
+ name: "self-instantiation via alias",
+ src: `package p
+type t[p any] struct { f p }
+type b = a
+type a t[b]`,
+ want: []string{"invalid recursive type", "a refers to t[b]", "t[b] refers to a"},
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ _, err := typecheck(test.src, nil, nil)
+ if err == nil {
+ t.Fatal("expected type-check error, got none")
+ }
+ for _, w := range test.want {
+ if !strings.Contains(err.Error(), w) {
+ t.Errorf("error message missing %q\n\tgot: %s", w, err)
+ }
+ }
+ })
+ }
+}
diff --git a/src/go/types/decl.go b/src/go/types/decl.go
index 486ef6f..a6533e2 100644
--- a/src/go/types/decl.go
+++ b/src/go/types/decl.go
@@ -257,6 +257,9 @@
// may refer to imported types. See go.dev/issue/50788.
// TODO(gri) This functionality is used elsewhere. Factor it out.
name := func(obj Object) string {
+ if named := asNamed(obj.Type()); named != nil && named.inst != nil {
+ return TypeString(named, check.qualifier)
+ }
return packagePrefix(obj.Pkg(), check.qualifier) + obj.Name()
}
diff --git a/src/go/types/issues_test.go b/src/go/types/issues_test.go
index 3649189..f4c80d6 100644
--- a/src/go/types/issues_test.go
+++ b/src/go/types/issues_test.go
@@ -1201,3 +1201,49 @@
t.Fatalf("unexpected type for {x}: %s", tv.Type)
}
}
+
+func TestIssue75022(t *testing.T) {
+ // Issue 75022: error messages for instantiated layout cycles should name
+ // the instantiation (e.g. "t[a] refers to a", not "t refers to a").
+ tests := []struct {
+ name string
+ src string
+ want []string
+ }{
+ {
+ name: "direct self-instantiation",
+ src: `package p
+type t[p any] struct { f p }
+type a t[a]`,
+ want: []string{"invalid recursive type", "a refers to t[a]", "t[a] refers to a"},
+ },
+ {
+ name: "self-instantiation via alias",
+ src: `package p
+type t[p any] struct { f p }
+type b = a
+type a t[b]`,
+ want: []string{"invalid recursive type", "a refers to t[b]", "t[b] refers to a"},
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ var got []string
+ conf := &Config{
+ Error: func(err error) { got = append(got, err.Error()) },
+ Importer: defaultImporter(token.NewFileSet()),
+ }
+ typecheck(test.src, conf, nil)
+ if len(got) == 0 {
+ t.Fatal("expected type-check error, got none")
+ }
+ fullErr := strings.Join(got, "\n")
+ for _, w := range test.want {
+ if !strings.Contains(fullErr, w) {
+ t.Errorf("error message missing %q\n\tgot: %s", w, fullErr)
+ }
+ }
+ })
+ }
+}
diff --git a/test/fixedbugs/issue50788.dir/b.go b/test/fixedbugs/issue50788.dir/b.go
index 97ae208..c41870d 100644
--- a/test/fixedbugs/issue50788.dir/b.go
+++ b/test/fixedbugs/issue50788.dir/b.go
@@ -6,4 +6,4 @@
import "./a"
-type T a.T[T] // ERROR "invalid recursive type T\n.*T refers to a\.T\n.*a\.T refers to T"
+type T a.T[T] // ERROR "invalid recursive type T\n.*T refers to a\.T\[T\]\n.*a\.T\[T\] refers to T"
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Congratulations on opening your first change. Thank you for your contribution!
Next steps:
A maintainer will review your change and provide feedback. See
https://go.dev/doc/contribute#review for more info and tips to get your
patch through code review.
Most changes in the Go project go through a few rounds of revision. This can be
surprising to people new to the project. The careful, iterative review process
is our way of helping mentor contributors and ensuring that their contributions
have a lasting impact.
During May-July and Nov-Jan the Go project is in a code freeze, during which
little code gets reviewed or merged. If a reviewer responds with a comment like
R=go1.11 or adds a tag like "wait-release", it means that this CL will be
reviewed as part of the next development cycle. See https://go.dev/s/release
for more details.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
func TestIssue75022(t *testing.T) {Let's put this test in `src/internal/types/testdata/fixedbugs/issue75022.go` and use the error matching syntax. That might look like:
```
// 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.
package p
type T[P any] struct {
_ P
}type A T[A] // ERROR "A refers to T[A]"
type B = C
type C T[B] // ERROR "C refers to T[B]"
type D = T[D] // ERROR "D refers to itself"
```
That way we can drop the other `issues_test`.
type T a.T[T] // ERROR "invalid recursive type T\n.*T refers to a\.T\[T\]\n.*a\.T\[T\] refers to T"Could we add another case here passing an imported type as an argument?
```
type U a.T[a.T[U]] // ERROR "invalid recursive type U\n.*U refers to a\.T\[a\.T\[U\]\]\n.*a\.T\[a\.T\[U\]\] refers to a\.T\[U\]\n.*a\.T\[U\] refers to U"
```
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
func TestIssue75022(t *testing.T) {Let's put this test in `src/internal/types/testdata/fixedbugs/issue75022.go` and use the error matching syntax. That might look like:
```
// 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.package p
type T[P any] struct {
_ P
}type A T[A] // ERROR "A refers to T[A]"
type B = C
type C T[B] // ERROR "C refers to T[B]"type D = T[D] // ERROR "D refers to itself"
```
That way we can drop the other `issues_test`.
I added some testcases here to the `src/internal/types/testdata/fixedbugs/issue75022.go`, but it seems that the test suite only keeps the primary error for `go/types` see in `testFiles()` [source](https://github.com/golang/go/blob/master/src/go/types/check_test.go#L167-L171) which means I can't test the full "refers to T[A] chain".
From my understanding the `go/test/fixedbugs/issue50788.dir` (which has the full error traces) only tests the `types2` package.
So to keep test coverage, I kept the `TestIssue75022()` in `src/go/types/issues_test.go` for coverage on `go/types`. If there's a better way to keep the coverage let me know. Thanks!
type T a.T[T] // ERROR "invalid recursive type T\n.*T refers to a\.T\[T\]\n.*a\.T\[T\] refers to T"Could we add another case here passing an imported type as an argument?
```
type U a.T[a.T[U]] // ERROR "invalid recursive type U\n.*U refers to a\.T\[a\.T\[U\]\]\n.*a\.T\[a\.T\[U\]\] refers to a\.T\[U\]\n.*a\.T\[U\] refers to U"
```
I have added in this test and a few more that reflect the original unit test `Test75022()` functionality
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |