Fix it so YAML integer types can be used where Go int types are expected

E.g. in date.AddDate.

In Hugo v0.152.0 we moved to a new YAML library (github.com/goccy/go-yaml) which produces uint64 for unsigned integers.

This unfortunately breaks common constructs like:

  .Date.AddDate 0 0 7

when .Date is a time.Time and the integers are unmarshaled from YAML front matter.

This commit adds code to handle conversion from uint64 (and other int types) to the required int types where possible.

Fixes #14079
This commit is contained in:
Bjørn Erik Pedersen
2025-10-22 14:22:59 +02:00
parent 29e2c2fa92
commit d4c78885ae
5 changed files with 161 additions and 1 deletions
+27
View File
@@ -18,6 +18,7 @@ package hreflect
import (
"context"
"math"
"reflect"
"sync"
"time"
@@ -309,3 +310,29 @@ func IsContextType(tp reflect.Type) bool {
})
return isContext
}
// ConvertIfPossible tries to convert val to typ if possible.
// This is currently only implemented for int kinds,
// added to handle the move to a new YAML library which produces uint64 for unsigned integers.
// We can expand on this later if needed.
// See Issue 14079.
func ConvertIfPossible(val reflect.Value, typ reflect.Type) (reflect.Value, bool) {
if IsInt(typ.Kind()) {
if IsInt(val.Kind()) {
if typ.OverflowInt(val.Int()) {
return reflect.Value{}, false
}
return val.Convert(typ), true
}
if IsUint(val.Kind()) {
if val.Uint() > uint64(math.MaxInt64) {
return reflect.Value{}, false
}
if typ.OverflowInt(int64(val.Uint())) {
return reflect.Value{}, false
}
return val.Convert(typ), true
}
}
return reflect.Value{}, false
}
+64
View File
@@ -15,6 +15,7 @@ package hreflect
import (
"context"
"math"
"reflect"
"testing"
"time"
@@ -176,3 +177,66 @@ func BenchmarkGetMethodByNamePara(b *testing.B) {
}
})
}
func TestCastIfPossible(t *testing.T) {
c := qt.New(t)
for _, test := range []struct {
name string
value any
typ any
expected any
ok bool
}{
// From uint to int.
{
name: "uint64(math.MaxUint64) to int16",
value: uint64(math.MaxUint64),
typ: int16(0),
ok: false, // overflow
},
{
name: "uint64(math.MaxUint64) to int64",
value: uint64(math.MaxUint64),
typ: int64(0),
ok: false, // overflow
},
{
name: "uint64(math.MaxInt16) to int16",
value: uint64(math.MaxInt16),
typ: int64(0),
ok: true,
expected: int64(math.MaxInt16),
},
// From int to int.
{
name: "int64(math.MaxInt64) to int16",
value: int64(math.MaxInt64),
typ: int16(0),
ok: false, // overflow
},
{
name: "int64(math.MaxInt16) to int",
value: int64(math.MaxInt16),
typ: int(0),
ok: true,
expected: int(math.MaxInt16),
},
{
name: "int64(math.MaxInt16) to int",
value: int64(math.MaxInt16),
typ: int(0),
ok: true,
expected: int(math.MaxInt16),
},
} {
v, ok := ConvertIfPossible(reflect.ValueOf(test.value), reflect.TypeOf(test.typ))
c.Assert(ok, qt.Equals, test.ok, qt.Commentf("test case: %s", test.name))
if test.ok {
c.Assert(v.Interface(), qt.Equals, test.expected, qt.Commentf("test case: %s", test.name))
}
}
}