From 0fd270ab7abec08c050f29a3bbeb83d7740d0a47 Mon Sep 17 00:00:00 2001 From: Andrew Gerrand Date: Fri, 8 Apr 2016 15:39:32 +1000 Subject: text/template: emit field error over nil pointer error where appropriate When evaluating "{{.MissingField}}" on a nil *T, Exec returns "can't evaluate field MissingField in type *T" instead of "nil pointer evaluating *T.MissingField". Fixes golang/go#15125 Change-Id: I6e73f61b8a72c694179c1f8cdc808766c90b6f57 Reviewed-on: https://go-review.googlesource.com/21705 Reviewed-by: Rob Pike --- src/text/template/exec.go | 12 +++++++----- src/text/template/exec_test.go | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) (limited to 'src/text') diff --git a/src/text/template/exec.go b/src/text/template/exec.go index a169e62ab0..22881c6852 100644 --- a/src/text/template/exec.go +++ b/src/text/template/exec.go @@ -538,14 +538,14 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, return s.evalCall(dot, method, node, fieldName, args, final) } hasArgs := len(args) > 1 || final.IsValid() - // It's not a method; must be a field of a struct or an element of a map. The receiver must not be nil. - if isNil { - s.errorf("nil pointer evaluating %s.%s", typ, fieldName) - } + // It's not a method; must be a field of a struct or an element of a map. switch receiver.Kind() { case reflect.Struct: tField, ok := receiver.Type().FieldByName(fieldName) if ok { + if isNil { + s.errorf("nil pointer evaluating %s.%s", typ, fieldName) + } field := receiver.FieldByIndex(tField.Index) if tField.PkgPath != "" { // field is unexported s.errorf("%s is an unexported field of struct type %s", fieldName, typ) @@ -556,8 +556,10 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, } return field } - s.errorf("%s is not a field of struct type %s", fieldName, typ) case reflect.Map: + if isNil { + s.errorf("nil pointer evaluating %s.%s", typ, fieldName) + } // If it's a map, attempt to use the field name as a key. nameVal := reflect.ValueOf(fieldName) if nameVal.Type().AssignableTo(receiver.Type().Key()) { diff --git a/src/text/template/exec_test.go b/src/text/template/exec_test.go index e507e917fe..bc2aa683ec 100644 --- a/src/text/template/exec_test.go +++ b/src/text/template/exec_test.go @@ -1280,3 +1280,20 @@ func TestBlock(t *testing.T) { t.Errorf("got %q, want %q", got, want2) } } + +// Check that calling an invalid field on nil pointer prints +// a field error instead of a distracting nil pointer error. +// https://golang.org/issue/15125 +func TestMissingFieldOnNil(t *testing.T) { + tmpl := Must(New("tmpl").Parse("{{.MissingField}}")) + var d *T + err := tmpl.Execute(ioutil.Discard, d) + got := "" + if err != nil { + got = err.Error() + } + want := "can't evaluate field MissingField in type *template.T" + if !strings.HasSuffix(got, want) { + t.Errorf("got error %q, want %q", got, want) + } +} -- cgit v1.3 From 7085fb31dfb1a6b447a139064a4a692677284132 Mon Sep 17 00:00:00 2001 From: Andrew Gerrand Date: Mon, 11 Apr 2016 11:14:53 +1000 Subject: html/template, text/template: clarify Parse{Files,Glob} semantics Document the subtle property that files with equivalent base names will overwrite extant templates with those same names. Fixes golang/go#14320 Change-Id: Ie9ace1b08e6896ea599836e31582123169aa7a25 Reviewed-on: https://go-review.googlesource.com/21824 Reviewed-by: Rob Pike --- src/html/template/template.go | 14 ++++++++++++++ src/text/template/helper.go | 14 ++++++++++++++ 2 files changed, 28 insertions(+) (limited to 'src/text') diff --git a/src/html/template/template.go b/src/html/template/template.go index 96ab268a7f..063e46d6bf 100644 --- a/src/html/template/template.go +++ b/src/html/template/template.go @@ -346,6 +346,11 @@ func Must(t *Template, err error) *Template { // the named files. The returned template's name will have the (base) name and // (parsed) contents of the first file. There must be at least one file. // If an error occurs, parsing stops and the returned *Template is nil. +// +// When parsing multiple files with the same name in different directories, +// the last one mentioned will be the one that results. +// For instance, ParseFiles("a/foo", "b/foo") stores "b/foo" as the template +// named "foo", while "a/foo" is unavailable. func ParseFiles(filenames ...string) (*Template, error) { return parseFiles(nil, filenames...) } @@ -353,6 +358,9 @@ func ParseFiles(filenames ...string) (*Template, error) { // ParseFiles parses the named files and associates the resulting templates with // t. If an error occurs, parsing stops and the returned template is nil; // otherwise it is t. There must be at least one file. +// +// When parsing multiple files with the same name in different directories, +// the last one mentioned will be the one that results. func (t *Template) ParseFiles(filenames ...string) (*Template, error) { return parseFiles(t, filenames...) } @@ -399,6 +407,9 @@ func parseFiles(t *Template, filenames ...string) (*Template, error) { // returned template will have the (base) name and (parsed) contents of the // first file matched by the pattern. ParseGlob is equivalent to calling // ParseFiles with the list of files matched by the pattern. +// +// When parsing multiple files with the same name in different directories, +// the last one mentioned will be the one that results. func ParseGlob(pattern string) (*Template, error) { return parseGlob(nil, pattern) } @@ -408,6 +419,9 @@ func ParseGlob(pattern string) (*Template, error) { // processed by filepath.Glob and must match at least one file. ParseGlob is // equivalent to calling t.ParseFiles with the list of files matched by the // pattern. +// +// When parsing multiple files with the same name in different directories, +// the last one mentioned will be the one that results. func (t *Template) ParseGlob(pattern string) (*Template, error) { return parseGlob(t, pattern) } diff --git a/src/text/template/helper.go b/src/text/template/helper.go index 787ca62e5f..9e0200c352 100644 --- a/src/text/template/helper.go +++ b/src/text/template/helper.go @@ -29,6 +29,11 @@ func Must(t *Template, err error) *Template { // the named files. The returned template's name will have the base name and // parsed contents of the first file. There must be at least one file. // If an error occurs, parsing stops and the returned *Template is nil. +// +// When parsing multiple files with the same name in different directories, +// the last one mentioned will be the one that results. +// For instance, ParseFiles("a/foo", "b/foo") stores "b/foo" as the template +// named "foo", while "a/foo" is unavailable. func ParseFiles(filenames ...string) (*Template, error) { return parseFiles(nil, filenames...) } @@ -41,6 +46,9 @@ func ParseFiles(filenames ...string) (*Template, error) { // of the (base) names of the files. If it does not, depending on t's // contents before calling ParseFiles, t.Execute may fail. In that // case use t.ExecuteTemplate to execute a valid template. +// +// When parsing multiple files with the same name in different directories, +// the last one mentioned will be the one that results. func (t *Template) ParseFiles(filenames ...string) (*Template, error) { t.init() return parseFiles(t, filenames...) @@ -88,6 +96,9 @@ func parseFiles(t *Template, filenames ...string) (*Template, error) { // returned template will have the (base) name and (parsed) contents of the // first file matched by the pattern. ParseGlob is equivalent to calling // ParseFiles with the list of files matched by the pattern. +// +// When parsing multiple files with the same name in different directories, +// the last one mentioned will be the one that results. func ParseGlob(pattern string) (*Template, error) { return parseGlob(nil, pattern) } @@ -97,6 +108,9 @@ func ParseGlob(pattern string) (*Template, error) { // processed by filepath.Glob and must match at least one file. ParseGlob is // equivalent to calling t.ParseFiles with the list of files matched by the // pattern. +// +// When parsing multiple files with the same name in different directories, +// the last one mentioned will be the one that results. func (t *Template) ParseGlob(pattern string) (*Template, error) { t.init() return parseGlob(t, pattern) -- cgit v1.3