mirror of
https://github.com/golang/go
synced 2024-09-30 10:28:33 -06:00
text/template: reintroduce implicit indirect of interface values in builtin funcs
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>
This commit is contained in:
parent
fabb4115ed
commit
39e3cbfff6
@ -874,6 +874,20 @@ func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
|
|||||||
return v, false
|
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
|
// printValue writes the textual representation of the value to the output of
|
||||||
// the template.
|
// the template.
|
||||||
func (s *state) printValue(n parse.Node, v reflect.Value) {
|
func (s *state) printValue(n parse.Node, v reflect.Value) {
|
||||||
|
@ -1329,7 +1329,80 @@ func TestAddrOfIndex(t *testing.T) {
|
|||||||
t.Fatalf("%s: Execute: %v", text, err)
|
t.Fatalf("%s: Execute: %v", text, err)
|
||||||
}
|
}
|
||||||
if buf.String() != "<1>" {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
// 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.
|
// indexed item must be a map, slice, or array.
|
||||||
func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) {
|
func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) {
|
||||||
v := item
|
v := indirectInterface(item)
|
||||||
if !v.IsValid() {
|
if !v.IsValid() {
|
||||||
return reflect.Value{}, fmt.Errorf("index of untyped nil")
|
return reflect.Value{}, fmt.Errorf("index of untyped nil")
|
||||||
}
|
}
|
||||||
for _, index := range indices {
|
for _, i := range indices {
|
||||||
|
index := indirectInterface(i)
|
||||||
var isNil bool
|
var isNil bool
|
||||||
if v, isNil = indirect(v); isNil {
|
if v, isNil = indirect(v); isNil {
|
||||||
return reflect.Value{}, fmt.Errorf("index of nil pointer")
|
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.
|
// 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.
|
// 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) {
|
func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) {
|
||||||
v := fn
|
v := indirectInterface(fn)
|
||||||
if !v.IsValid() {
|
if !v.IsValid() {
|
||||||
return reflect.Value{}, fmt.Errorf("call of nil")
|
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))
|
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.
|
// Compute the expected type. Clumsy because of variadics.
|
||||||
var argType reflect.Type
|
var argType reflect.Type
|
||||||
if !typ.IsVariadic() || i < numIn-1 {
|
if !typ.IsVariadic() || i < numIn-1 {
|
||||||
@ -269,7 +271,7 @@ func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) {
|
|||||||
// Boolean logic.
|
// Boolean logic.
|
||||||
|
|
||||||
func truth(arg reflect.Value) bool {
|
func truth(arg reflect.Value) bool {
|
||||||
t, _ := isTrue(arg)
|
t, _ := isTrue(indirectInterface(arg))
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,7 +352,7 @@ func basicKind(v reflect.Value) (kind, error) {
|
|||||||
|
|
||||||
// eq evaluates the comparison a == b || a == c || ...
|
// eq evaluates the comparison a == b || a == c || ...
|
||||||
func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
|
func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
|
||||||
v1 := arg1
|
v1 := indirectInterface(arg1)
|
||||||
k1, err := basicKind(v1)
|
k1, err := basicKind(v1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -358,7 +360,8 @@ func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
|
|||||||
if len(arg2) == 0 {
|
if len(arg2) == 0 {
|
||||||
return false, errNoComparison
|
return false, errNoComparison
|
||||||
}
|
}
|
||||||
for _, v2 := range arg2 {
|
for _, arg := range arg2 {
|
||||||
|
v2 := indirectInterface(arg)
|
||||||
k2, err := basicKind(v2)
|
k2, err := basicKind(v2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -407,11 +410,13 @@ func ne(arg1, arg2 reflect.Value) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// lt evaluates the comparison a < b.
|
// 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)
|
k1, err := basicKind(v1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
v2 := indirectInterface(arg2)
|
||||||
k2, err := basicKind(v2)
|
k2, err := basicKind(v2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
Loading…
Reference in New Issue
Block a user