diff --git a/go/types/typeutil/map_test.go b/go/types/typeutil/map_test.go new file mode 100644 index 00000000..4ace9fb7 --- /dev/null +++ b/go/types/typeutil/map_test.go @@ -0,0 +1,67 @@ +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") + } +} + +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) + } +} + +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 064964e9..bb898d2f 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" @@ -27,26 +29,68 @@ type Map[V any] struct { m typeutil.Map } -func (m *Map[V]) Delete(key types.Type) bool { return m.m.Delete(key) } +type mapValue[V any] struct{ value V } + +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 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, mapValue[V]{value}) } +func (m *Map[V]) Len() int { + if m == nil { + return 0 + } + return m.m.Len() } -func (m *Map[V]) Set(key types.Type, value V) { m.m.Set(key, value) } -func (m *Map[V]) Len() int { 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.(V)) + 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]) String() string { return m.m.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]) 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 := "" + 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 { + if m == nil { + return "{}" + } + return m.m.KeysString() +} +func (m *Map[V]) SetHasher(h typeutil.Hasher) { + if m != nil { + m.m.SetHasher(h) + } +}