From 67aede4364aef515010e3220c608fcf229d5e8af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Thu, 21 May 2026 10:36:10 +0200 Subject: [PATCH] tpl/collections: Make dict return nil when no values are provided --- common/hashing/hashing_test.go | 9 +++++++++ tpl/collections/collections.go | 10 ++++++++++ .../collections_integration_test.go | 18 ++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/common/hashing/hashing_test.go b/common/hashing/hashing_test.go index de2fed8f0..f98ab7d51 100644 --- a/common/hashing/hashing_test.go +++ b/common/hashing/hashing_test.go @@ -62,6 +62,15 @@ func TestXxHashFromString(t *testing.T) { c.Assert(got, qt.Equals, uint64(7148569436472236994)) } +func TestHashNilMapVsEmptyMap(t *testing.T) { + c := qt.New(t) + + var m1 map[string]any = nil + m2 := map[string]any{} + + c.Assert(HashString(m1), qt.Equals, HashString(m2)) +} + func TestXxHashFromStringHexEncoded(t *testing.T) { c := qt.New(t) s := "The quick brown fox jumps over the lazy dog" diff --git a/tpl/collections/collections.go b/tpl/collections/collections.go index 04840f8c4..2563a60f1 100644 --- a/tpl/collections/collections.go +++ b/tpl/collections/collections.go @@ -157,7 +157,17 @@ func (ns *Namespace) Delimit(ctx context.Context, l, sep any, last ...any) (stri // Dictionary creates a new map from the given parameters by // treating values as key-value pairs. The number of values must be even. // The keys can be string slices, which will create the needed nested structure. +// If no values are provided, nil will be returned. func (ns *Namespace) Dictionary(values ...any) (map[string]any, error) { + if len(values) == 0 { + // A common construct is to do + // {{ $opts := dict }} + // And then conditionally assign it if some condition is set. + // The only difference between this and an empty map is that this cannot be written to, + // which is not something we do in Hugo (or: If we do, that's a bug). + // This saves us ~48 bytes in memory allocation on 64-bit architectures. + return nil, nil + } if len(values)%2 != 0 { return nil, errors.New("invalid dictionary call") } diff --git a/tpl/collections/collections_integration_test.go b/tpl/collections/collections_integration_test.go index 6cd9639a7..59cb51640 100644 --- a/tpl/collections/collections_integration_test.go +++ b/tpl/collections/collections_integration_test.go @@ -653,3 +653,21 @@ All. } }) } + +func TestEmptyDictShouldBeNil(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +-- layouts/home.html -- +{{ $d := dict }} +{{ printf "dict: %T %t %d" $d (eq $d nil) (len $d) }} +{{ range $d }}FAIL{{ end }} +index: {{ index $d "foo" }}| +{{ $d2 := dict "foo" "bar" }} +{{ $d3 := merge $d $d2 }} +{{ printf "d3: %v" $d3 }}| +` + + hugolib.Test(t, files).AssertFileContent("public/index.html", "dict: map[string]interface {} true 0", "! FAIL", "index: |", "d3: map[foo:bar]|") +}