shithub: hugo

Download patch

ref: 8279d2e2271ee64725133d36a12d1d7e2158bffd
parent: c4fa2f07996c7f1f4e257089a3c3c5b4c1339722
author: satotake <doublequotation@gmail.com>
date: Mon Mar 9 17:32:38 EDT 2020

Support unComparable args of uniq/complement/in

Fixes #6105

--- a/tpl/collections/collections.go
+++ b/tpl/collections/collections.go
@@ -271,11 +271,6 @@
 	lv := reflect.ValueOf(l)
 	vv := reflect.ValueOf(v)
 
-	if !vv.Type().Comparable() {
-		return false, errors.Errorf("value to check must be comparable: %T", v)
-	}
-
-	// Normalize numeric types to float64 etc.
 	vvk := normalize(vv)
 
 	switch lv.Kind() {
@@ -282,7 +277,7 @@
 	case reflect.Array, reflect.Slice:
 		for i := 0; i < lv.Len(); i++ {
 			lvv, isNil := indirectInterface(lv.Index(i))
-			if isNil || !lvv.Type().Comparable() {
+			if isNil {
 				continue
 			}
 
@@ -713,6 +708,7 @@
 	switch v.Kind() {
 	case reflect.Slice:
 		slice = reflect.MakeSlice(v.Type(), 0, 0)
+
 	case reflect.Array:
 		slice = reflect.MakeSlice(reflect.SliceOf(v.Type().Elem()), 0, 0)
 	default:
@@ -720,12 +716,12 @@
 	}
 
 	seen := make(map[interface{}]bool)
+
 	for i := 0; i < v.Len(); i++ {
 		ev, _ := indirectInterface(v.Index(i))
-		if !ev.Type().Comparable() {
-			return nil, errors.New("elements must be comparable")
-		}
+
 		key := normalize(ev)
+
 		if _, found := seen[key]; !found {
 			slice = reflect.Append(slice, ev)
 			seen[key] = true
--- a/tpl/collections/collections_test.go
+++ b/tpl/collections/collections_test.go
@@ -348,6 +348,9 @@
 		// template.HTML
 		{template.HTML("this substring should be found"), "substring", true},
 		{template.HTML("this substring should not be found"), "subseastring", false},
+		// Uncomparable, use hashstructure
+		{[]string{"a", "b"}, []string{"a", "b"}, false},
+		{[][]string{{"a", "b"}}, []string{"a", "b"}, true},
 	} {
 
 		errMsg := qt.Commentf("[%d] %v", i, test)
@@ -356,10 +359,6 @@
 		c.Assert(err, qt.IsNil)
 		c.Assert(result, qt.Equals, test.expect, errMsg)
 	}
-
-	// Slices are not comparable
-	_, err := ns.In([]string{"a", "b"}, []string{"a", "b"})
-	c.Assert(err, qt.Not(qt.IsNil))
 }
 
 type testPage struct {
@@ -835,9 +834,14 @@
 		// Structs
 		{pagesVals{p3v, p2v, p3v, p2v}, pagesVals{p3v, p2v}, false},
 
+		// not Comparable(), use hashstruscture
+		{[]map[string]int{
+			{"K1": 1}, {"K2": 2}, {"K1": 1}, {"K2": 1},
+		}, []map[string]int{
+			{"K1": 1}, {"K2": 2}, {"K2": 1},
+		}, false},
+
 		// should fail
-		// uncomparable types
-		{[]map[string]int{{"K1": 1}}, []map[string]int{{"K2": 2}, {"K2": 2}}, true},
 		{1, 1, true},
 		{"foo", "fo", true},
 	} {
--- a/tpl/collections/complement.go
+++ b/tpl/collections/complement.go
@@ -44,9 +44,6 @@
 		sl := reflect.MakeSlice(v.Type(), 0, 0)
 		for i := 0; i < v.Len(); i++ {
 			ev, _ := indirectInterface(v.Index(i))
-			if !ev.Type().Comparable() {
-				return nil, errors.New("elements in complement must be comparable")
-			}
 			if _, found := aset[normalize(ev)]; !found {
 				sl = reflect.Append(sl, ev)
 			}
--- a/tpl/collections/complement_test.go
+++ b/tpl/collections/complement_test.go
@@ -65,7 +65,10 @@
 		{[]string{"a", "b", "c"}, []interface{}{"error"}, false},
 		{"error", []interface{}{[]string{"c", "d"}, []string{"a", "b"}}, false},
 		{[]string{"a", "b", "c"}, []interface{}{[][]string{{"c", "d"}}}, false},
-		{[]interface{}{[][]string{{"c", "d"}}}, []interface{}{[]string{"c", "d"}, []string{"a", "b"}}, false},
+		{
+			[]interface{}{[][]string{{"c", "d"}}}, []interface{}{[]string{"c", "d"}, []string{"a", "b"}},
+			[]interface{}{[][]string{{"c", "d"}}},
+		},
 	} {
 
 		errMsg := qt.Commentf("[%d]", i)
--- a/tpl/collections/reflect_helpers.go
+++ b/tpl/collections/reflect_helpers.go
@@ -18,6 +18,7 @@
 	"reflect"
 	"time"
 
+	"github.com/mitchellh/hashstructure"
 	"github.com/pkg/errors"
 )
 
@@ -42,11 +43,19 @@
 	}
 }
 
-// normalizes different numeric types to make them comparable.
+// normalizes different numeric types if isNumber
+// or get the hash values if not Comparable (such as map or struct)
+// to make them comparable
 func normalize(v reflect.Value) interface{} {
 	k := v.Kind()
 
 	switch {
+	case !v.Type().Comparable():
+		h, err := hashstructure.Hash(v.Interface(), nil)
+		if err != nil {
+			panic(err)
+		}
+		return h
 	case isNumber(k):
 		f, err := numberToFloat(v)
 		if err == nil {
@@ -53,7 +62,6 @@
 			return f
 		}
 	}
-
 	return v.Interface()
 }
 
--- a/tpl/collections/symdiff.go
+++ b/tpl/collections/symdiff.go
@@ -48,10 +48,8 @@
 
 			for i := 0; i < v.Len(); i++ {
 				ev, _ := indirectInterface(v.Index(i))
-				if !ev.Type().Comparable() {
-					return nil, errors.New("symdiff: elements must be comparable")
-				}
 				key := normalize(ev)
+
 				// Append if the key is not in their intersection.
 				if ids1[key] != ids2[key] {
 					v, err := convertValue(ev, sliceElemType)