tpl/collections: Honor the Eqer interface in where comparisons

The where function previously fell through to a no-op when comparing
two values whose kinds were not handled by the primitive type switches
(e.g. two Page interface values). This made `where pages "Parent" $page`
return an empty list, while the equivalent `range pages` + `if eq` worked.

Use compare.Eqer for equality operators when either side implements it,
matching the behavior of the eq/ne template funcs.

Fixes #14777

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bjørn Erik Pedersen
2026-04-20 18:12:29 +02:00
parent 52123ae23f
commit f5fce935e7
2 changed files with 73 additions and 0 deletions
@@ -289,6 +289,51 @@ disableKinds = ['rss','sitemap', 'taxonomy', 'term', 'page']
b.AssertFileContentExact("public/index.html", "0: /a3_b1.html\n\n1: /b2.html\n\n2: /a1.html\n\n3: /a2.html\n$")
}
// Issue 14777.
func TestWhereWithPageEqualityIssue14777(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = 'http://example.com/'
disableKinds = ['rss','sitemap','taxonomy','term']
-- content/s1/_index.md --
---
title: S1
---
-- content/s1/p1.md --
---
title: P1
---
-- content/s1/p2.md --
---
title: P2
---
-- content/s2/_index.md --
---
title: S2
---
-- content/s2/p3.md --
---
title: P3
---
-- layouts/section.html --
{{ $page := . }}
WhereEq: {{ range where site.AllPages "Parent" "eq" $page }}{{ .Title }}|{{ end }}$
WhereDefault: {{ range where site.AllPages "Parent" $page }}{{ .Title }}|{{ end }}$
RangeIf: {{ range site.AllPages }}{{ if eq .Parent $page }}{{ .Title }}|{{ end }}{{ end }}$
WhereNe: {{ range where site.AllPages "Parent" "ne" $page }}{{ .Title }}|{{ end }}$
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/s1/index.html",
"WhereEq: P1|P2|$",
"WhereDefault: P1|P2|$",
"RangeIf: P1|P2|$",
)
}
// Issue 13621.
func TestWhereNotInEmptySlice(t *testing.T) {
t.Parallel()
+28
View File
@@ -23,6 +23,7 @@ import (
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hreflect"
"github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/compare"
)
// Where returns a filtered subset of collection c.
@@ -85,6 +86,16 @@ func (ns *Namespace) checkCondition(v, mv reflect.Value, op string) (bool, error
return false, nil
}
switch op {
case "", "=", "==", "eq", "!=", "<>", "ne":
if eq, ok := tryEq(v, mv); ok {
if op == "!=" || op == "<>" || op == "ne" {
return !eq, nil
}
return eq, nil
}
}
var ivp, imvp *int64
var fvp, fmvp *float64
var svp, smvp *string
@@ -690,6 +701,23 @@ func (ns *Namespace) checkWhereMap(ctxv, seqv, kv, mv reflect.Value, path []stri
// toString returns the string value if possible, "" if not.
// tryEq returns the equality of v and mv via the compare.Eqer interface
// if either side implements it. The second return value reports whether
// such a comparison was possible.
func tryEq(v, mv reflect.Value) (bool, bool) {
if !v.CanInterface() || !mv.CanInterface() {
return false, false
}
vi, mvi := v.Interface(), mv.Interface()
if e, ok := vi.(compare.Eqer); ok {
return e.Eq(mvi), true
}
if e, ok := mvi.(compare.Eqer); ok {
return e.Eq(vi), true
}
return false, false
}
func (ns *Namespace) toTimeUnix(v reflect.Value) int64 {
t, ok := hreflect.AsTime(v, ns.loc)
if !ok {