1
0
mirror of https://github.com/golang/go synced 2024-11-18 09:54:57 -07:00

text/template: add support for reflect.Value args, results in funcs

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>
This commit is contained in:
Russ Cox 2016-10-19 09:11:16 -04:00
parent d2aa8601b5
commit 5378dd7768
3 changed files with 88 additions and 44 deletions

View 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:

View 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>")
}
}
}

View 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 {