]> Cypherpunks repositories - gostls13.git/commitdiff
text/template: add 'nil' as a keyword in the language
authorRob Pike <r@golang.org>
Thu, 9 Aug 2012 03:02:19 +0000 (20:02 -0700)
committerRob Pike <r@golang.org>
Thu, 9 Aug 2012 03:02:19 +0000 (20:02 -0700)
The keyword reprents an untyped nil and is useful for
passing nil values to methods and functions. The
nil will be promoted to the appropriate type when
used; if a type cannot be assigned, an error results.

R=rsc, dsymonds
CC=golang-dev
https://golang.org/cl/6459056

src/pkg/text/template/doc.go
src/pkg/text/template/exec.go
src/pkg/text/template/exec_test.go
src/pkg/text/template/funcs.go
src/pkg/text/template/parse/lex.go
src/pkg/text/template/parse/lex_test.go
src/pkg/text/template/parse/node.go
src/pkg/text/template/parse/parse.go
src/pkg/text/template/parse/parse_test.go

index 4a1682d97a347a184ef44ae7cb5e3eabca9d912e..3e4c66a276159dcef1d53b5ce121cd4c3f1d5569 100644 (file)
@@ -100,6 +100,7 @@ An argument is a simple value, denoted by one of the following.
        - A boolean, string, character, integer, floating-point, imaginary
          or complex constant in Go syntax. These behave like Go's untyped
          constants, although raw strings may not span newlines.
+       - The keyword nil, representing an untyped Go nil.
        - The character '.' (period):
                .
          The result is the value of dot.
index 5fad5ccecbd87141a7e46b92ead74506085984b1..a0413514487da0dea8901d96fd3d9bd8b3846f89 100644 (file)
@@ -327,6 +327,8 @@ func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final ref
                return reflect.ValueOf(word.True)
        case *parse.DotNode:
                return dot
+       case *parse.NilNode:
+               s.errorf("nil is not a command")
        case *parse.NumberNode:
                return s.idealConstant(word)
        case *parse.StringNode:
@@ -507,17 +509,23 @@ func (s *state) evalCall(dot, fun reflect.Value, name string, args []parse.Node,
        return result[0]
 }
 
+// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
+func canBeNil(typ reflect.Type) bool {
+       switch typ.Kind() {
+       case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
+               return true
+       }
+       return false
+}
+
 // validateType guarantees that the value is valid and assignable to the type.
 func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Value {
        if !value.IsValid() {
-               switch typ.Kind() {
-               case reflect.Interface, reflect.Ptr, reflect.Chan, reflect.Map, reflect.Slice, reflect.Func:
+               if canBeNil(typ) {
                        // An untyped nil interface{}. Accept as a proper nil value.
-                       // TODO: Can we delete the other types in this list? Should we?
-                       value = reflect.Zero(typ)
-               default:
-                       s.errorf("invalid value; expected %s", typ)
+                       return reflect.Zero(typ)
                }
+               s.errorf("invalid value; expected %s", typ)
        }
        if !value.Type().AssignableTo(typ) {
                if value.Kind() == reflect.Interface && !value.IsNil() {
@@ -547,6 +555,11 @@ func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) refle
        switch arg := n.(type) {
        case *parse.DotNode:
                return s.validateType(dot, typ)
+       case *parse.NilNode:
+               if canBeNil(typ) {
+                       return reflect.Zero(typ)
+               }
+               s.errorf("cannot assign nil to %s", typ)
        case *parse.FieldNode:
                return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, zero), typ)
        case *parse.VariableNode:
@@ -644,6 +657,9 @@ func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Valu
                return s.evalFieldNode(dot, n, nil, zero)
        case *parse.IdentifierNode:
                return s.evalFunction(dot, n.Ident, nil, zero)
+       case *parse.NilNode:
+               // NilNode is handled in evalArg, the only place that calls here.
+               s.errorf("evalEmptyInterface: nil (can't happen)")
        case *parse.NumberNode:
                return s.idealConstant(n)
        case *parse.StringNode:
index 4efe2d1b3820bc9cdeaa1ae7972574885f8e987c..95e0592df8e1847e2dbf560814c2a4af1a9e81dc 100644 (file)
@@ -63,6 +63,7 @@ type T struct {
        BinaryFunc      func(string, string) string
        VariadicFunc    func(...string) string
        VariadicFuncInt func(int, ...string) string
+       NilOKFunc       func(*int) bool
        // Template to test evaluation of templates.
        Tmpl *Template
        // Unexported field; cannot be accessed by template.
@@ -127,6 +128,7 @@ var tVal = &T{
        BinaryFunc:        func(a, b string) string { return fmt.Sprintf("[%s=%s]", a, b) },
        VariadicFunc:      func(s ...string) string { return fmt.Sprint("<", strings.Join(s, "+"), ">") },
        VariadicFuncInt:   func(a int, s ...string) string { return fmt.Sprint(a, "=<", strings.Join(s, "+"), ">") },
+       NilOKFunc:         func(s *int) bool { return s == nil },
        Tmpl:              Must(New("x").Parse("test template")), // "x" is the value of .X
 }
 
@@ -222,6 +224,7 @@ var execTests = []execTest{
        // Trivial cases.
        {"empty", "", "", nil, true},
        {"text", "some text", "some text", nil, true},
+       {"nil action", "{{nil}}", "", nil, false},
 
        // Ideal constants.
        {"ideal int", "{{typeOf 3}}", "int", 0, true},
@@ -230,6 +233,7 @@ var execTests = []execTest{
        {"ideal complex", "{{typeOf 1i}}", "complex128", 0, true},
        {"ideal int", "{{typeOf " + bigInt + "}}", "int", 0, true},
        {"ideal too big", "{{typeOf " + bigUint + "}}", "", 0, false},
+       {"ideal nil without type", "{{nil}}", "", 0, false},
 
        // Fields of structs.
        {".X", "-{{.X}}-", "-x-", tVal, true},
@@ -295,7 +299,8 @@ var execTests = []execTest{
        {".Method2(3, .X)", "-{{.Method2 3 .X}}-", "-Method2: 3 x-", 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},
-       {".Method3(nil)", "-{{.Method3 .MXI.unset}}-", "-Method3: <nil>-", tVal, true},
+       {".Method3(nil constant)", "-{{.Method3 nil}}-", "-Method3: <nil>-", tVal, true},
+       {".Method3(nil value)", "-{{.Method3 .MXI.unset}}-", "-Method3: <nil>-", 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}}",
@@ -306,6 +311,8 @@ var execTests = []execTest{
        {"chained method on variable",
                "{{with $x := .}}{{with .SI}}{{$.GetU.TrueFalse $.True}}{{end}}{{end}}",
                "true", tVal, true},
+       {".NilOKFunc not nil", "{{call .NilOKFunc .PI}}", "false", tVal, true},
+       {".NilOKFunc nil", "{{call .NilOKFunc nil}}", "true", tVal, true},
 
        // Function call builtin.
        {".BinaryFunc", "{{call .BinaryFunc `1` `2`}}", "[1=2]", tVal, true},
@@ -324,6 +331,7 @@ var execTests = []execTest{
        {".VariadicFuncBad0", "{{call .VariadicFunc 3}}", "", tVal, false},
        {".VariadicFuncIntBad0", "{{call .VariadicFuncInt}}", "", tVal, false},
        {".VariadicFuncIntBad`", "{{call .VariadicFuncInt `x`}}", "", tVal, false},
+       {".VariadicFuncNilBad", "{{call .VariadicFunc nil}}", "", tVal, false},
 
        // Pipelines.
        {"pipeline", "-{{.Method0 | .Method2 .U16}}-", "-Method2: 16 M0-", tVal, true},
@@ -332,6 +340,7 @@ var execTests = []execTest{
        // If.
        {"if true", "{{if true}}TRUE{{end}}", "TRUE", tVal, true},
        {"if false", "{{if false}}TRUE{{else}}FALSE{{end}}", "FALSE", tVal, true},
+       {"if nil", "{{if nil}}TRUE{{end}}", "", tVal, false},
        {"if 1", "{{if 1}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true},
        {"if 0", "{{if 0}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true},
        {"if 1.5", "{{if 1.5}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true},
@@ -351,7 +360,8 @@ var execTests = []execTest{
 
        // Print etc.
        {"print", `{{print "hello, print"}}`, "hello, print", tVal, true},
-       {"print", `{{print 1 2 3}}`, "1 2 3", tVal, true},
+       {"print 123", `{{print 1 2 3}}`, "1 2 3", tVal, true},
+       {"print nil", `{{print nil}}`, "<nil>", tVal, true},
        {"println", `{{println 1 2 3}}`, "1 2 3\n", tVal, true},
        {"printf int", `{{printf "%04x" 127}}`, "007f", tVal, true},
        {"printf float", `{{printf "%g" 3.5}}`, "3.5", tVal, true},
@@ -391,6 +401,7 @@ var execTests = []execTest{
        {"map[one]", "{{index .MSI `one`}}", "1", tVal, true},
        {"map[two]", "{{index .MSI `two`}}", "2", tVal, true},
        {"map[NO]", "{{index .MSI `XXX`}}", "0", tVal, true},
+       {"map[nil]", "{{index .MSI nil}}", "0", tVal, true},
        {"map[WRONG]", "{{index .MSI 10}}", "", tVal, false},
        {"double index", "{{index .SMSI 1 `eleven`}}", "11", tVal, true},
 
index e6fa0fb5f2a24f00836c15ef697fb2beef147dbf..d3ecb51e37aa62da930b0a4d7ae7f4a68b7603c3 100644 (file)
@@ -122,6 +122,9 @@ func index(item interface{}, indices ...interface{}) (interface{}, error) {
                        }
                        v = v.Index(int(x))
                case reflect.Map:
+                       if !index.IsValid() {
+                               index = reflect.Zero(v.Type().Key())
+                       }
                        if !index.Type().AssignableTo(v.Type().Key()) {
                                return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type())
                        }
@@ -187,10 +190,13 @@ func call(fn interface{}, args ...interface{}) (interface{}, error) {
                } else {
                        argType = dddType
                }
+               if !value.IsValid() && canBeNil(argType) {
+                       value = reflect.Zero(argType)
+               }
                if !value.Type().AssignableTo(argType) {
                        return nil, fmt.Errorf("arg %d has type %s; should be %s", i, value.Type(), argType)
                }
-               argv[i] = reflect.ValueOf(arg)
+               argv[i] = value
        }
        result := v.Call(argv)
        if len(result) == 2 {
index 1334b3033b98d9c81ffd668249c5300f65a53fdc..443fb86423401ad559895efdfc1ace503ad08d8c 100644 (file)
@@ -60,6 +60,7 @@ const (
        itemElse     // else keyword
        itemEnd      // end keyword
        itemIf       // if keyword
+       itemNil      // the untyped nil constant, easiest to treat as a keyword
        itemRange    // range keyword
        itemTemplate // template keyword
        itemWith     // with keyword
@@ -89,6 +90,7 @@ var itemName = map[itemType]string{
        itemElse:     "else",
        itemIf:       "if",
        itemEnd:      "end",
+       itemNil:      "nil",
        itemRange:    "range",
        itemTemplate: "template",
        itemWith:     "with",
@@ -109,6 +111,7 @@ var key = map[string]itemType{
        "end":      itemEnd,
        "if":       itemIf,
        "range":    itemRange,
+       "nil":      itemNil,
        "template": itemTemplate,
        "with":     itemWith,
 }
index 26d242b41f51ad4fa3cfec9f4a13c2b689cc329a..842e92db21d2e745c4243a20369e7a85570349d2 100644 (file)
@@ -85,6 +85,12 @@ var lexTests = []lexTest{
                tRight,
                tEOF,
        }},
+       {"nil", "{{nil}}", []item{
+               tLeft,
+               {itemNil, 0, "nil"},
+               tRight,
+               tEOF,
+       }},
        {"dots", "{{.x . .2 .x.y}}", []item{
                tLeft,
                {itemField, 0, ".x"},
index db645624c562064725d2189146d941e2adfe9a72..8a779ce1a9082fb0e5a7f6e6a9ece349d59b27ab 100644 (file)
@@ -44,6 +44,7 @@ const (
        NodeIdentifier                 // An identifier; always a function name.
        NodeIf                         // An if action.
        NodeList                       // A list of Nodes.
+       NodeNil                        // An untyped nil constant.
        NodeNumber                     // A numerical constant.
        NodePipe                       // A pipeline of commands.
        NodeRange                      // A range action.
@@ -288,6 +289,26 @@ func (d *DotNode) Copy() Node {
        return newDot()
 }
 
+// NilNode holds the special identifier 'nil' representing an untyped nil constant.
+// It is represented by a nil pointer.
+type NilNode bool
+
+func newNil() *NilNode {
+       return nil
+}
+
+func (d *NilNode) Type() NodeType {
+       return NodeNil
+}
+
+func (d *NilNode) String() string {
+       return "nil"
+}
+
+func (d *NilNode) Copy() Node {
+       return newNil()
+}
+
 // FieldNode holds a field (identifier starting with '.').
 // The names may be chained ('.x.y').
 // The period is dropped from each ident.
index 7970b5fcc649ac154822cb2484cb2d05d5819b01..7ddb6fff1e534e7d0b997a435225efa0f8ec3e31 100644 (file)
@@ -355,7 +355,7 @@ func (t *Tree) pipeline(context string) (pipe *PipeNode) {
                        }
                        return
                case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier,
-                       itemVariable, itemNumber, itemRawString, itemString:
+                       itemNumber, itemNil, itemRawString, itemString, itemVariable:
                        t.backup()
                        pipe.append(t.command())
                default:
@@ -470,6 +470,8 @@ Loop:
                        cmd.append(NewIdentifier(token.val))
                case itemDot:
                        cmd.append(newDot())
+               case itemNil:
+                       cmd.append(newNil())
                case itemVariable:
                        cmd.append(t.useVar(token.val))
                case itemField:
index b2e788238d3acb200c3416e8c4fe21ce35b86385..d7bfc78c058fdb741708a5d1ba04716f3f966256 100644 (file)
@@ -205,8 +205,8 @@ var parseTests = []parseTest{
                `{{range $x := .SI}}{{.}}{{end}}`},
        {"range 2 vars", "{{range $x, $y := .SI}}{{.}}{{end}}", noError,
                `{{range $x, $y := .SI}}{{.}}{{end}}`},
-       {"constants", "{{range .SI 1 -3.2i true false 'a'}}{{end}}", noError,
-               `{{range .SI 1 -3.2i true false 'a'}}{{end}}`},
+       {"constants", "{{range .SI 1 -3.2i true false 'a' nil}}{{end}}", noError,
+               `{{range .SI 1 -3.2i true false 'a' nil}}{{end}}`},
        {"template", "{{template `x`}}", noError,
                `{{template "x"}}`},
        {"template with arg", "{{template `x` .Y}}", noError,