]> Cypherpunks repositories - gostls13.git/commitdiff
text/template: reintroduce implicit indirect of interface values in builtin funcs
authorRuss Cox <rsc@golang.org>
Fri, 11 Nov 2016 16:54:18 +0000 (11:54 -0500)
committerRuss Cox <rsc@golang.org>
Fri, 11 Nov 2016 19:46:17 +0000 (19:46 +0000)
CL 31462 made it possible to operate directly on reflect.Values
instead of always forcing a round trip to interface{} and back.
The round trip was losing addressability, which hurt users.

The round trip was also losing "interface-ness", which helped users.
That is, using reflect.ValueOf(v.Interface()) instead of v was doing
an implicit indirect any time v was itself an interface{} value: the result
was the reflect.Value for the underlying concrete value contained in the
interface, not the interface itself.

CL 31462 eliminated some "unnecessary" reflect.Value round trips
in order to preserve addressability, but in doing so it lost this implicit
indirection. This CL adds the indirection back.

It may help to compare the changes in this CL against funcs.go from CL 31462:
https://go-review.googlesource.com/#/c/31462/4/src/text/template/funcs.go

Everywhere CL 31462 changed 'v := reflect.ValueOf(x)' to 'v := x',
this CL changes 'v := x' to 'v := indirectInterface(x)'.

Fixes #17714.

Change-Id: I67cec4eb41fed1d56e1c19f12b0abbd0e59d35a2
Reviewed-on: https://go-review.googlesource.com/33139
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Rob Pike <r@golang.org>
src/text/template/exec.go
src/text/template/exec_test.go
src/text/template/funcs.go

index 7db4a87d2ea82673c7da9227047c1063a79617f4..49f15faacde10182d31c23206452574d88376ac5 100644 (file)
@@ -874,6 +874,20 @@ func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
        return v, false
 }
 
+// indirectInterface returns the concrete value in an interface value,
+// or else the zero reflect.Value.
+// That is, if v represents the interface value x, the result is the same as reflect.ValueOf(x):
+// the fact that x was an interface value is forgotten.
+func indirectInterface(v reflect.Value) reflect.Value {
+       if v.Kind() != reflect.Interface {
+               return v
+       }
+       if v.IsNil() {
+               return reflect.Value{}
+       }
+       return v.Elem()
+}
+
 // printValue writes the textual representation of the value to the output of
 // the template.
 func (s *state) printValue(n parse.Node, v reflect.Value) {
index 99b9434b783606f1c8a928d80b12b296f5260f13..9b4da435bc2c52f5ae9ecb8ac1c23335af912c76 100644 (file)
@@ -1329,7 +1329,80 @@ func TestAddrOfIndex(t *testing.T) {
                        t.Fatalf("%s: Execute: %v", text, err)
                }
                if buf.String() != "<1>" {
-                       t.Fatalf("%s: template output = %q, want %q", text, buf, "<1>")
+                       t.Fatalf("%s: template output = %q, want %q", text, &buf, "<1>")
+               }
+       }
+}
+
+func TestInterfaceValues(t *testing.T) {
+       // golang.org/issue/17714.
+       // Before index worked on reflect.Values, interface values
+       // were always implicitly promoted to the underlying value,
+       // except that nil interfaces were promoted to the zero reflect.Value.
+       // Eliminating a round trip to interface{} and back to reflect.Value
+       // eliminated this promotion, breaking these cases.
+       tests := []struct {
+               text string
+               out  string
+       }{
+               {`{{index .Nil 1}}`, "ERROR: index of untyped nil"},
+               {`{{index .Slice 2}}`, "2"},
+               {`{{index .Slice .Two}}`, "2"},
+               {`{{call .Nil 1}}`, "ERROR: call of nil"},
+               {`{{call .PlusOne 1}}`, "2"},
+               {`{{call .PlusOne .One}}`, "2"},
+               {`{{and (index .Slice 0) true}}`, "0"},
+               {`{{and .Zero true}}`, "0"},
+               {`{{and (index .Slice 1) false}}`, "false"},
+               {`{{and .One false}}`, "false"},
+               {`{{or (index .Slice 0) false}}`, "false"},
+               {`{{or .Zero false}}`, "false"},
+               {`{{or (index .Slice 1) true}}`, "1"},
+               {`{{or .One true}}`, "1"},
+               {`{{not (index .Slice 0)}}`, "true"},
+               {`{{not .Zero}}`, "true"},
+               {`{{not (index .Slice 1)}}`, "false"},
+               {`{{not .One}}`, "false"},
+               {`{{eq (index .Slice 0) .Zero}}`, "true"},
+               {`{{eq (index .Slice 1) .One}}`, "true"},
+               {`{{ne (index .Slice 0) .Zero}}`, "false"},
+               {`{{ne (index .Slice 1) .One}}`, "false"},
+               {`{{ge (index .Slice 0) .One}}`, "false"},
+               {`{{ge (index .Slice 1) .Zero}}`, "true"},
+               {`{{gt (index .Slice 0) .One}}`, "false"},
+               {`{{gt (index .Slice 1) .Zero}}`, "true"},
+               {`{{le (index .Slice 0) .One}}`, "true"},
+               {`{{le (index .Slice 1) .Zero}}`, "false"},
+               {`{{lt (index .Slice 0) .One}}`, "true"},
+               {`{{lt (index .Slice 1) .Zero}}`, "false"},
+       }
+
+       for _, tt := range tests {
+               tmpl := Must(New("tmpl").Parse(tt.text))
+               var buf bytes.Buffer
+               err := tmpl.Execute(&buf, map[string]interface{}{
+                       "PlusOne": func(n int) int {
+                               return n + 1
+                       },
+                       "Slice": []int{0, 1, 2, 3},
+                       "One":   1,
+                       "Two":   2,
+                       "Nil":   nil,
+                       "Zero":  0,
+               })
+               if strings.HasPrefix(tt.out, "ERROR:") {
+                       e := strings.TrimSpace(strings.TrimPrefix(tt.out, "ERROR:"))
+                       if err == nil || !strings.Contains(err.Error(), e) {
+                               t.Errorf("%s: Execute: %v, want error %q", tt.text, err, e)
+                       }
+                       continue
+               }
+               if err != nil {
+                       t.Errorf("%s: Execute: %v", tt.text, err)
+                       continue
+               }
+               if buf.String() != tt.out {
+                       t.Errorf("%s: template output = %q, want %q", tt.text, &buf, tt.out)
                }
        }
 }
index 8d8bc059f02aaddfe880772f3f9a8bd1badc929d..3047b272e57af3999c2269032f1332ba9ddbbef2 100644 (file)
@@ -151,11 +151,12 @@ func prepareArg(value reflect.Value, argType reflect.Type) (reflect.Value, error
 // arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
 // indexed item must be a map, slice, or array.
 func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) {
-       v := item
+       v := indirectInterface(item)
        if !v.IsValid() {
                return reflect.Value{}, fmt.Errorf("index of untyped nil")
        }
-       for _, index := range indices {
+       for _, i := range indices {
+               index := indirectInterface(i)
                var isNil bool
                if v, isNil = indirect(v); isNil {
                        return reflect.Value{}, fmt.Errorf("index of nil pointer")
@@ -221,7 +222,7 @@ func length(item interface{}) (int, error) {
 // call returns the result of evaluating the first argument as a function.
 // The function must return 1 result, or 2 results, the second of which is an error.
 func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) {
-       v := fn
+       v := indirectInterface(fn)
        if !v.IsValid() {
                return reflect.Value{}, fmt.Errorf("call of nil")
        }
@@ -245,7 +246,8 @@ func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) {
                }
        }
        argv := make([]reflect.Value, len(args))
-       for i, value := range args {
+       for i, arg := range args {
+               value := indirectInterface(arg)
                // Compute the expected type. Clumsy because of variadics.
                var argType reflect.Type
                if !typ.IsVariadic() || i < numIn-1 {
@@ -269,7 +271,7 @@ func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) {
 // Boolean logic.
 
 func truth(arg reflect.Value) bool {
-       t, _ := isTrue(arg)
+       t, _ := isTrue(indirectInterface(arg))
        return t
 }
 
@@ -350,7 +352,7 @@ func basicKind(v reflect.Value) (kind, error) {
 
 // eq evaluates the comparison a == b || a == c || ...
 func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
-       v1 := arg1
+       v1 := indirectInterface(arg1)
        k1, err := basicKind(v1)
        if err != nil {
                return false, err
@@ -358,7 +360,8 @@ func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
        if len(arg2) == 0 {
                return false, errNoComparison
        }
-       for _, v2 := range arg2 {
+       for _, arg := range arg2 {
+               v2 := indirectInterface(arg)
                k2, err := basicKind(v2)
                if err != nil {
                        return false, err
@@ -407,11 +410,13 @@ func ne(arg1, arg2 reflect.Value) (bool, error) {
 }
 
 // lt evaluates the comparison a < b.
-func lt(v1, v2 reflect.Value) (bool, error) {
+func lt(arg1, arg2 reflect.Value) (bool, error) {
+       v1 := indirectInterface(arg1)
        k1, err := basicKind(v1)
        if err != nil {
                return false, err
        }
+       v2 := indirectInterface(arg2)
        k2, err := basicKind(v2)
        if err != nil {
                return false, err