mirror of
https://github.com/golang/go
synced 2024-11-21 20:54:45 -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:
parent
2012290c7e
commit
c3344d61bd
@ -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.
|
||||
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.
|
||||
Method invocations may be chained, but only the last element of
|
||||
the chain may be a method; others must be struct fields:
|
||||
.Field1.Field2.Method
|
||||
Method invocations may be chained and combined with fields
|
||||
to any depth:
|
||||
.Field1.Method1.Field2.Method2
|
||||
Methods can also be evaluated on variables, including chaining:
|
||||
$x.Field1.Method
|
||||
$x.Method1.Field
|
||||
- The name of a niladic function, such as
|
||||
fun
|
||||
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
|
||||
The result is the value of evaluating the 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.)
|
||||
functionName [Argument...]
|
||||
The result is the value of calling the function associated
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Up to the last entry, it must be a field.
|
||||
n := len(ident)
|
||||
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)
|
||||
}
|
||||
|
||||
@ -358,27 +360,25 @@ func isExported(name string) bool {
|
||||
// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
|
||||
// The 'final' argument represents the return value from the preceding
|
||||
// value of the pipeline, if any.
|
||||
// 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).
|
||||
func (s *state) evalField(dot reflect.Value, fieldName string, args []node, final reflect.Value,
|
||||
receiver reflect.Value) reflect.Value {
|
||||
typ := dot.Type()
|
||||
if receiver.IsValid() {
|
||||
receiver, _ = indirect(receiver)
|
||||
// Need to get to a value of type *T to guarantee we see all
|
||||
// methods of T and *T.
|
||||
ptr := receiver
|
||||
if ptr.CanAddr() {
|
||||
ptr = ptr.Addr()
|
||||
}
|
||||
if method, ok := methodByName(ptr.Type(), fieldName); ok {
|
||||
return s.evalCall(dot, ptr, method.Func, fieldName, args, final)
|
||||
}
|
||||
func (s *state) evalField(dot reflect.Value, fieldName string, args []node, final, receiver reflect.Value) reflect.Value {
|
||||
if !receiver.IsValid() {
|
||||
return zero
|
||||
}
|
||||
typ := receiver.Type()
|
||||
receiver, _ = indirect(receiver)
|
||||
// Need to get to a value of type *T to guarantee we see all
|
||||
// methods of T and *T.
|
||||
ptr := receiver
|
||||
if ptr.CanAddr() {
|
||||
ptr = ptr.Addr()
|
||||
}
|
||||
if method, ok := methodByName(ptr.Type(), fieldName); ok {
|
||||
return s.evalCall(dot, ptr, method.Func, fieldName, args, final)
|
||||
}
|
||||
// It's not a method; is it a field of a struct?
|
||||
dot, isNil := indirect(dot)
|
||||
if dot.Kind() == reflect.Struct {
|
||||
field := dot.FieldByName(fieldName)
|
||||
receiver, isNil := indirect(receiver)
|
||||
if receiver.Kind() == reflect.Struct {
|
||||
field := receiver.FieldByName(fieldName)
|
||||
if field.IsValid() {
|
||||
if len(args) > 1 || final.IsValid() {
|
||||
s.errorf("%s is not a method but has arguments", fieldName)
|
||||
@ -391,7 +391,7 @@ receiver reflect.Value) reflect.Value {
|
||||
if isNil {
|
||||
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")
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
// T has lots of interesting pieces to use to test execution.
|
||||
type T struct {
|
||||
// Basics
|
||||
True bool
|
||||
I int
|
||||
U16 uint16
|
||||
X string
|
||||
@ -52,6 +53,7 @@ type U struct {
|
||||
}
|
||||
|
||||
var tVal = &T{
|
||||
True: true,
|
||||
I: 17,
|
||||
U16: 16,
|
||||
X: "x",
|
||||
@ -128,6 +130,18 @@ func (t *T) EPERM(error bool) (bool, os.Error) {
|
||||
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 {
|
||||
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, $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 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.
|
||||
{"pipeline", "-{{.Method0 | .Method2 .U16}}-", "-Method2: 16 M0-", tVal, true},
|
||||
@ -309,7 +332,7 @@ var execTests = []execTest{
|
||||
|
||||
// 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},
|
||||
{"bug0", "{{range .MSIone}}{{if $.Method1 .}}X{{end}}{{end}}", "X", tVal, true},
|
||||
}
|
||||
|
||||
func zeroArgs() string {
|
||||
|
Loading…
Reference in New Issue
Block a user