1
0
mirror of https://github.com/golang/go synced 2024-10-02 08:18:32 -06:00

exp/template: fix bug in argument evaluation.

Must keep dot and the receiver separate - variables broke that symmetry.
Also clean up function invocation and rename "data" to "dot" for clarity.

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/4678048
This commit is contained in:
Rob Pike 2011-07-11 18:06:24 +10:00
parent 3c6a7b3fc5
commit d366c36945
2 changed files with 66 additions and 61 deletions

View File

@ -106,32 +106,32 @@ func (t *Template) ExecuteInSet(wr io.Writer, data interface{}, set *Set) (err o
// Walk functions step through the major pieces of the template structure, // Walk functions step through the major pieces of the template structure,
// generating output as they go. // generating output as they go.
func (s *state) walk(data reflect.Value, n node) { func (s *state) walk(dot reflect.Value, n node) {
switch n := n.(type) { switch n := n.(type) {
case *actionNode: case *actionNode:
s.line = n.line s.line = n.line
defer s.pop(s.mark()) defer s.pop(s.mark())
s.printValue(n, s.evalPipeline(data, n.pipe)) s.printValue(n, s.evalPipeline(dot, n.pipe))
case *ifNode: case *ifNode:
s.line = n.line s.line = n.line
s.walkIfOrWith(nodeIf, data, n.pipe, n.list, n.elseList) s.walkIfOrWith(nodeIf, dot, n.pipe, n.list, n.elseList)
case *listNode: case *listNode:
for _, node := range n.nodes { for _, node := range n.nodes {
s.walk(data, node) s.walk(dot, node)
} }
case *rangeNode: case *rangeNode:
s.line = n.line s.line = n.line
s.walkRange(data, n) s.walkRange(dot, n)
case *templateNode: case *templateNode:
s.line = n.line s.line = n.line
s.walkTemplate(data, n) s.walkTemplate(dot, n)
case *textNode: case *textNode:
if _, err := s.wr.Write(n.text); err != nil { if _, err := s.wr.Write(n.text); err != nil {
s.error(err) s.error(err)
} }
case *withNode: case *withNode:
s.line = n.line s.line = n.line
s.walkIfOrWith(nodeWith, data, n.pipe, n.list, n.elseList) s.walkIfOrWith(nodeWith, dot, n.pipe, n.list, n.elseList)
default: default:
s.errorf("unknown node: %s", n) s.errorf("unknown node: %s", n)
} }
@ -139,9 +139,9 @@ func (s *state) walk(data reflect.Value, n node) {
// walkIfOrWith walks an 'if' or 'with' node. The two control structures // walkIfOrWith walks an 'if' or 'with' node. The two control structures
// are identical in behavior except that 'with' sets dot. // are identical in behavior except that 'with' sets dot.
func (s *state) walkIfOrWith(typ nodeType, data reflect.Value, pipe *pipeNode, list, elseList *listNode) { func (s *state) walkIfOrWith(typ nodeType, dot reflect.Value, pipe *pipeNode, list, elseList *listNode) {
defer s.pop(s.mark()) defer s.pop(s.mark())
val := s.evalPipeline(data, pipe) val := s.evalPipeline(dot, pipe)
truth, ok := isTrue(val) truth, ok := isTrue(val)
if !ok { if !ok {
s.errorf("if/with can't use value of type %T", val.Interface()) s.errorf("if/with can't use value of type %T", val.Interface())
@ -150,10 +150,10 @@ func (s *state) walkIfOrWith(typ nodeType, data reflect.Value, pipe *pipeNode, l
if typ == nodeWith { if typ == nodeWith {
s.walk(val, list) s.walk(val, list)
} else { } else {
s.walk(data, list) s.walk(dot, list)
} }
} else if elseList != nil { } else if elseList != nil {
s.walk(data, elseList) s.walk(dot, elseList)
} }
} }
@ -181,9 +181,9 @@ func isTrue(val reflect.Value) (truth, ok bool) {
return truth, true return truth, true
} }
func (s *state) walkRange(data reflect.Value, r *rangeNode) { func (s *state) walkRange(dot reflect.Value, r *rangeNode) {
defer s.pop(s.mark()) defer s.pop(s.mark())
val, _ := indirect(s.evalPipeline(data, r.pipe)) val, _ := indirect(s.evalPipeline(dot, r.pipe))
switch val.Kind() { switch val.Kind() {
case reflect.Array, reflect.Slice: case reflect.Array, reflect.Slice:
if val.Len() == 0 { if val.Len() == 0 {
@ -215,13 +215,13 @@ func (s *state) walkRange(data reflect.Value, r *rangeNode) {
s.errorf("range can't iterate over value of type %T", val.Interface()) s.errorf("range can't iterate over value of type %T", val.Interface())
} }
if r.elseList != nil { if r.elseList != nil {
s.walk(data, r.elseList) s.walk(dot, r.elseList)
} }
} }
func (s *state) walkTemplate(data reflect.Value, t *templateNode) { func (s *state) walkTemplate(dot reflect.Value, t *templateNode) {
// Can't use evalArg because there are two types we expect. // Can't use evalArg because there are two types we expect.
arg := s.evalEmptyInterface(data, t.name) arg := s.evalEmptyInterface(dot, t.name)
if !arg.IsValid() { if !arg.IsValid() {
s.errorf("invalid value in template invocation; expected string or *Template") s.errorf("invalid value in template invocation; expected string or *Template")
} }
@ -243,12 +243,12 @@ func (s *state) walkTemplate(data reflect.Value, t *templateNode) {
} }
} }
defer s.pop(s.mark()) defer s.pop(s.mark())
data = s.evalPipeline(data, t.pipe) dot = s.evalPipeline(dot, t.pipe)
newState := *s newState := *s
newState.tmpl = tmpl newState.tmpl = tmpl
// No dynamic scoping: template invocations inherit no variables. // No dynamic scoping: template invocations inherit no variables.
newState.vars = []variable{{"$", data}} newState.vars = []variable{{"$", dot}}
newState.walk(data, tmpl.root) newState.walk(dot, tmpl.root)
} }
// Eval functions evaluate pipelines, commands, and their elements and extract // Eval functions evaluate pipelines, commands, and their elements and extract
@ -259,12 +259,12 @@ func (s *state) walkTemplate(data reflect.Value, t *templateNode) {
// pipeline has a variable declaration, the variable will be pushed on the // pipeline has a variable declaration, the variable will be pushed on the
// stack. Callers should therefore pop the stack after they are finished // stack. Callers should therefore pop the stack after they are finished
// executing commands depending on the pipeline value. // executing commands depending on the pipeline value.
func (s *state) evalPipeline(data reflect.Value, pipe *pipeNode) (value reflect.Value) { func (s *state) evalPipeline(dot reflect.Value, pipe *pipeNode) (value reflect.Value) {
if pipe == nil { if pipe == nil {
return return
} }
for _, cmd := range pipe.cmds { for _, cmd := range pipe.cmds {
value = s.evalCommand(data, cmd, value) // previous value is this one's final arg. value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg.
// If the object has type interface{}, dig down one level to the thing inside. // If the object has type interface{}, dig down one level to the thing inside.
if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 { if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 {
value = reflect.ValueOf(value.Interface()) // lovely! value = reflect.ValueOf(value.Interface()) // lovely!
@ -282,23 +282,23 @@ func (s *state) notAFunction(args []node, final reflect.Value) {
} }
} }
func (s *state) evalCommand(data reflect.Value, cmd *commandNode, final reflect.Value) reflect.Value { func (s *state) evalCommand(dot reflect.Value, cmd *commandNode, final reflect.Value) reflect.Value {
firstWord := cmd.args[0] firstWord := cmd.args[0]
switch n := firstWord.(type) { switch n := firstWord.(type) {
case *fieldNode: case *fieldNode:
return s.evalFieldNode(data, n, cmd.args, final) return s.evalFieldNode(dot, n, cmd.args, final)
case *identifierNode: case *identifierNode:
// Must be a function. // Must be a function.
return s.evalFunction(data, n.ident, cmd.args, final) return s.evalFunction(dot, n.ident, cmd.args, final)
case *variableNode: case *variableNode:
return s.evalVariableNode(n, cmd.args, final) return s.evalVariableNode(dot, n, cmd.args, final)
} }
s.notAFunction(cmd.args, final) s.notAFunction(cmd.args, final)
switch word := firstWord.(type) { switch word := firstWord.(type) {
case *boolNode: case *boolNode:
return reflect.ValueOf(word.true) return reflect.ValueOf(word.true)
case *dotNode: case *dotNode:
return data return dot
case *numberNode: case *numberNode:
// These are ideal constants but we don't know the type // These are ideal constants but we don't know the type
// and we have no context. (If it was a method argument, // and we have no context. (If it was a method argument,
@ -320,35 +320,35 @@ func (s *state) evalCommand(data reflect.Value, cmd *commandNode, final reflect.
panic("not reached") panic("not reached")
} }
func (s *state) evalFieldNode(data reflect.Value, field *fieldNode, args []node, final reflect.Value) reflect.Value { func (s *state) evalFieldNode(dot reflect.Value, field *fieldNode, args []node, final reflect.Value) reflect.Value {
return s.evalFieldChain(data, field.ident, args, final) return s.evalFieldChain(dot, dot, field.ident, args, final)
} }
func (s *state) evalVariableNode(v *variableNode, args []node, final reflect.Value) reflect.Value { func (s *state) evalVariableNode(dot reflect.Value, v *variableNode, args []node, final reflect.Value) reflect.Value {
// $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields. // $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields.
data := s.varValue(v.ident[0]) value := s.varValue(v.ident[0])
if len(v.ident) == 1 { if len(v.ident) == 1 {
return data return value
} }
return s.evalFieldChain(data, v.ident[1:], args, final) return s.evalFieldChain(dot, value, v.ident[1:], args, final)
} }
func (s *state) evalFieldChain(data reflect.Value, ident []string, args []node, final reflect.Value) reflect.Value { func (s *state) evalFieldChain(dot, receiver reflect.Value, ident []string, args []node, final reflect.Value) reflect.Value {
// Up to the last entry, it must be a field. // Up to the last entry, it must be a field.
n := len(ident) n := len(ident)
for i := 0; i < n-1; i++ { for i := 0; i < n-1; i++ {
data = s.evalField(data, ident[i], nil, zero, false) dot = s.evalField(dot, ident[i], nil, zero, zero)
} }
// Now it can be a field or method and if a method, gets arguments. // Now it can be a field or method and if a method, gets arguments.
return s.evalField(data, ident[n-1], args, final, true) return s.evalField(dot, ident[n-1], args, final, receiver)
} }
func (s *state) evalFunction(data reflect.Value, name string, args []node, final reflect.Value) reflect.Value { func (s *state) evalFunction(dot reflect.Value, name string, args []node, final reflect.Value) reflect.Value {
function, ok := findFunction(name, s.tmpl, s.set) function, ok := findFunction(name, s.tmpl, s.set)
if !ok { if !ok {
s.errorf("%q is not a defined function", name) s.errorf("%q is not a defined function", name)
} }
return s.evalCall(data, function, name, false, args, final) return s.evalCall(dot, zero, function, name, args, final)
} }
// Is this an exported - upper case - name? // Is this an exported - upper case - name?
@ -362,25 +362,25 @@ func isExported(name string) bool {
// value of the pipeline, if any. // value of the pipeline, if any.
// If we're in a chain, such as (.X.Y.Z), .X and .Y cannot be methods; // If we're in a chain, such as (.X.Y.Z), .X and .Y cannot be methods;
// canBeMethod will be true only for the last element of such chains (here .Z). // canBeMethod will be true only for the last element of such chains (here .Z).
func (s *state) evalField(data reflect.Value, fieldName string, args []node, final reflect.Value, func (s *state) evalField(dot reflect.Value, fieldName string, args []node, final reflect.Value,
canBeMethod bool) reflect.Value { receiver reflect.Value) reflect.Value {
typ := data.Type() typ := dot.Type()
var isNil bool if receiver.IsValid() {
data, isNil = indirect(data) receiver, _ = indirect(receiver)
if canBeMethod {
// Need to get to a value of type *T to guarantee we see all // Need to get to a value of type *T to guarantee we see all
// methods of T and *T. // methods of T and *T.
ptr := data ptr := receiver
if ptr.CanAddr() { if ptr.CanAddr() {
ptr = ptr.Addr() ptr = ptr.Addr()
} }
if method, ok := methodByName(ptr.Type(), fieldName); ok { if method, ok := methodByName(ptr.Type(), fieldName); ok {
return s.evalCall(ptr, method.Func, fieldName, true, args, final) return s.evalCall(dot, ptr, method.Func, fieldName, args, final)
} }
} }
// It's not a method; is it a field of a struct? // It's not a method; is it a field of a struct?
if data.Kind() == reflect.Struct { dot, isNil := indirect(dot)
field := data.FieldByName(fieldName) if dot.Kind() == reflect.Struct {
field := dot.FieldByName(fieldName)
if field.IsValid() { if field.IsValid() {
if len(args) > 1 || final.IsValid() { if len(args) > 1 || final.IsValid() {
s.errorf("%s is not a method but has arguments", fieldName) s.errorf("%s is not a method but has arguments", fieldName)
@ -411,8 +411,9 @@ var (
osErrorType = reflect.TypeOf(new(os.Error)).Elem() osErrorType = reflect.TypeOf(new(os.Error)).Elem()
) )
func (s *state) evalCall(v, fun reflect.Value, name string, isMethod bool, args []node, final reflect.Value) reflect.Value { func (s *state) evalCall(dot, receiver, fun reflect.Value, name string, args []node, final reflect.Value) reflect.Value {
typ := fun.Type() typ := fun.Type()
isMethod := receiver.IsValid()
if !isMethod && len(args) > 0 { // Args will be nil if it's a niladic call in an argument list if !isMethod && len(args) > 0 { // Args will be nil if it's a niladic call in an argument list
args = args[1:] // first arg is name of function; not used in call. args = args[1:] // first arg is name of function; not used in call.
} }
@ -437,18 +438,18 @@ func (s *state) evalCall(v, fun reflect.Value, name string, isMethod bool, args
// First arg is the receiver. // First arg is the receiver.
i := 0 i := 0
if isMethod { if isMethod {
argv[0] = v argv[0] = receiver
i++ i++
} }
// Others must be evaluated. Fixed args first. // Others must be evaluated. Fixed args first.
for ; i < numFixed; i++ { for ; i < numFixed; i++ {
argv[i] = s.evalArg(v, typ.In(i), args[i]) argv[i] = s.evalArg(dot, typ.In(i), args[i])
} }
// And now the ... args. // And now the ... args.
if typ.IsVariadic() { if typ.IsVariadic() {
argType := typ.In(typ.NumIn() - 1).Elem() // Argument is a slice. argType := typ.In(typ.NumIn() - 1).Elem() // Argument is a slice.
for ; i < len(args); i++ { for ; i < len(args); i++ {
argv[i] = s.evalArg(v, argType, args[i]) argv[i] = s.evalArg(dot, argType, args[i])
} }
} }
// Add final value if necessary. // Add final value if necessary.
@ -474,14 +475,14 @@ func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Valu
return value return value
} }
func (s *state) evalArg(data reflect.Value, typ reflect.Type, n node) reflect.Value { func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n node) reflect.Value {
switch arg := n.(type) { switch arg := n.(type) {
case *dotNode: case *dotNode:
return s.validateType(data, typ) return s.validateType(dot, typ)
case *fieldNode: case *fieldNode:
return s.validateType(s.evalFieldNode(data, arg, []node{n}, zero), typ) return s.validateType(s.evalFieldNode(dot, arg, []node{n}, zero), typ)
case *variableNode: case *variableNode:
return s.validateType(s.evalVariableNode(arg, nil, zero), typ) return s.validateType(s.evalVariableNode(dot, arg, nil, zero), typ)
} }
switch typ.Kind() { switch typ.Kind() {
case reflect.Bool: case reflect.Bool:
@ -494,7 +495,7 @@ func (s *state) evalArg(data reflect.Value, typ reflect.Type, n node) reflect.Va
return s.evalInteger(typ, n) return s.evalInteger(typ, n)
case reflect.Interface: case reflect.Interface:
if typ.NumMethod() == 0 { if typ.NumMethod() == 0 {
return s.evalEmptyInterface(data, n) return s.evalEmptyInterface(dot, n)
} }
case reflect.String: case reflect.String:
return s.evalString(typ, n) return s.evalString(typ, n)
@ -565,16 +566,16 @@ func (s *state) evalComplex(typ reflect.Type, n node) reflect.Value {
panic("not reached") panic("not reached")
} }
func (s *state) evalEmptyInterface(data reflect.Value, n node) reflect.Value { func (s *state) evalEmptyInterface(dot reflect.Value, n node) reflect.Value {
switch n := n.(type) { switch n := n.(type) {
case *boolNode: case *boolNode:
return reflect.ValueOf(n.true) return reflect.ValueOf(n.true)
case *dotNode: case *dotNode:
return data return dot
case *fieldNode: case *fieldNode:
return s.evalFieldNode(data, n, nil, zero) return s.evalFieldNode(dot, n, nil, zero)
case *identifierNode: case *identifierNode:
return s.evalFunction(data, n.ident, nil, zero) return s.evalFunction(dot, n.ident, nil, zero)
case *numberNode: case *numberNode:
if n.isComplex { if n.isComplex {
return reflect.ValueOf(n.complex128) return reflect.ValueOf(n.complex128)
@ -591,7 +592,7 @@ func (s *state) evalEmptyInterface(data reflect.Value, n node) reflect.Value {
case *stringNode: case *stringNode:
return reflect.ValueOf(n.text) return reflect.ValueOf(n.text)
case *variableNode: case *variableNode:
return s.evalVariableNode(n, nil, zero) return s.evalVariableNode(dot, n, nil, zero)
} }
s.errorf("can't handle assignment of %s to empty interface argument", n) s.errorf("can't handle assignment of %s to empty interface argument", n)
panic("not reached") panic("not reached")

View File

@ -285,6 +285,10 @@ var execTests = []execTest{
// Error handling. // Error handling.
{"error method, error", "{{.EPERM true}}", "", tVal, false}, {"error method, error", "{{.EPERM true}}", "", tVal, false},
{"error method, no error", "{{.EPERM false}}", "false", tVal, true}, {"error method, no error", "{{.EPERM false}}", "false", tVal, true},
// Fixed bugs.
// Must separate dot and receiver; otherwise args are evaluated with dot set to variable.
{"problem", "{{range .MSIone}}-{{if $.Method1 .}}X{{end}}{{end}}-", "-X-", tVal, true},
} }
func zeroArgs() string { func zeroArgs() string {