]> Cypherpunks repositories - gostls13.git/commitdiff
text/template: add support for reflect.Value args, results in funcs
authorRuss Cox <rsc@golang.org>
Wed, 19 Oct 2016 13:11:16 +0000 (09:11 -0400)
committerRuss Cox <rsc@golang.org>
Wed, 19 Oct 2016 18:20:35 +0000 (18:20 +0000)
Add support for passing reflect.Values to and returning reflect.Values from
any registered functions in the FuncMap, much as if they were
interface{} values. Keeping the reflect.Value instead of round-tripping
to interface{} preserves addressability of the value, which is important
for method lookup.

Change index and a few other built-in functions to use reflect.Values,
making a loop using explicit indexing now match the semantics that
range has always had.

Fixes #14916.

Change-Id: Iae1a2fd9bb426886a7fcd9204f30a2d6ad4646ad
Reviewed-on: https://go-review.googlesource.com/31462
Reviewed-by: Rob Pike <r@golang.org>
src/text/template/exec.go
src/text/template/exec_test.go
src/text/template/funcs.go

index 8e5ad93ca6b5b881d87b73a6bbeaf808bd368904..5a6e454ec611f9d4cea8db992521ef700a1cc44e 100644 (file)
@@ -171,13 +171,19 @@ func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{})
 // execution stops, but partial results may already have been written to
 // the output writer.
 // A template may be executed safely in parallel.
+//
+// If data is a reflect.Value, the template applies to the concrete
+// value that the reflect.Value holds, as in fmt.Print.
 func (t *Template) Execute(wr io.Writer, data interface{}) error {
        return t.execute(wr, data)
 }
 
 func (t *Template) execute(wr io.Writer, data interface{}) (err error) {
        defer errRecover(&err)
-       value := reflect.ValueOf(data)
+       value, ok := data.(reflect.Value)
+       if !ok {
+               value = reflect.ValueOf(data)
+       }
        state := &state{
                tmpl: t,
                wr:   wr,
@@ -596,8 +602,9 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node,
 }
 
 var (
-       errorType       = reflect.TypeOf((*error)(nil)).Elem()
-       fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
+       errorType        = reflect.TypeOf((*error)(nil)).Elem()
+       fmtStringerType  = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
+       reflectValueType = reflect.TypeOf((*reflect.Value)(nil)).Elem()
 )
 
 // evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
@@ -661,7 +668,11 @@ func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, a
                s.at(node)
                s.errorf("error calling %s: %s", name, result[1].Interface().(error))
        }
-       return result[0]
+       v := result[0]
+       if v.Type() == reflectValueType {
+               v = v.Interface().(reflect.Value)
+       }
+       return v
 }
 
 // canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
@@ -669,6 +680,8 @@ func canBeNil(typ reflect.Type) bool {
        switch typ.Kind() {
        case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
                return true
+       case reflect.Struct:
+               return typ == reflectValueType
        }
        return false
 }
@@ -682,6 +695,9 @@ func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Valu
                }
                s.errorf("invalid value; expected %s", typ)
        }
+       if typ == reflectValueType && value.Type() != typ {
+               return reflect.ValueOf(value)
+       }
        if typ != nil && !value.Type().AssignableTo(typ) {
                if value.Kind() == reflect.Interface && !value.IsNil() {
                        value = value.Elem()
@@ -743,6 +759,10 @@ func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) refle
                if typ.NumMethod() == 0 {
                        return s.evalEmptyInterface(dot, n)
                }
+       case reflect.Struct:
+               if typ == reflectValueType {
+                       return reflect.ValueOf(s.evalEmptyInterface(dot, n))
+               }
        case reflect.String:
                return s.evalString(typ, n)
        case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
index 3ef065edcfdebefd07065d3ca56d3b9073a10240..709296185030d47d64fb06c51ef2fd1941752054 100644 (file)
@@ -1310,3 +1310,26 @@ func TestMaxExecDepth(t *testing.T) {
                t.Errorf("got error %q; want %q", got, want)
        }
 }
+
+func TestAddrOfIndex(t *testing.T) {
+       // golang.org/issue/14916.
+       // Before index worked on reflect.Values, the .String could not be
+       // found on the (incorrectly unaddressable) V value,
+       // in contrast to range, which worked fine.
+       // Also testing that passing a reflect.Value to tmpl.Execute works.
+       texts := []string{
+               `{{range .}}{{.String}}{{end}}`,
+               `{{with index . 0}}{{.String}}{{end}}`,
+       }
+       for _, text := range texts {
+               tmpl := Must(New("tmpl").Parse(text))
+               var buf bytes.Buffer
+               err := tmpl.Execute(&buf, reflect.ValueOf([]V{{1}}))
+               if err != nil {
+                       t.Fatal("%s: Execute: %v", text, err)
+               }
+               if buf.String() != "<1>" {
+                       t.Fatalf("%s: template output = %q, want %q", text, buf, "<1>")
+               }
+       }
+}
index cd0b82b243d592620a7ba960c3dcbf2c03303850..8d8bc059f02aaddfe880772f3f9a8bd1badc929d 100644 (file)
@@ -21,6 +21,12 @@ import (
 // which the second has type error. In that case, if the second (error)
 // return value evaluates to non-nil during execution, execution terminates and
 // Execute returns that error.
+//
+// When template execution invokes a function with an argument list, that list
+// must be assignable to the function's parameter types. Functions meant to
+// apply to arguments of arbitrary type can use parameters of type interface{} or
+// of type reflect.Value. Similarly, functions meant to return a result of arbitrary
+// type can return interface{} or reflect.Value.
 type FuncMap map[string]interface{}
 
 var builtins = FuncMap{
@@ -144,16 +150,15 @@ func prepareArg(value reflect.Value, argType reflect.Type) (reflect.Value, error
 // index returns the result of indexing its first argument by the following
 // 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 interface{}, indices ...interface{}) (interface{}, error) {
-       v := reflect.ValueOf(item)
+func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) {
+       v := item
        if !v.IsValid() {
-               return nil, fmt.Errorf("index of untyped nil")
+               return reflect.Value{}, fmt.Errorf("index of untyped nil")
        }
-       for _, i := range indices {
-               index := reflect.ValueOf(i)
+       for _, index := range indices {
                var isNil bool
                if v, isNil = indirect(v); isNil {
-                       return nil, fmt.Errorf("index of nil pointer")
+                       return reflect.Value{}, fmt.Errorf("index of nil pointer")
                }
                switch v.Kind() {
                case reflect.Array, reflect.Slice, reflect.String:
@@ -164,18 +169,18 @@ func index(item interface{}, indices ...interface{}) (interface{}, error) {
                        case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
                                x = int64(index.Uint())
                        case reflect.Invalid:
-                               return nil, fmt.Errorf("cannot index slice/array with nil")
+                               return reflect.Value{}, fmt.Errorf("cannot index slice/array with nil")
                        default:
-                               return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type())
+                               return reflect.Value{}, fmt.Errorf("cannot index slice/array with type %s", index.Type())
                        }
                        if x < 0 || x >= int64(v.Len()) {
-                               return nil, fmt.Errorf("index out of range: %d", x)
+                               return reflect.Value{}, fmt.Errorf("index out of range: %d", x)
                        }
                        v = v.Index(int(x))
                case reflect.Map:
                        index, err := prepareArg(index, v.Type().Key())
                        if err != nil {
-                               return nil, err
+                               return reflect.Value{}, err
                        }
                        if x := v.MapIndex(index); x.IsValid() {
                                v = x
@@ -186,10 +191,10 @@ func index(item interface{}, indices ...interface{}) (interface{}, error) {
                        // the loop holds invariant: v.IsValid()
                        panic("unreachable")
                default:
-                       return nil, fmt.Errorf("can't index item of type %s", v.Type())
+                       return reflect.Value{}, fmt.Errorf("can't index item of type %s", v.Type())
                }
        }
-       return v.Interface(), nil
+       return v, nil
 }
 
 // Length
@@ -215,33 +220,32 @@ 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 interface{}, args ...interface{}) (interface{}, error) {
-       v := reflect.ValueOf(fn)
+func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) {
+       v := fn
        if !v.IsValid() {
-               return nil, fmt.Errorf("call of nil")
+               return reflect.Value{}, fmt.Errorf("call of nil")
        }
        typ := v.Type()
        if typ.Kind() != reflect.Func {
-               return nil, fmt.Errorf("non-function of type %s", typ)
+               return reflect.Value{}, fmt.Errorf("non-function of type %s", typ)
        }
        if !goodFunc(typ) {
-               return nil, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut())
+               return reflect.Value{}, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut())
        }
        numIn := typ.NumIn()
        var dddType reflect.Type
        if typ.IsVariadic() {
                if len(args) < numIn-1 {
-                       return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
+                       return reflect.Value{}, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
                }
                dddType = typ.In(numIn - 1).Elem()
        } else {
                if len(args) != numIn {
-                       return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
+                       return reflect.Value{}, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
                }
        }
        argv := make([]reflect.Value, len(args))
-       for i, arg := range args {
-               value := reflect.ValueOf(arg)
+       for i, value := range args {
                // Compute the expected type. Clumsy because of variadics.
                var argType reflect.Type
                if !typ.IsVariadic() || i < numIn-1 {
@@ -252,26 +256,26 @@ func call(fn interface{}, args ...interface{}) (interface{}, error) {
 
                var err error
                if argv[i], err = prepareArg(value, argType); err != nil {
-                       return nil, fmt.Errorf("arg %d: %s", i, err)
+                       return reflect.Value{}, fmt.Errorf("arg %d: %s", i, err)
                }
        }
        result := v.Call(argv)
        if len(result) == 2 && !result[1].IsNil() {
-               return result[0].Interface(), result[1].Interface().(error)
+               return result[0], result[1].Interface().(error)
        }
-       return result[0].Interface(), nil
+       return result[0], nil
 }
 
 // Boolean logic.
 
-func truth(a interface{}) bool {
-       t, _ := IsTrue(a)
+func truth(arg reflect.Value) bool {
+       t, _ := isTrue(arg)
        return t
 }
 
 // and computes the Boolean AND of its arguments, returning
 // the first false argument it encounters, or the last argument.
-func and(arg0 interface{}, args ...interface{}) interface{} {
+func and(arg0 reflect.Value, args ...reflect.Value) reflect.Value {
        if !truth(arg0) {
                return arg0
        }
@@ -286,7 +290,7 @@ func and(arg0 interface{}, args ...interface{}) interface{} {
 
 // or computes the Boolean OR of its arguments, returning
 // the first true argument it encounters, or the last argument.
-func or(arg0 interface{}, args ...interface{}) interface{} {
+func or(arg0 reflect.Value, args ...reflect.Value) reflect.Value {
        if truth(arg0) {
                return arg0
        }
@@ -300,7 +304,7 @@ func or(arg0 interface{}, args ...interface{}) interface{} {
 }
 
 // not returns the Boolean negation of its argument.
-func not(arg interface{}) bool {
+func not(arg reflect.Value) bool {
        return !truth(arg)
 }
 
@@ -345,8 +349,8 @@ func basicKind(v reflect.Value) (kind, error) {
 }
 
 // eq evaluates the comparison a == b || a == c || ...
-func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
-       v1 := reflect.ValueOf(arg1)
+func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
+       v1 := arg1
        k1, err := basicKind(v1)
        if err != nil {
                return false, err
@@ -354,8 +358,7 @@ func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
        if len(arg2) == 0 {
                return false, errNoComparison
        }
-       for _, arg := range arg2 {
-               v2 := reflect.ValueOf(arg)
+       for _, v2 := range arg2 {
                k2, err := basicKind(v2)
                if err != nil {
                        return false, err
@@ -397,20 +400,18 @@ func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
 }
 
 // ne evaluates the comparison a != b.
-func ne(arg1, arg2 interface{}) (bool, error) {
+func ne(arg1, arg2 reflect.Value) (bool, error) {
        // != is the inverse of ==.
        equal, err := eq(arg1, arg2)
        return !equal, err
 }
 
 // lt evaluates the comparison a < b.
-func lt(arg1, arg2 interface{}) (bool, error) {
-       v1 := reflect.ValueOf(arg1)
+func lt(v1, v2 reflect.Value) (bool, error) {
        k1, err := basicKind(v1)
        if err != nil {
                return false, err
        }
-       v2 := reflect.ValueOf(arg2)
        k2, err := basicKind(v2)
        if err != nil {
                return false, err
@@ -446,7 +447,7 @@ func lt(arg1, arg2 interface{}) (bool, error) {
 }
 
 // le evaluates the comparison <= b.
-func le(arg1, arg2 interface{}) (bool, error) {
+func le(arg1, arg2 reflect.Value) (bool, error) {
        // <= is < or ==.
        lessThan, err := lt(arg1, arg2)
        if lessThan || err != nil {
@@ -456,7 +457,7 @@ func le(arg1, arg2 interface{}) (bool, error) {
 }
 
 // gt evaluates the comparison a > b.
-func gt(arg1, arg2 interface{}) (bool, error) {
+func gt(arg1, arg2 reflect.Value) (bool, error) {
        // > is the inverse of <=.
        lessOrEqual, err := le(arg1, arg2)
        if err != nil {
@@ -466,7 +467,7 @@ func gt(arg1, arg2 interface{}) (bool, error) {
 }
 
 // ge evaluates the comparison a >= b.
-func ge(arg1, arg2 interface{}) (bool, error) {
+func ge(arg1, arg2 reflect.Value) (bool, error) {
        // >= is the inverse of <.
        lessThan, err := lt(arg1, arg2)
        if err != nil {