Skip to content
This repository has been archived by the owner on Jul 22, 2024. It is now read-only.

Commit

Permalink
Merge pull request #13 from jbardin/jbardin/pointers
Browse files Browse the repository at this point in the history
Allow reflectwalk to walk down chains of pointers
  • Loading branch information
mitchellh authored Oct 2, 2016
2 parents 0c9480f + 69e22e6 commit 4a18c29
Showing 2 changed files with 110 additions and 27 deletions.
53 changes: 35 additions & 18 deletions reflectwalk.go
Original file line number Diff line number Diff line change
@@ -81,35 +81,52 @@ func walk(v reflect.Value, w interface{}) (err error) {
// almost any part is changed). I will try to explain here.
//
// First, we check if the value is an interface, if so, we really need
// to check the interface's VALUE to see whether it is a pointer (pointers
// to interfaces are not allowed).
// to check the interface's VALUE to see whether it is a pointer.
//
// Check whether the value is then an interface. If so, then set pointer
// Check whether the value is then a pointer. If so, then set pointer
// to true to notify the user.
//
// If we still have a pointer or an interface after the indirections, then
// we unwrap another level
//
// At this time, we also set "v" to be the dereferenced value. This is
// because once we've unwrapped the pointer we want to use that value.
pointer := false
pointerV := v
if pointerV.Kind() == reflect.Interface {
pointerV = pointerV.Elem()
}
if pointerV.Kind() == reflect.Ptr {
pointer = true
v = reflect.Indirect(pointerV)
}
if pw, ok := w.(PointerWalker); ok {
if err = pw.PointerEnter(pointer); err != nil {
return
}

defer func() {
if err != nil {
for {
if pointerV.Kind() == reflect.Interface {
pointerV = pointerV.Elem()
}
if pointerV.Kind() == reflect.Ptr {
pointer = true
v = reflect.Indirect(pointerV)
}
if pw, ok := w.(PointerWalker); ok {
if err = pw.PointerEnter(pointer); err != nil {
return
}

err = pw.PointerExit(pointer)
}()
defer func(pointer bool) {
if err != nil {
return
}

err = pw.PointerExit(pointer)
}(pointer)
}

if pointer {
pointerV = v
}
pointer = false

// If we still have a pointer or interface we have to indirect another level.
switch pointerV.Kind() {
case reflect.Ptr, reflect.Interface:
continue
}
break
}

// We preserve the original value here because if it is an interface
84 changes: 75 additions & 9 deletions reflectwalk_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package reflectwalk

import (
"fmt"
"reflect"
"testing"
)
@@ -24,15 +25,27 @@ func (t *TestEnterExitWalker) Exit(l Location) error {
}

type TestPointerWalker struct {
Ps []bool
pointers []bool
count int
enters int
exits int
}

func (t *TestPointerWalker) PointerEnter(v bool) error {
t.Ps = append(t.Ps, v)
t.pointers = append(t.pointers, v)
t.enters++
if v {
t.count++
}
return nil
}

func (t *TestPointerWalker) PointerExit(v bool) error {
t.exits++
if t.pointers[len(t.pointers)-1] != v {
return fmt.Errorf("bad pointer exit '%t' at exit %d", v, t.exits)
}
t.pointers = t.pointers[:len(t.pointers)-1]
return nil
}

@@ -327,20 +340,57 @@ func TestWalk_Pointer(t *testing.T) {

type S struct {
Foo string
Bar *string
Baz **string
}

s := ""
sp := &s

data := &S{
Foo: "foo",
Baz: &sp,
}

err := Walk(data, w)
if err != nil {
t.Fatalf("err: %s", err)
}

expected := []bool{true, false}
if !reflect.DeepEqual(w.Ps, expected) {
t.Fatalf("bad: %#v", w.Ps)
if w.enters != 5 {
t.Fatal("expected 4 values, saw", w.enters)
}

if w.count != 4 {
t.Fatal("exptec 3 pointers, saw", w.count)
}

if w.exits != w.enters {
t.Fatalf("number of enters (%d) and exits (%d) don't match", w.enters, w.exits)
}
}

func TestWalk_PointerPointer(t *testing.T) {
w := new(TestPointerWalker)

s := ""
sp := &s
pp := &sp

err := Walk(pp, w)
if err != nil {
t.Fatalf("err: %s", err)
}

if w.enters != 2 {
t.Fatal("expected 2 values, saw", w.enters)
}

if w.count != 2 {
t.Fatal("expected 2 pointers, saw", w.count)
}

if w.exits != w.enters {
t.Fatalf("number of enters (%d) and exits (%d) don't match", w.enters, w.exits)
}
}

@@ -400,25 +450,41 @@ func TestWalk_SliceWithPtr(t *testing.T) {
}
}

type testErr struct{}

func (t *testErr) Error() string {
return "test error"
}

func TestWalk_Struct(t *testing.T) {
w := new(TestStructWalker)

// This makes sure we can also walk over pointer-to-pointers, and the ever
// so rare pointer-to-interface
type S struct {
Foo string
Bar string
Bar *string
Baz **string
Err *error
}

bar := "ptr"
baz := &bar
e := error(&testErr{})

data := &S{
Foo: "foo",
Bar: "bar",
Bar: &bar,
Baz: &baz,
Err: &e,
}

err := Walk(data, w)
if err != nil {
t.Fatalf("err: %s", err)
}

expected := []string{"Foo", "Bar"}
expected := []string{"Foo", "Bar", "Baz", "Err"}
if !reflect.DeepEqual(w.Fields, expected) {
t.Fatalf("bad: %#v", w.Fields)
}

0 comments on commit 4a18c29

Please sign in to comment.