diff --git a/lang/core/template_func.go b/lang/core/template_func.go index 7b243428..1edf8668 100644 --- a/lang/core/template_func.go +++ b/lang/core/template_func.go @@ -425,8 +425,12 @@ func (obj *TemplateFunc) run(templateText string, vars types.Value) (string, err // parameter types. Functions meant to apply to arguments of // arbitrary type can use parameters of type interface{} or of // type reflect.Value. - f := wrap(name, fn) // wrap it so that it meets API expectations - funcMap[name] = f // add it + f, err := wrap(name, fn) // wrap it so that it meets API expectations + if err != nil { + obj.init.Logf("warning, skipping function named: `%s`, err: %v", name, err) + continue + } + funcMap[name] = f // add it } var err error @@ -581,7 +585,14 @@ func safename(name string) string { // function API with what is expected from the reflection API. It returns a // version that includes the optional second error return value so that our // functions can return errors without causing a panic. -func wrap(name string, fn *types.FuncValue) interface{} { +func wrap(name string, fn *types.FuncValue) (_ interface{}, reterr error) { + defer func() { + // catch unhandled panics + if r := recover(); r != nil { + reterr = fmt.Errorf("panic in template wrap of `%s` function: %+v", name, r) + } + }() + if fn.T.Map == nil { panic("malformed func type") } @@ -600,7 +611,8 @@ func wrap(name string, fn *types.FuncValue) interface{} { in = append(in, t.Reflect()) } - out := []reflect.Type{fn.T.Out.Reflect(), errorType} + ret := fn.T.Out.Reflect() // this can panic! + out := []reflect.Type{ret, errorType} var variadic = false // currently not supported in our function value typ := reflect.FuncOf(in, out, variadic) @@ -640,5 +652,5 @@ func wrap(name string, fn *types.FuncValue) interface{} { return []reflect.Value{reflect.ValueOf(result.Value()), nilError} } val := reflect.MakeFunc(typ, f) - return val.Interface() + return val.Interface(), nil } diff --git a/lang/types/type.go b/lang/types/type.go index 5805a353..e8073c1f 100644 --- a/lang/types/type.go +++ b/lang/types/type.go @@ -899,6 +899,11 @@ func (obj *Type) Reflect() reflect.Type { if t == nil { panic("malformed struct field") } + if strings.Title(k) != k { // is exported? + //k = strings.Title(k) // TODO: is this helpful? + // reflect.StructOf would panic on anything unexported + panic(fmt.Sprintf("struct has unexported field: %s", k)) + } fields = append(fields, reflect.StructField{ Name: k, // struct field name diff --git a/lang/types/type_test.go b/lang/types/type_test.go index d03117d7..23c169ca 100644 --- a/lang/types/type_test.go +++ b/lang/types/type_test.go @@ -1502,3 +1502,25 @@ func TestTypeOf0(t *testing.T) { // TODO: implement testing of the TypeOf function // TODO: implement testing TypeOf for struct field name mappings } + +func TestReflect0(t *testing.T) { + mustPanic := func() (reterr error) { + defer func() { + // catch unhandled panics + if r := recover(); r != nil { + reterr = fmt.Errorf("panic: %+v", r) + } + }() + + // It's unclear if we want this behaviour forever, but it is the + // current behaviour, and I'd at least like to know if it + // changes so we can understand where (if at all) it's required. + typ := NewType("struct{field1 str}") + _ = typ.Reflect() + return nil + } + + if err := mustPanic(); err == nil { + t.Errorf("expected panic, got nil") + } +}