]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
tpl/collections: Honor the Eqer interface in where comparisons
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Mon, 20 Apr 2026 16:12:29 +0000 (18:12 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Mon, 20 Apr 2026 17:45:06 +0000 (19:45 +0200)
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>
tpl/collections/collections_integration_test.go
tpl/collections/where.go

index 88f62286fb9bfad28e956e640fc1e66d15767759..6cd9639a79d1a2d5175857e10a31ff89d2f799ce 100644 (file)
@@ -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()
index 0cb84b8f5605f657fe52e502954c112fc4e87841..7a813fd7cfdb2522c7b06055a0f5fe07616ac251 100644 (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 {