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)
return field
}
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()) {
}
return result
}
+ case reflect.Ptr:
+ etyp := receiver.Type().Elem()
+ if etyp.Kind() == reflect.Struct {
+ if _, ok := etyp.FieldByName(fieldName); !ok {
+ // If there's no such field, say "can't evaluate"
+ // instead of "nil pointer evaluating".
+ break
+ }
+ }
+ if isNil {
+ s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
+ }
}
s.errorf("can't evaluate field %s in type %s", fieldName, typ)
panic("not reached")
panic("not reached")
}
-// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
+// indirect returns the item at the end of indirection, and a bool to indicate
+// if it's nil. If the returned bool is true, the returned value's kind will be
+// either a pointer or interface.
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
if v.IsNil() {
}
}
-// 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 := "<nil>"
- if err != nil {
- got = err.Error()
+func TestEvalFieldErrors(t *testing.T) {
+ tests := []struct {
+ name, src string
+ value interface{}
+ want string
+ }{
+ {
+ // 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
+ "MissingFieldOnNil",
+ "{{.MissingField}}",
+ (*T)(nil),
+ "can't evaluate field MissingField in type *template.T",
+ },
+ {
+ "MissingFieldOnNonNil",
+ "{{.MissingField}}",
+ &T{},
+ "can't evaluate field MissingField in type *template.T",
+ },
+ {
+ "ExistingFieldOnNil",
+ "{{.X}}",
+ (*T)(nil),
+ "nil pointer evaluating *template.T.X",
+ },
+ {
+ "MissingKeyOnNilMap",
+ "{{.MissingKey}}",
+ (*map[string]string)(nil),
+ "nil pointer evaluating *map[string]string.MissingKey",
+ },
+ {
+ "MissingKeyOnNilMapPtr",
+ "{{.MissingKey}}",
+ (*map[string]string)(nil),
+ "nil pointer evaluating *map[string]string.MissingKey",
+ },
+ {
+ "MissingKeyOnMapPtrToNil",
+ "{{.MissingKey}}",
+ &map[string]string{},
+ "<nil>",
+ },
}
- want := "can't evaluate field MissingField in type *template.T"
- if !strings.HasSuffix(got, want) {
- t.Errorf("got error %q, want %q", got, want)
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ tmpl := Must(New("tmpl").Parse(tc.src))
+ err := tmpl.Execute(ioutil.Discard, tc.value)
+ got := "<nil>"
+ if err != nil {
+ got = err.Error()
+ }
+ if !strings.HasSuffix(got, tc.want) {
+ t.Fatalf("got error %q, want %q", got, tc.want)
+ }
+ })
}
}