From 01ab391f82a9aea36b5e52b0035ed6d9c4fc6f6a Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Wed, 25 Mar 2020 21:20:53 -0700 Subject: [PATCH] go/go2go: support instantiating with an instantiated type Add metrics test case to test GO2PATH. Change-Id: I48c8a418d743ccd80cdcf36fc043ca0e2dd932f4 --- .../testdata/go2path/src/metrics/metrics.go2 | 122 ++++++++++++++++++ .../go2path/src/metrics/metrics_test.go2 | 57 ++++++++ src/go/go2go/names.go | 31 ++--- src/go/go2go/rewrite.go | 54 +++++++- src/go/go2go/types.go | 25 ++++ 5 files changed, 273 insertions(+), 16 deletions(-) create mode 100644 src/cmd/go2go/testdata/go2path/src/metrics/metrics.go2 create mode 100644 src/cmd/go2go/testdata/go2path/src/metrics/metrics_test.go2 diff --git a/src/cmd/go2go/testdata/go2path/src/metrics/metrics.go2 b/src/cmd/go2go/testdata/go2path/src/metrics/metrics.go2 new file mode 100644 index 0000000000..1cb4477bf4 --- /dev/null +++ b/src/cmd/go2go/testdata/go2path/src/metrics/metrics.go2 @@ -0,0 +1,122 @@ +// Copyright 2020 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 metrics provides tracking arbitrary metrics composed of +// values of comparable types. +package metrics + +import ( + "sync" + + "maps" +) + +// Metric1 tracks metrics of values of some type. +type Metric1(type T comparable) struct { + mu sync.Mutex + m map[T]int +} + +// Add adds another instance of some value. +func (m *Metric1(T)) Add(v T) { + m.mu.Lock() + defer m.mu.Unlock() + if m.m == nil { + m.m = make(map[T]int) + } + m.m[v]++ +} + +// Count returns the number of instances we've seen of v. +func (m *Metric1(T)) Count(v T) int { + return m.m[v] +} + +// Metrics returns all the values we've seen, in an indeterminate order. +func (m *Metric1(T)) Metrics() []T { + return maps.Keys(m.m) +} + +contract cmp2(T1, T2) { + comparable(T1) + comparable(T2) +} + +type key2(type T1, T2 cmp2) struct { + f1 T1 + f2 T2 +} + +// Metric2 tracks metrics of pairs of values. +type Metric2(type T1, T2 cmp2) struct { + mu sync.Mutex + m map[key2(T1, T2)]int +} + +// Add adds another instance of some pair of values. +func (m *Metric2(T1, T2)) Add(v1 T1, v2 T2) { + m.mu.Lock() + defer m.mu.Unlock() + if m.m == nil { + m.m = make(map[key2(T1, T2)]int) + } + m.m[key2(T1, T2){v1, v2}]++ +} + +// Count returns the number of instances we've seen of v1/v2. +func (m *Metric2(T1, T2)) Count(v1 T1, v2 T2) int { + return m.m[key2(T1, T2){v1, v2}] +} + +// Metrics returns all the values we've seen, in an indeterminate order. +func (m *Metric2(T1, T2)) Metrics() (r1 []T1, r2 []T2) { + for _, k := range maps.Keys(m.m) { + r1 = append(r1, k.f1) + r2 = append(r2, k.f2) + } + return r1, r2 +} + +contract cmp3(T1, T2, T3) { + comparable(T1) + comparable(T2) + comparable(T3) +} + +type key3(type T1, T2, T3 cmp3) struct { + f1 T1 + f2 T2 + f3 T3 +} + +// Metric3 tracks metrics of triplets of values. +type Metric3(type T1, T2, T3 cmp3) struct { + mu sync.Mutex + m map[key3(T1, T2, T3)]int +} + +// Add adds another instance of some triplet of values. +func (m *Metric3(T1, T2, T3)) Add(v1 T1, v2 T2, v3 T3) { + m.mu.Lock() + defer m.mu.Unlock() + if m.m == nil { + m.m = make(map[key3(T1, T2, T3)]int) + } + m.m[key3(T1, T2, T3){v1, v2, v3}]++ +} + +// Count returns the number of instances we've seen of v1/v2/v3. +func (m *Metric3(T1, T2, T3)) Count(v1 T1, v2 T2, v3 T3) int { + return m.m[key3(T1, T2, T3){v1, v2, v3}] +} + +// Metrics returns all the values we've seen, in an indeterminate order. +func (m *Metric3(T1, T2, T3)) Metrics() (r1 []T1, r2 []T2, r3 []T3) { + for k := range m.m { + r1 = append(r1, k.f1) + r2 = append(r2, k.f2) + r3 = append(r3, k.f3) + } + return r1, r2, r3 +} diff --git a/src/cmd/go2go/testdata/go2path/src/metrics/metrics_test.go2 b/src/cmd/go2go/testdata/go2path/src/metrics/metrics_test.go2 new file mode 100644 index 0000000000..e2334ab277 --- /dev/null +++ b/src/cmd/go2go/testdata/go2path/src/metrics/metrics_test.go2 @@ -0,0 +1,57 @@ +// Copyright 2020 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 metrics + +import ( + "sort" + "testing" + + "slices" +) + +type S struct{ a, b, c string } + +func TestMetrics(t *testing.T) { + m1 := Metric1(string){} + if got := m1.Count("a"); got != 0 { + t.Errorf("Count(%q) = %d, want 0", "a", got) + } + m1.Add("a") + m1.Add("a") + if got := m1.Count("a"); got != 2 { + t.Errorf("Count(%q) = %d, want 2", "a", got) + } + if got, want := m1.Metrics(), []string{"a"}; !slices.Equal(got, want) { + t.Errorf("Metrics = %v, want %v", got, want) + } + + m2 := Metric2(int, float64){} + m2.Add(1, 1) + m2.Add(2, 2) + m2.Add(3, 3) + m2.Add(3, 3) + k1, k2 := m2.Metrics() + + sort.Ints(k1) + w1 := []int{1, 2, 3} + if !slices.Equal(k1, w1) { + t.Errorf("Metric2.Metrics first slice = %v, want %v", k1, w1) + } + + sort.Float64s(k2) + w2 := []float64{1, 2, 3} + if !slices.Equal(k2, w2) { + t.Errorf("Metric2.Metrics first slice = %v, want %v", k2, w2) + } + + m3 := Metric3(string, S, S){} + m3.Add("a", S{"d", "e", "f"}, S{"g", "h", "i"}) + m3.Add("a", S{"d", "e", "f"}, S{"g", "h", "i"}) + m3.Add("a", S{"d", "e", "f"}, S{"g", "h", "i"}) + m3.Add("b", S{"d", "e", "f"}, S{"g", "h", "i"}) + if got := m3.Count("a", S{"d", "e", "f"}, S{"g", "h", "i"}); got != 3 { + t.Errorf("Count(%v, %v, %v) = %d, want 3", "a", S{"d", "e", "f"}, S{"g", "h", "i"}, got) + } +} diff --git a/src/go/go2go/names.go b/src/go/go2go/names.go index f8a578e730..02eddd7e77 100644 --- a/src/go/go2go/names.go +++ b/src/go/go2go/names.go @@ -20,17 +20,19 @@ const nameSep = '୦' const nameIntro = '୮' var nameCodes = map[rune]int{ - ' ': 0, - '*': 1, - ';': 2, - ',': 3, - '{': 4, - '}': 5, - '[': 6, - ']': 7, - '(': 8, - ')': 9, - '.': 10, + ' ': 0, + '*': 1, + ';': 2, + ',': 3, + '{': 4, + '}': 5, + '[': 6, + ']': 7, + '(': 8, + ')': 9, + '.': 10, + nameSep: 11, + nameIntro: 12, } // instantiatedName returns the name of a newly instantiated function. @@ -49,10 +51,7 @@ func (t *translator) instantiatedName(qid qualifiedIdent, types []types.Type) (s // This is not possible in general but we assume that // identifiers will not contain nameSep or nameIntro. for _, r := range s { - if r == nameSep || r == nameIntro { - panic(fmt.Sprintf("identifier %q contains mangling rune %c", s, r)) - } - if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' { + if (unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_') && r != nameSep && r != nameIntro { sb.WriteRune(r) } else { code, ok := nameCodes[r] @@ -66,6 +65,8 @@ func (t *translator) instantiatedName(qid qualifiedIdent, types []types.Type) (s return sb.String(), nil } +// importableName returns a name that we define in each package, so that +// we have something to import to avoid an unused package error. func (t *translator) importableName() string { return "Importable" + string(nameSep) } diff --git a/src/go/go2go/rewrite.go b/src/go/go2go/rewrite.go index f9a65c14a9..1e0bf423a0 100644 --- a/src/go/go2go/rewrite.go +++ b/src/go/go2go/rewrite.go @@ -689,8 +689,15 @@ func (t *translator) instantiationTypes(call *ast.CallExpr) (argList []ast.Expr, typeArgs = true } else { for _, typ := range inferred.Targs { - typeList = append(typeList, typ) arg := ast.NewIdent(typ.String()) + if named, ok := typ.(*types.Named); ok && len(named.TArgs()) > 0 { + var narg *ast.Ident + typ, narg = t.lookupInstantiatedType(named) + if narg != nil { + arg = ast.NewIdent(narg.Name) + } + } + typeList = append(typeList, typ) argList = append(argList, arg) t.setType(arg, typ) } @@ -699,6 +706,51 @@ func (t *translator) instantiationTypes(call *ast.CallExpr) (argList []ast.Expr, return } +// lookupInstantiatedType looks for an existing instantiation of an +// instantiated type. +func (t *translator) lookupInstantiatedType(typ *types.Named) (types.Type, *ast.Ident) { + // The name should be something like pkg.name. + name := typ.Obj().Name() + idx := strings.Index(name, "<") + if idx < 0 { + return typ, nil + } + name = name[:idx] + fields := strings.Split(name, ".") + if len(fields) > 2 { + panic(fmt.Sprintf("unparseable instantiated name %q", name)) + } + if len(fields) > 1 { + name = fields[1] + } + + tpkg := typ.Obj().Pkg() + nobj := tpkg.Scope().Lookup(name) + if nobj == nil { + panic(fmt.Sprintf("can't find %q in scope of package %q", name, tpkg.Name())) + } + + targs := typ.TArgs() + instantiations := t.typeInstantiations[nobj.Type()] + for _, inst := range instantiations { + if t.sameTypes(targs, inst.types) { + newName := inst.decl.Name + nm := typ.NumMethods() + methods := make([]*types.Func, 0, nm) + for i := 0; i < nm; i++ { + methods = append(methods, typ.Method(i)) + } + obj := typ.Obj() + obj = types.NewTypeName(obj.Pos(), obj.Pkg(), newName, nil) + nt := types.NewNamed(obj, typ.Underlying(), methods) + nt.SetTArgs(targs) + return nt, inst.decl + } + } + + panic(fmt.Sprintf("did not find instantiation for %v %v\n", typ, typ.Underlying())) +} + // sameTypes reports whether two type slices are the same. func (t *translator) sameTypes(a, b []types.Type) bool { if len(a) != len(b) { diff --git a/src/go/go2go/types.go b/src/go/go2go/types.go index 4545787b5d..dff761a7a0 100644 --- a/src/go/go2go/types.go +++ b/src/go/go2go/types.go @@ -168,6 +168,31 @@ func (t *translator) doInstantiateType(ta *typeArgs, typ types.Type) types.Type } return types.NewChan(typ.Dir(), elem) case *types.Named: + targs := typ.TArgs() + targsChanged := false + if len(targs) > 0 { + newTargs := make([]types.Type, 0, len(targs)) + for _, targ := range targs { + newTarg := t.instantiateType(ta, targ) + if newTarg != targ { + targsChanged = true + } + newTargs = append(newTargs, newTarg) + } + targs = newTargs + } + if targsChanged { + nm := typ.NumMethods() + methods := make([]*types.Func, 0, nm) + for i := 0; i < nm; i++ { + methods = append(methods, typ.Method(i)) + } + obj := typ.Obj() + obj = types.NewTypeName(obj.Pos(), obj.Pkg(), obj.Name(), nil) + nt := types.NewNamed(obj, typ.Underlying(), methods) + nt.SetTArgs(targs) + return nt + } return typ case *types.TypeParam: if instType, ok := ta.typ(typ); ok {