From b2b19ace49a7d2b05d38c52f9567130513123087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Sat, 27 Oct 2018 11:10:39 +0200 Subject: [PATCH] common/collections: Allow a mix of slice types in append/Scratch.Add The type handling in these was improved in Hugo 0.49, but this also meant that it was no longer possible to start out with a string slice and later append `Page` etc. to it. This commit makes sure that the old behaviour is now possible again by falling back to a `[]interface{}` as a last resort. Fixes #5361 --- common/collections/append.go | 33 ++++++++++++++++++++++++++++++- common/collections/append_test.go | 8 +++++--- common/maps/scratch_test.go | 14 +++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/common/collections/append.go b/common/collections/append.go index 97bf8498896..617d6258a1a 100644 --- a/common/collections/append.go +++ b/common/collections/append.go @@ -48,6 +48,10 @@ func Append(to interface{}, from ...interface{}) (interface{}, error) { // If we get []string []string, we append the from slice to to if tot == fromt { return reflect.AppendSlice(tov, fromv).Interface(), nil + } else if !fromt.AssignableTo(tot) { + // Fall back to a []interface{} slice. + return appendToInterfaceSliceFromValues(tov, fromv) + } } } @@ -60,7 +64,8 @@ func Append(to interface{}, from ...interface{}) (interface{}, error) { for _, f := range from { fv := reflect.ValueOf(f) if !fv.Type().AssignableTo(tot) { - return nil, fmt.Errorf("append element type mismatch: expected %v, got %v", tot, fv.Type()) + // Fall back to a []interface{} slice. + return appendToInterfaceSlice(tov, from...) } tov = reflect.Append(tov, fv) } @@ -68,6 +73,32 @@ func Append(to interface{}, from ...interface{}) (interface{}, error) { return tov.Interface(), nil } +func appendToInterfaceSliceFromValues(slice1, slice2 reflect.Value) ([]interface{}, error) { + var tos []interface{} + + for _, slice := range []reflect.Value{slice1, slice2} { + for i := 0; i < slice.Len(); i++ { + tos = append(tos, slice.Index(i).Interface()) + } + } + + return tos, nil +} + +func appendToInterfaceSlice(tov reflect.Value, from ...interface{}) ([]interface{}, error) { + var tos []interface{} + + for i := 0; i < tov.Len(); i++ { + tos = append(tos, tov.Index(i).Interface()) + } + + for _, v := range from { + tos = append(tos, v) + } + + return tos, nil +} + // indirect is borrowed from the Go stdlib: 'text/template/exec.go' // TODO(bep) consolidate func indirect(v reflect.Value) (rv reflect.Value, isNil bool) { diff --git a/common/collections/append_test.go b/common/collections/append_test.go index f89ec60f0b7..c08a69c0d89 100644 --- a/common/collections/append_test.go +++ b/common/collections/append_test.go @@ -46,10 +46,12 @@ func TestAppend(t *testing.T) { {testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn1{"b"}}, []interface{}{&tstSlicerIn1{"c"}}, testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn1{"b"}, &tstSlicerIn1{"c"}}}, + //https://github.com/gohugoio/hugo/issues/5361 + {[]string{"a", "b"}, []interface{}{tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}}, + []interface{}{"a", "b", &tstSlicer{"a"}, &tstSlicer{"b"}}}, + {[]string{"a", "b"}, []interface{}{&tstSlicer{"a"}}, + []interface{}{"a", "b", &tstSlicer{"a"}}}, // Errors - {testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn1{"b"}}, - []interface{}{"c"}, - false}, {"", []interface{}{[]string{"a", "b"}}, false}, // No string concatenation. {"ab", diff --git a/common/maps/scratch_test.go b/common/maps/scratch_test.go index bf37d79dfb5..4550a22c532 100644 --- a/common/maps/scratch_test.go +++ b/common/maps/scratch_test.go @@ -96,6 +96,20 @@ func TestScratchAddTypedSliceToInterfaceSlice(t *testing.T) { } +// https://github.com/gohugoio/hugo/issues/5361 +func TestScratchAddDifferentTypedSliceToInterfaceSlice(t *testing.T) { + t.Parallel() + assert := require.New(t) + + scratch := NewScratch() + scratch.Set("slice", []string{"foo"}) + + _, err := scratch.Add("slice", []int{1, 2}) + assert.NoError(err) + assert.Equal([]interface{}{"foo", 1, 2}, scratch.Get("slice")) + +} + func TestScratchSet(t *testing.T) { t.Parallel() assert := require.New(t)