From fc2e860eda39f6bcbe1fecac7b92a50598d1fab9 Mon Sep 17 00:00:00 2001 From: dajiaohuang Date: Thu, 1 Oct 2026 22:18:19 +0800 Subject: [PATCH 1/5] go/ir: avoid canonicalizing function signatures --- go/ir/instantiate.go | 5 ++++- internal/passes/buildir/buildir_test.go | 6 ++++++ .../testdata/src/issue1749/issue1749.go | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 internal/passes/buildir/testdata/src/issue1749/issue1749.go diff --git a/go/ir/instantiate.go b/go/ir/instantiate.go index bcb6d9a89..cfdf16e01 100644 --- a/go/ir/instantiate.go +++ b/go/ir/instantiate.go @@ -97,7 +97,10 @@ func createInstance(fn *Function, rtargs, targs []types.Type) *Function { panic("Instantiate of a Signature returned a non-signature") } obj = fn.object // instantiation does not exist yet - sig = prog.canon.Type(instance).(*types.Signature) + // Do not canonicalize signatures here. Receiver types are not part of + // signature identity, so a function signature may otherwise reuse a + // previously canonicalized method signature with a receiver. + sig = instance } // Choose strategy (instance or wrapper). diff --git a/internal/passes/buildir/buildir_test.go b/internal/passes/buildir/buildir_test.go index fe78b3af3..358104ff7 100644 --- a/internal/passes/buildir/buildir_test.go +++ b/internal/passes/buildir/buildir_test.go @@ -11,6 +11,7 @@ import ( "golang.org/x/tools/go/analysis/analysistest" "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/internal/xtools-internal/testenv" ) func Test(t *testing.T) { @@ -26,3 +27,8 @@ func Test(t *testing.T) { } } } + +func TestGenericMethodValueWithGenericFunction(t *testing.T) { + testenv.NeedsGoCommand1Point(t, 27) + analysistest.Run(t, analysistest.TestData(), buildir.Analyzer, "issue1749") +} diff --git a/internal/passes/buildir/testdata/src/issue1749/issue1749.go b/internal/passes/buildir/testdata/src/issue1749/issue1749.go new file mode 100644 index 000000000..03f424749 --- /dev/null +++ b/internal/passes/buildir/testdata/src/issue1749/issue1749.go @@ -0,0 +1,18 @@ +package issue1749 + +type T struct{} + +func (T) M[P any]() P { + var zero P + return zero +} + +func G[P any]() P { + var zero P + return zero +} + +func F(t T) string { + f := t.M[string] + return f() + G[string]() +} From 03777b2cbb7b81220587be3e807b004f076427d1 Mon Sep 17 00:00:00 2001 From: dajiaohuang Date: Fri, 2 Oct 2026 00:11:50 +0800 Subject: [PATCH 2/5] Preserve nil values in generic type map --- go/types/typeutil/map_test.go | 27 +++++++++++++++++++++++++++ go/types/typeutil/upstream.go | 9 +++++---- 2 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 go/types/typeutil/map_test.go diff --git a/go/types/typeutil/map_test.go b/go/types/typeutil/map_test.go new file mode 100644 index 000000000..2fce0ba0b --- /dev/null +++ b/go/types/typeutil/map_test.go @@ -0,0 +1,27 @@ +package typeutil + +import ( + "go/types" + "testing" +) + +func TestMapNilValue(t *testing.T) { + key := types.Typ[types.Int] + m := new(Map[*int]) + + m.Set(key, nil) + if got, ok := m.At(key); !ok || got != nil { + t.Fatalf("At(%v) = (%v, %t), want (nil, true)", key, got, ok) + } + + called := false + m.Iterate(func(gotKey types.Type, gotValue *int) { + called = true + if gotKey != key || gotValue != nil { + t.Errorf("Iterate got (%v, %v), want (%v, nil)", gotKey, gotValue, key) + } + }) + if !called { + t.Fatal("Iterate did not visit the nil-valued entry") + } +} diff --git a/go/types/typeutil/upstream.go b/go/types/typeutil/upstream.go index 064964e98..5f55c7f38 100644 --- a/go/types/typeutil/upstream.go +++ b/go/types/typeutil/upstream.go @@ -27,21 +27,22 @@ type Map[V any] struct { m typeutil.Map } +type mapValue[V any] struct{ value V } + func (m *Map[V]) Delete(key types.Type) bool { return m.m.Delete(key) } func (m *Map[V]) At(key types.Type) (V, bool) { v := m.m.At(key) if v == nil { var zero V return zero, false - } else { - return v.(V), true } + return v.(mapValue[V]).value, true } -func (m *Map[V]) Set(key types.Type, value V) { m.m.Set(key, value) } +func (m *Map[V]) Set(key types.Type, value V) { m.m.Set(key, mapValue[V]{value}) } func (m *Map[V]) Len() int { return m.m.Len() } func (m *Map[V]) Iterate(f func(key types.Type, value V)) { ff := func(key types.Type, value any) { - f(key, value.(V)) + f(key, value.(mapValue[V]).value) } m.m.Iterate(ff) From 5ebfb6c55261c1dcd160601fac6a86a42453925b Mon Sep 17 00:00:00 2001 From: dajiaohuang Date: Fri, 2 Oct 2026 00:25:52 +0800 Subject: [PATCH 3/5] Keep generic map string output unchanged --- go/types/typeutil/map_test.go | 10 ++++++++++ go/types/typeutil/upstream.go | 14 +++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/go/types/typeutil/map_test.go b/go/types/typeutil/map_test.go index 2fce0ba0b..8573e5de6 100644 --- a/go/types/typeutil/map_test.go +++ b/go/types/typeutil/map_test.go @@ -25,3 +25,13 @@ func TestMapNilValue(t *testing.T) { t.Fatal("Iterate did not visit the nil-valued entry") } } + +func TestMapStringUnwrapsValues(t *testing.T) { + key := types.Typ[types.Int] + m := new(Map[string]) + m.Set(key, "value") + + if got, want := m.String(), `{int: "value"}`; got != want { + t.Fatalf("String() = %q, want %q", got, want) + } +} diff --git a/go/types/typeutil/upstream.go b/go/types/typeutil/upstream.go index 5f55c7f38..a00e4f512 100644 --- a/go/types/typeutil/upstream.go +++ b/go/types/typeutil/upstream.go @@ -1,8 +1,10 @@ package typeutil import ( + "fmt" "go/ast" "go/types" + "strings" _ "unsafe" "golang.org/x/tools/go/types/typeutil" @@ -48,6 +50,16 @@ func (m *Map[V]) Iterate(f func(key types.Type, value V)) { } func (m *Map[V]) Keys() []types.Type { return m.m.Keys() } -func (m *Map[V]) String() string { return m.m.String() } +func (m *Map[V]) String() string { + var b strings.Builder + b.WriteByte('{') + sep := "" + m.Iterate(func(key types.Type, value V) { + fmt.Fprintf(&b, "%s%v: %q", sep, key, any(value)) + sep = ", " + }) + b.WriteByte('}') + return b.String() +} func (m *Map[V]) KeysString() string { return m.m.KeysString() } func (m *Map[V]) SetHasher(h typeutil.Hasher) { m.m.SetHasher(h) } From 378d45b5cae44846c62869a08e41a4eec7ee5ca5 Mon Sep 17 00:00:00 2001 From: dajiaohuang Date: Fri, 2 Oct 2026 00:39:43 +0800 Subject: [PATCH 4/5] Support nil generic type map receivers --- go/types/typeutil/map_test.go | 30 +++++++++++++++++++++++++ go/types/typeutil/upstream.go | 41 ++++++++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/go/types/typeutil/map_test.go b/go/types/typeutil/map_test.go index 8573e5de6..4ace9fb76 100644 --- a/go/types/typeutil/map_test.go +++ b/go/types/typeutil/map_test.go @@ -35,3 +35,33 @@ func TestMapStringUnwrapsValues(t *testing.T) { t.Fatalf("String() = %q, want %q", got, want) } } + +func TestNilMapReadOperations(t *testing.T) { + var m *Map[int] + key := types.Typ[types.Int] + + if got, ok := m.At(key); ok || got != 0 { + t.Fatalf("At(%v) = (%d, %t), want (0, false)", key, got, ok) + } + if m.Delete(key) { + t.Fatal("Delete on nil map returned true") + } + if m.Len() != 0 { + t.Fatalf("Len() = %d, want 0", m.Len()) + } + called := false + m.Iterate(func(types.Type, int) { called = true }) + if called { + t.Fatal("Iterate called its callback on a nil map") + } + if keys := m.Keys(); keys == nil || len(keys) != 0 { + t.Fatalf("Keys() = %v, want a non-nil empty slice", keys) + } + if got := m.String(); got != "{}" { + t.Fatalf("String() = %q, want {}", got) + } + if got := m.KeysString(); got != "{}" { + t.Fatalf("KeysString() = %q, want {}", got) + } + m.SetHasher(MakeHasher()) +} diff --git a/go/types/typeutil/upstream.go b/go/types/typeutil/upstream.go index a00e4f512..bb898d2ff 100644 --- a/go/types/typeutil/upstream.go +++ b/go/types/typeutil/upstream.go @@ -31,8 +31,14 @@ type Map[V any] struct { type mapValue[V any] struct{ value V } -func (m *Map[V]) Delete(key types.Type) bool { return m.m.Delete(key) } +func (m *Map[V]) Delete(key types.Type) bool { + return m != nil && m.m.Delete(key) +} func (m *Map[V]) At(key types.Type) (V, bool) { + if m == nil { + var zero V + return zero, false + } v := m.m.At(key) if v == nil { var zero V @@ -41,16 +47,32 @@ func (m *Map[V]) At(key types.Type) (V, bool) { return v.(mapValue[V]).value, true } func (m *Map[V]) Set(key types.Type, value V) { m.m.Set(key, mapValue[V]{value}) } -func (m *Map[V]) Len() int { return m.m.Len() } +func (m *Map[V]) Len() int { + if m == nil { + return 0 + } + return m.m.Len() +} func (m *Map[V]) Iterate(f func(key types.Type, value V)) { + if m == nil { + return + } ff := func(key types.Type, value any) { f(key, value.(mapValue[V]).value) } m.m.Iterate(ff) } -func (m *Map[V]) Keys() []types.Type { return m.m.Keys() } +func (m *Map[V]) Keys() []types.Type { + if m == nil { + return []types.Type{} + } + return m.m.Keys() +} func (m *Map[V]) String() string { + if m == nil { + return "{}" + } var b strings.Builder b.WriteByte('{') sep := "" @@ -61,5 +83,14 @@ func (m *Map[V]) String() string { b.WriteByte('}') return b.String() } -func (m *Map[V]) KeysString() string { return m.m.KeysString() } -func (m *Map[V]) SetHasher(h typeutil.Hasher) { m.m.SetHasher(h) } +func (m *Map[V]) KeysString() string { + if m == nil { + return "{}" + } + return m.m.KeysString() +} +func (m *Map[V]) SetHasher(h typeutil.Hasher) { + if m != nil { + m.m.SetHasher(h) + } +} From c82687df2216f4f4f63f6889935f88a864388bdf Mon Sep 17 00:00:00 2001 From: dajiaohuang Date: Fri, 2 Oct 2026 14:14:32 +0800 Subject: [PATCH 5/5] Keep fix independent of generic IR change --- go/ir/instantiate.go | 5 +---- internal/passes/buildir/buildir_test.go | 6 ------ .../testdata/src/issue1749/issue1749.go | 18 ------------------ 3 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 internal/passes/buildir/testdata/src/issue1749/issue1749.go diff --git a/go/ir/instantiate.go b/go/ir/instantiate.go index cfdf16e01..bcb6d9a89 100644 --- a/go/ir/instantiate.go +++ b/go/ir/instantiate.go @@ -97,10 +97,7 @@ func createInstance(fn *Function, rtargs, targs []types.Type) *Function { panic("Instantiate of a Signature returned a non-signature") } obj = fn.object // instantiation does not exist yet - // Do not canonicalize signatures here. Receiver types are not part of - // signature identity, so a function signature may otherwise reuse a - // previously canonicalized method signature with a receiver. - sig = instance + sig = prog.canon.Type(instance).(*types.Signature) } // Choose strategy (instance or wrapper). diff --git a/internal/passes/buildir/buildir_test.go b/internal/passes/buildir/buildir_test.go index 358104ff7..fe78b3af3 100644 --- a/internal/passes/buildir/buildir_test.go +++ b/internal/passes/buildir/buildir_test.go @@ -11,7 +11,6 @@ import ( "golang.org/x/tools/go/analysis/analysistest" "honnef.co/go/tools/internal/passes/buildir" - "honnef.co/go/tools/internal/xtools-internal/testenv" ) func Test(t *testing.T) { @@ -27,8 +26,3 @@ func Test(t *testing.T) { } } } - -func TestGenericMethodValueWithGenericFunction(t *testing.T) { - testenv.NeedsGoCommand1Point(t, 27) - analysistest.Run(t, analysistest.TestData(), buildir.Analyzer, "issue1749") -} diff --git a/internal/passes/buildir/testdata/src/issue1749/issue1749.go b/internal/passes/buildir/testdata/src/issue1749/issue1749.go deleted file mode 100644 index 03f424749..000000000 --- a/internal/passes/buildir/testdata/src/issue1749/issue1749.go +++ /dev/null @@ -1,18 +0,0 @@ -package issue1749 - -type T struct{} - -func (T) M[P any]() P { - var zero P - return zero -} - -func G[P any]() P { - var zero P - return zero -} - -func F(t T) string { - f := t.M[string] - return f() + G[string]() -}