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

exp/template: allow niladic methods inside chained field references.

Also really fix the bug about dot vs. receivers.

R=rsc, r
CC=golang-dev
https://golang.org/cl/4705047
This commit is contained in:
Rob Pike 2011-07-14 07:52:07 +10:00
parent 2012290c7e
commit c3344d61bd
3 changed files with 55 additions and 29 deletions

View File

@ -105,11 +105,11 @@ An argument is a simple value, denoted by one of the following.
any type) or two return values, the second of which is an os.Error. any type) or two return values, the second of which is an os.Error.
If it has two and the returned error is non-nil, execution terminates If it has two and the returned error is non-nil, execution terminates
and an error is returned to the caller as the value of Execute. and an error is returned to the caller as the value of Execute.
Method invocations may be chained, but only the last element of Method invocations may be chained and combined with fields
the chain may be a method; others must be struct fields: to any depth:
.Field1.Field2.Method .Field1.Method1.Field2.Method2
Methods can also be evaluated on variables, including chaining: Methods can also be evaluated on variables, including chaining:
$x.Field1.Method $x.Method1.Field
- The name of a niladic function, such as - The name of a niladic function, such as
fun fun
The result is the value of invoking the function, fun(). The return The result is the value of invoking the function, fun(). The return
@ -125,7 +125,10 @@ value (argument) or a function or method call, possibly with multiple arguments:
Argument Argument
The result is the value of evaluating the argument. The result is the value of evaluating the argument.
.Method [Argument...] .Method [Argument...]
The result is the value of calling the method with the arguments: The method can be alone or the last element of a chain but,
unlike methods in the middle of a chain, it can take arguments.
The result is the value of calling the method with the
arguments:
dot.Method(Argument1, etc.) dot.Method(Argument1, etc.)
functionName [Argument...] functionName [Argument...]
The result is the value of calling the function associated The result is the value of calling the function associated

View File

@ -331,13 +331,15 @@ func (s *state) evalVariableNode(dot reflect.Value, v *variableNode, args []node
return s.evalFieldChain(dot, value, v.ident[1:], args, final) return s.evalFieldChain(dot, value, v.ident[1:], args, final)
} }
// evalFieldChain evaluates .X.Y.Z possibly followed by arguments.
// dot is the environment in which to evaluate arguments, while
// receiver is the value being walked along the chain.
func (s *state) evalFieldChain(dot, receiver 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.
n := len(ident) n := len(ident)
for i := 0; i < n-1; i++ { for i := 0; i < n-1; i++ {
dot = s.evalField(dot, ident[i], nil, zero, zero) receiver = s.evalField(dot, ident[i], args[:1], zero, receiver)
} }
// Now it can be a field or method and if a method, gets arguments. // Now if it's a method, it gets the arguments.
return s.evalField(dot, ident[n-1], args, final, receiver) return s.evalField(dot, ident[n-1], args, final, receiver)
} }
@ -358,27 +360,25 @@ func isExported(name string) bool {
// evalField evaluates an expression like (.Field) or (.Field arg1 arg2). // evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
// The 'final' argument represents the return value from the preceding // The 'final' argument represents the return value from the preceding
// 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; func (s *state) evalField(dot reflect.Value, fieldName string, args []node, final, receiver reflect.Value) reflect.Value {
// canBeMethod will be true only for the last element of such chains (here .Z). if !receiver.IsValid() {
func (s *state) evalField(dot reflect.Value, fieldName string, args []node, final reflect.Value, return zero
receiver reflect.Value) reflect.Value { }
typ := dot.Type() typ := receiver.Type()
if receiver.IsValid() { receiver, _ = indirect(receiver)
receiver, _ = indirect(receiver) // 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 := receiver
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(dot, ptr, method.Func, fieldName, 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?
dot, isNil := indirect(dot) receiver, isNil := indirect(receiver)
if dot.Kind() == reflect.Struct { if receiver.Kind() == reflect.Struct {
field := dot.FieldByName(fieldName) field := receiver.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)
@ -391,7 +391,7 @@ receiver reflect.Value) reflect.Value {
if isNil { if isNil {
s.errorf("nil pointer evaluating %s.%s", typ, fieldName) s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
} }
s.errorf("can't handle evaluation of field %s in type %s", fieldName, typ) s.errorf("can't evaluate field %s in type %s", fieldName, typ)
panic("not reached") panic("not reached")
} }

View File

@ -17,6 +17,7 @@ import (
// T has lots of interesting pieces to use to test execution. // T has lots of interesting pieces to use to test execution.
type T struct { type T struct {
// Basics // Basics
True bool
I int I int
U16 uint16 U16 uint16
X string X string
@ -52,6 +53,7 @@ type U struct {
} }
var tVal = &T{ var tVal = &T{
True: true,
I: 17, I: 17,
U16: 16, U16: 16,
X: "x", X: "x",
@ -128,6 +130,18 @@ func (t *T) EPERM(error bool) (bool, os.Error) {
return false, nil return false, nil
} }
// A few methods to test chaining.
func (t *T) GetU() *U {
return t.U
}
func (u *U) TrueFalse(b bool) string {
if b {
return "true"
}
return ""
}
func typeOf(arg interface{}) string { func typeOf(arg interface{}) string {
return fmt.Sprintf("%T", arg) return fmt.Sprintf("%T", arg)
} }
@ -211,6 +225,15 @@ var execTests = []execTest{
{".Method2(.U16, `str`)", "-{{.Method2 .U16 `str`}}-", "-Method2: 16 str-", tVal, true}, {".Method2(.U16, `str`)", "-{{.Method2 .U16 `str`}}-", "-Method2: 16 str-", tVal, true},
{".Method2(.U16, $x)", "{{if $x := .X}}-{{.Method2 .U16 $x}}{{end}}-", "-Method2: 16 x-", tVal, true}, {".Method2(.U16, $x)", "{{if $x := .X}}-{{.Method2 .U16 $x}}{{end}}-", "-Method2: 16 x-", tVal, true},
{"method on var", "{{if $x := .}}-{{$x.Method2 .U16 $x.X}}{{end}}-", "-Method2: 16 x-", tVal, true}, {"method on var", "{{if $x := .}}-{{$x.Method2 .U16 $x.X}}{{end}}-", "-Method2: 16 x-", tVal, true},
{"method on chained var",
"{{range .MSIone}}{{if $.U.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
"true", tVal, true},
{"chained method",
"{{range .MSIone}}{{if $.GetU.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
"true", tVal, true},
{"chained method on variable",
"{{with $x := .}}{{with .SI}}{{$.GetU.TrueFalse $.True}}{{end}}{{end}}",
"true", tVal, true},
// Pipelines. // Pipelines.
{"pipeline", "-{{.Method0 | .Method2 .U16}}-", "-Method2: 16 M0-", tVal, true}, {"pipeline", "-{{.Method0 | .Method2 .U16}}-", "-Method2: 16 M0-", tVal, true},
@ -309,7 +332,7 @@ var execTests = []execTest{
// Fixed bugs. // Fixed bugs.
// Must separate dot and receiver; otherwise args are evaluated with dot set to variable. // 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}, {"bug0", "{{range .MSIone}}{{if $.Method1 .}}X{{end}}{{end}}", "X", tVal, true},
} }
func zeroArgs() string { func zeroArgs() string {