go/go2go: support instantiating with an instantiated type

Add metrics test case to test GO2PATH.

Change-Id: I48c8a418d743ccd80cdcf36fc043ca0e2dd932f4
This commit is contained in:
Ian Lance Taylor 2020-03-25 21:20:53 -07:00 committed by Robert Griesemer
parent 42884d7524
commit 01ab391f82
5 changed files with 273 additions and 16 deletions

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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<type1, type2>.
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) {

View File

@ -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 {