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:
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.
|
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
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user