diff --git a/src/text/template/exec.go b/src/text/template/exec.go index 7db4a87d2e..49f15faacd 100644 --- a/src/text/template/exec.go +++ b/src/text/template/exec.go @@ -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) { diff --git a/src/text/template/exec_test.go b/src/text/template/exec_test.go index 99b9434b78..9b4da435bc 100644 --- a/src/text/template/exec_test.go +++ b/src/text/template/exec_test.go @@ -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) } } } diff --git a/src/text/template/funcs.go b/src/text/template/funcs.go index 8d8bc059f0..3047b272e5 100644 --- a/src/text/template/funcs.go +++ b/src/text/template/funcs.go @@ -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