Skip to content

Commit

Permalink
Merge pull request jmoiron#358 from nussjustin/traversals-by-name-func
Browse files Browse the repository at this point in the history
Add and use TraversalsByNameFunc for zero allocation field traversal
  • Loading branch information
jmoiron authored Dec 11, 2017
2 parents 99f3ad6 + 2824d91 commit de86474
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 11 deletions.
12 changes: 7 additions & 5 deletions named.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,16 +163,18 @@ func bindArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{
v = v.Elem()
}

fields := m.TraversalsByName(v.Type(), names)
for i, t := range fields {
err := m.TraversalsByNameFunc(v.Type(), names, func(i int, t []int) error {
if len(t) == 0 {
return arglist, fmt.Errorf("could not find name %s in %#v", names[i], arg)
return fmt.Errorf("could not find name %s in %#v", names[i], arg)
}

val := reflectx.FieldByIndexesReadOnly(v, t)
arglist = append(arglist, val.Interface())
}

return arglist, nil
return nil
})

return arglist, err
}

// like bindArgs, but for maps.
Expand Down
31 changes: 25 additions & 6 deletions reflectx/reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,20 +166,39 @@ func (m *Mapper) FieldsByName(v reflect.Value, names []string) []reflect.Value {
// traversals for each mapped name. Panics if t is not a struct or Indirectable
// to a struct. Returns empty int slice for each name not found.
func (m *Mapper) TraversalsByName(t reflect.Type, names []string) [][]int {
r := make([][]int, 0, len(names))
m.TraversalsByNameFunc(t, names, func(_ int, i []int) error {
if i == nil {
r = append(r, []int{})
} else {
r = append(r, i)
}

return nil
})
return r
}

// TraversalsByNameFunc traverses the mapped names and calls fn with the index of
// each name and the struct traversal represented by that name. Panics if t is not
// a struct or Indirectable to a struct. Returns the first error returned by fn or nil.
func (m *Mapper) TraversalsByNameFunc(t reflect.Type, names []string, fn func(int, []int) error) error {
t = Deref(t)
mustBe(t, reflect.Struct)
tm := m.TypeMap(t)

r := make([][]int, 0, len(names))
for _, name := range names {
for i, name := range names {
fi, ok := tm.Names[name]
if !ok {
r = append(r, []int{})
if err := fn(i, nil); err != nil {
return err
}
} else {
r = append(r, fi.Index)
if err := fn(i, fi.Index); err != nil {
return err
}
}
}
return r
return nil
}

// FieldByIndexes returns a value for the field given by the struct traversal
Expand Down
69 changes: 69 additions & 0 deletions reflectx/reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -903,3 +903,72 @@ func BenchmarkFieldByIndexL4(b *testing.B) {
}
}
}

func BenchmarkTraversalsByName(b *testing.B) {
type A struct {
Value int
}

type B struct {
A A
}

type C struct {
B B
}

type D struct {
C C
}

m := NewMapper("")
t := reflect.TypeOf(D{})
names := []string{"C", "B", "A", "Value"}

b.ResetTimer()

for i := 0; i < b.N; i++ {
if l := len(m.TraversalsByName(t, names)); l != len(names) {
b.Errorf("expected %d values, got %d", len(names), l)
}
}
}

func BenchmarkTraversalsByNameFunc(b *testing.B) {
type A struct {
Z int
}

type B struct {
A A
}

type C struct {
B B
}

type D struct {
C C
}

m := NewMapper("")
t := reflect.TypeOf(D{})
names := []string{"C", "B", "A", "Z", "Y"}

b.ResetTimer()

for i := 0; i < b.N; i++ {
var l int

if err := m.TraversalsByNameFunc(t, names, func(_ int, _ []int) error {
l++
return nil
}); err != nil {
b.Errorf("unexpected error %s", err)
}

if l != len(names) {
b.Errorf("expected %d values, got %d", len(names), l)
}
}
}

0 comments on commit de86474

Please sign in to comment.