reflect: implement StructOf

This change exposes a facility to create new struct types from a slice of
reflect.StructFields.

- reflect: first stab at implementing StructOf
- reflect: tests for StructOf

StructOf creates new struct types in the form of structTypeWithMethods
to accomodate the GC (especially the uncommonType.methods slice field.)

Creating struct types with embedded interfaces with unexported methods
is not supported yet and will panic.
Creating struct types with non-ASCII field names or types is not yet
supported (see #15064.)

Binaries' sizes for linux_amd64:

old=tip (0104a31)

            old bytes     new bytes     delta
bin/go      9911336       9915456       +0.04%
reflect     781704        830048        +6.18%

Updates #5748.
Updates #15064.

Change-Id: I3b8fd4fadd6ce3b1b922e284f0ae72a3a8e3ce44
Reviewed-on: https://go-review.googlesource.com/9251
Reviewed-by: David Crawshaw <crawshaw@golang.org>
Run-TryBot: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
Sebastien Binet 2016-03-05 13:37:38 +01:00 committed by David Crawshaw
parent 014f3e1e09
commit 633ab7426a
3 changed files with 1043 additions and 7 deletions

View file

@ -21,6 +21,8 @@ import (
"sync"
"testing"
"time"
"unicode"
"unicode/utf8"
"unsafe"
)
@ -3958,6 +3960,579 @@ func TestSliceOfGC(t *testing.T) {
}
}
func TestStructOf(t *testing.T) {
// check construction and use of type not in binary
fields := []StructField{
StructField{
Name: "S",
Tag: "s",
Type: TypeOf(""),
},
StructField{
Name: "X",
Tag: "x",
Type: TypeOf(byte(0)),
},
StructField{
Name: "Y",
Type: TypeOf(uint64(0)),
},
StructField{
Name: "Z",
Type: TypeOf([3]uint16{}),
},
}
st := StructOf(fields)
v := New(st).Elem()
runtime.GC()
v.FieldByName("X").Set(ValueOf(byte(2)))
v.FieldByIndex([]int{1}).Set(ValueOf(byte(1)))
runtime.GC()
s := fmt.Sprint(v.Interface())
want := `{ 1 0 [0 0 0]}`
if s != want {
t.Errorf("constructed struct = %s, want %s", s, want)
}
// check the size, alignment and field offsets
stt := TypeOf(struct {
String string
X byte
Y uint64
Z [3]uint16
}{})
if st.Size() != stt.Size() {
t.Errorf("constructed struct size = %v, want %v", st.Size(), stt.Size())
}
if st.Align() != stt.Align() {
t.Errorf("constructed struct align = %v, want %v", st.Align(), stt.Align())
}
if st.FieldAlign() != stt.FieldAlign() {
t.Errorf("constructed struct field align = %v, want %v", st.FieldAlign(), stt.FieldAlign())
}
for i := 0; i < st.NumField(); i++ {
o1 := st.Field(i).Offset
o2 := stt.Field(i).Offset
if o1 != o2 {
t.Errorf("constructed struct field %v offset = %v, want %v", i, o1, o2)
}
}
// check duplicate names
shouldPanic(func() {
StructOf([]StructField{
StructField{Name: "string", Type: TypeOf("")},
StructField{Name: "string", Type: TypeOf("")},
})
})
shouldPanic(func() {
StructOf([]StructField{
StructField{Type: TypeOf("")},
StructField{Name: "string", Type: TypeOf("")},
})
})
shouldPanic(func() {
StructOf([]StructField{
StructField{Type: TypeOf("")},
StructField{Type: TypeOf("")},
})
})
// check that type already in binary is found
checkSameType(t, Zero(StructOf(fields[2:3])).Interface(), struct{ Y uint64 }{})
}
func TestStructOfExportRules(t *testing.T) {
type S1 struct{}
type s2 struct{}
type ΦType struct{}
type φType struct{}
testPanic := func(i int, mustPanic bool, f func()) {
defer func() {
err := recover()
if err == nil && mustPanic {
t.Errorf("test-%d did not panic", i)
}
if err != nil && !mustPanic {
t.Errorf("test-%d panicked: %v\n", i, err)
}
}()
f()
}
for i, test := range []struct {
field StructField
mustPanic bool
exported bool
}{
{
field: StructField{Name: "", Type: TypeOf(S1{})},
mustPanic: false,
exported: true,
},
{
field: StructField{Name: "", Type: TypeOf((*S1)(nil))},
mustPanic: false,
exported: true,
},
{
field: StructField{Name: "", Type: TypeOf(s2{})},
mustPanic: false,
exported: false,
},
{
field: StructField{Name: "", Type: TypeOf((*s2)(nil))},
mustPanic: false,
exported: false,
},
{
field: StructField{Name: "", Type: TypeOf(S1{}), PkgPath: "other/pkg"},
mustPanic: true,
exported: true,
},
{
field: StructField{Name: "", Type: TypeOf((*S1)(nil)), PkgPath: "other/pkg"},
mustPanic: true,
exported: true,
},
{
field: StructField{Name: "", Type: TypeOf(s2{}), PkgPath: "other/pkg"},
mustPanic: true,
exported: false,
},
{
field: StructField{Name: "", Type: TypeOf((*s2)(nil)), PkgPath: "other/pkg"},
mustPanic: true,
exported: false,
},
{
field: StructField{Name: "S", Type: TypeOf(S1{})},
mustPanic: false,
exported: true,
},
{
field: StructField{Name: "S", Type: TypeOf((*S1)(nil))},
mustPanic: false,
exported: true,
},
{
field: StructField{Name: "S", Type: TypeOf(s2{})},
mustPanic: false,
exported: true,
},
{
field: StructField{Name: "S", Type: TypeOf((*s2)(nil))},
mustPanic: false,
exported: true,
},
{
field: StructField{Name: "s", Type: TypeOf(S1{})},
mustPanic: true,
exported: false,
},
{
field: StructField{Name: "s", Type: TypeOf((*S1)(nil))},
mustPanic: true,
exported: false,
},
{
field: StructField{Name: "s", Type: TypeOf(s2{})},
mustPanic: true,
exported: false,
},
{
field: StructField{Name: "s", Type: TypeOf((*s2)(nil))},
mustPanic: true,
exported: false,
},
{
field: StructField{Name: "s", Type: TypeOf(S1{}), PkgPath: "other/pkg"},
mustPanic: true, // TODO(sbinet): creating a name with a package path
exported: false,
},
{
field: StructField{Name: "s", Type: TypeOf((*S1)(nil)), PkgPath: "other/pkg"},
mustPanic: true, // TODO(sbinet): creating a name with a package path
exported: false,
},
{
field: StructField{Name: "s", Type: TypeOf(s2{}), PkgPath: "other/pkg"},
mustPanic: true, // TODO(sbinet): creating a name with a package path
exported: false,
},
{
field: StructField{Name: "s", Type: TypeOf((*s2)(nil)), PkgPath: "other/pkg"},
mustPanic: true, // TODO(sbinet): creating a name with a package path
exported: false,
},
{
field: StructField{Name: "", Type: TypeOf(ΦType{})},
mustPanic: true, // TODO(sbinet): creating a struct with UTF-8 fields not supported
exported: true,
},
{
field: StructField{Name: "", Type: TypeOf(φType{})},
mustPanic: true, // TODO(sbinet): creating a struct with UTF-8 fields not supported
exported: false,
},
{
field: StructField{Name: "Φ", Type: TypeOf(0)},
mustPanic: false,
exported: true,
},
{
field: StructField{Name: "φ", Type: TypeOf(0)},
mustPanic: false,
exported: false,
},
} {
testPanic(i, test.mustPanic, func() {
typ := StructOf([]StructField{test.field})
if typ == nil {
t.Errorf("test-%d: error creating struct type", i)
return
}
field := typ.Field(0)
n := field.Name
if n == "" {
n = field.Type.Name()
}
exported := isExported(n)
if exported != test.exported {
t.Errorf("test-%d: got exported=%v want exported=%v", exported, test.exported)
}
})
}
}
// isExported reports whether name is an exported Go symbol
// (that is, whether it begins with an upper-case letter).
//
func isExported(name string) bool {
ch, _ := utf8.DecodeRuneInString(name)
return unicode.IsUpper(ch)
}
func TestStructOfGC(t *testing.T) {
type T *uintptr
tt := TypeOf(T(nil))
fields := []StructField{
{Name: "X", Type: tt},
{Name: "Y", Type: tt},
}
st := StructOf(fields)
const n = 10000
var x []interface{}
for i := 0; i < n; i++ {
v := New(st).Elem()
for j := 0; j < v.NumField(); j++ {
p := new(uintptr)
*p = uintptr(i*n + j)
v.Field(j).Set(ValueOf(p).Convert(tt))
}
x = append(x, v.Interface())
}
runtime.GC()
for i, xi := range x {
v := ValueOf(xi)
for j := 0; j < v.NumField(); j++ {
k := v.Field(j).Elem().Interface()
if k != uintptr(i*n+j) {
t.Errorf("lost x[%d].%c = %d, want %d", i, "XY"[j], k, i*n+j)
}
}
}
}
func TestStructOfAlg(t *testing.T) {
st := StructOf([]StructField{{Name: "X", Tag: "x", Type: TypeOf(int(0))}})
v1 := New(st).Elem()
v2 := New(st).Elem()
if !DeepEqual(v1.Interface(), v1.Interface()) {
t.Errorf("constructed struct %v not equal to itself", v1.Interface())
}
v1.FieldByName("X").Set(ValueOf(int(1)))
if i1, i2 := v1.Interface(), v2.Interface(); DeepEqual(i1, i2) {
t.Errorf("constructed structs %v and %v should not be equal", i1, i2)
}
st = StructOf([]StructField{{Name: "X", Tag: "x", Type: TypeOf([]int(nil))}})
v1 = New(st).Elem()
shouldPanic(func() { _ = v1.Interface() == v1.Interface() })
}
func TestStructOfGenericAlg(t *testing.T) {
st1 := StructOf([]StructField{
{Name: "X", Tag: "x", Type: TypeOf(int64(0))},
{Name: "Y", Type: TypeOf(string(""))},
})
st := StructOf([]StructField{
{Name: "S0", Type: st1},
{Name: "S1", Type: st1},
})
for _, table := range []struct {
rt Type
idx []int
}{
{
rt: st,
idx: []int{0, 1},
},
{
rt: st1,
idx: []int{1},
},
{
rt: StructOf(
[]StructField{
{Name: "XX", Type: TypeOf([0]int{})},
{Name: "YY", Type: TypeOf("")},
},
),
idx: []int{1},
},
{
rt: StructOf(
[]StructField{
{Name: "XX", Type: TypeOf([0]int{})},
{Name: "YY", Type: TypeOf("")},
{Name: "ZZ", Type: TypeOf([2]int{})},
},
),
idx: []int{1},
},
{
rt: StructOf(
[]StructField{
{Name: "XX", Type: TypeOf([1]int{})},
{Name: "YY", Type: TypeOf("")},
},
),
idx: []int{1},
},
{
rt: StructOf(
[]StructField{
{Name: "XX", Type: TypeOf([1]int{})},
{Name: "YY", Type: TypeOf("")},
{Name: "ZZ", Type: TypeOf([1]int{})},
},
),
idx: []int{1},
},
{
rt: StructOf(
[]StructField{
{Name: "XX", Type: TypeOf([2]int{})},
{Name: "YY", Type: TypeOf("")},
{Name: "ZZ", Type: TypeOf([2]int{})},
},
),
idx: []int{1},
},
{
rt: StructOf(
[]StructField{
{Name: "XX", Type: TypeOf(int64(0))},
{Name: "YY", Type: TypeOf(byte(0))},
{Name: "ZZ", Type: TypeOf("")},
},
),
idx: []int{2},
},
{
rt: StructOf(
[]StructField{
{Name: "XX", Type: TypeOf(int64(0))},
{Name: "YY", Type: TypeOf(int64(0))},
{Name: "ZZ", Type: TypeOf("")},
{Name: "AA", Type: TypeOf([1]int64{})},
},
),
idx: []int{2},
},
} {
v1 := New(table.rt).Elem()
v2 := New(table.rt).Elem()
if !DeepEqual(v1.Interface(), v1.Interface()) {
t.Errorf("constructed struct %v not equal to itself", v1.Interface())
}
v1.FieldByIndex(table.idx).Set(ValueOf("abc"))
v2.FieldByIndex(table.idx).Set(ValueOf("def"))
if i1, i2 := v1.Interface(), v2.Interface(); DeepEqual(i1, i2) {
t.Errorf("constructed structs %v and %v should not be equal", i1, i2)
}
abc := "abc"
v1.FieldByIndex(table.idx).Set(ValueOf(abc))
val := "+" + abc + "-"
v2.FieldByIndex(table.idx).Set(ValueOf(val[1:4]))
if i1, i2 := v1.Interface(), v2.Interface(); !DeepEqual(i1, i2) {
t.Errorf("constructed structs %v and %v should be equal", i1, i2)
}
// Test hash
m := MakeMap(MapOf(table.rt, TypeOf(int(0))))
m.SetMapIndex(v1, ValueOf(1))
if i1, i2 := v1.Interface(), v2.Interface(); !m.MapIndex(v2).IsValid() {
t.Errorf("constructed structs %#v and %#v have different hashes", i1, i2)
}
v2.FieldByIndex(table.idx).Set(ValueOf("abc"))
if i1, i2 := v1.Interface(), v2.Interface(); !DeepEqual(i1, i2) {
t.Errorf("constructed structs %v and %v should be equal", i1, i2)
}
if i1, i2 := v1.Interface(), v2.Interface(); !m.MapIndex(v2).IsValid() {
t.Errorf("constructed structs %v and %v have different hashes", i1, i2)
}
}
}
func TestStructOfDirectIface(t *testing.T) {
{
type T struct{ X [1]*byte }
i1 := Zero(TypeOf(T{})).Interface()
v1 := ValueOf(&i1).Elem()
p1 := v1.InterfaceData()[1]
i2 := Zero(StructOf([]StructField{
{
Name: "X",
Type: ArrayOf(1, TypeOf((*int8)(nil))),
},
})).Interface()
v2 := ValueOf(&i2).Elem()
p2 := v2.InterfaceData()[1]
if p1 != 0 {
t.Errorf("got p1=%v. want=%v", p1, nil)
}
if p2 != 0 {
t.Errorf("got p2=%v. want=%v", p2, nil)
}
}
{
type T struct{ X [0]*byte }
i1 := Zero(TypeOf(T{})).Interface()
v1 := ValueOf(&i1).Elem()
p1 := v1.InterfaceData()[1]
i2 := Zero(StructOf([]StructField{
{
Name: "X",
Type: ArrayOf(0, TypeOf((*int8)(nil))),
},
})).Interface()
v2 := ValueOf(&i2).Elem()
p2 := v2.InterfaceData()[1]
if p1 == 0 {
t.Errorf("got p1=%v. want=not-%v", p1, nil)
}
if p2 == 0 {
t.Errorf("got p2=%v. want=not-%v", p2, nil)
}
}
}
type StructI int
func (i StructI) Get() int { return int(i) }
type StructIPtr int
func (i *StructIPtr) Get() int { return int(*i) }
func TestStructOfWithInterface(t *testing.T) {
const want = 42
type Iface interface {
Get() int
}
for i, table := range []struct {
typ Type
val Value
impl bool
}{
{
typ: TypeOf(StructI(want)),
val: ValueOf(StructI(want)),
impl: true,
},
{
typ: PtrTo(TypeOf(StructI(want))),
val: ValueOf(func() interface{} {
v := StructI(want)
return &v
}()),
impl: true,
},
{
typ: PtrTo(TypeOf(StructIPtr(want))),
val: ValueOf(func() interface{} {
v := StructIPtr(want)
return &v
}()),
impl: true,
},
{
typ: TypeOf(StructIPtr(want)),
val: ValueOf(StructIPtr(want)),
impl: false,
},
// {
// typ: TypeOf((*Iface)(nil)).Elem(), // FIXME(sbinet): fix method.ifn/tfn
// val: ValueOf(StructI(want)),
// impl: true,
// },
} {
rt := StructOf(
[]StructField{
{
Name: "",
PkgPath: "",
Type: table.typ,
},
},
)
rv := New(rt).Elem()
rv.Field(0).Set(table.val)
if _, ok := rv.Interface().(Iface); ok != table.impl {
if table.impl {
t.Errorf("test-%d: type=%v fails to implement Iface.\n", i, table.typ)
} else {
t.Errorf("test-%d: type=%v should NOT implement Iface\n", table.typ)
}
continue
}
if !table.impl {
continue
}
v := rv.Interface().(Iface).Get()
if v != want {
t.Errorf("test-%d: x.Get()=%v. want=%v\n", i, v, want)
}
fct := rv.MethodByName("Get")
out := fct.Call(nil)
if !DeepEqual(out[0].Interface(), want) {
t.Errorf("test-%d: x.Get()=%v. want=%v\n", i, out[0].Interface(), want)
}
}
}
func TestChanOf(t *testing.T) {
// check construction and use of type not in binary
type T string