Compare commits

...

8 Commits

Author SHA1 Message Date
Thomas Pelletier b730b2be5d Bump testing to go 1.23 (#961) 2024-08-17 16:26:05 -04:00
vito a437caafe5 Fix reflect.Pointer backward compatibility (#956) 2024-08-17 16:07:56 -04:00
guoguangwu be6c57be30 Fix readme typo(#951) 2024-08-17 15:56:40 -04:00
Daniel Weiße d55304782e Allow int, uint, and floats as map keys (#958)
Signed-off-by: Daniel Weiße <dw@edgeless.systems>
2024-08-17 15:44:21 -04:00
Daniel Weiße 0977c05dd5 Update goreleaser action to v6 and set goreleaser binary to v2 (#959)
Signed-off-by: Daniel Weiße <dw@edgeless.systems>
2024-08-17 15:40:55 -04:00
Daniel Martí bccd6e48f4 allocate unstable.Parser as part of decoder (#953)
This way, calls to Unmarshal or Decoder.Decode allocate once
at the start rather than twice.

                                │    old     │               new                │
                                │ allocs/op  │ allocs/op   vs base              │
    Unmarshal/HugoFrontMatter-8   141.0 ± 0%   140.0 ± 0%  -0.71% (p=0.002 n=6)
2024-05-24 14:49:06 -04:00
Daniel Martí 9b890cf9c5 go.mod: bump minimum and language to 1.21 (#949)
* go.mod: bump minimum and language to 1.21

CI only tests Go 1.21 and 1.22, and older versions of Go are no longer
getting any bug or security fixes, so advertise that we only support
Go 1.21 or later via go.mod.

While here, ensure the module is tidy and resolve deprecation warnings,
and remove now-unnecessary Go version build tags.

* replace sort.Slice with slices.SortFunc

The latter is more efficient, and allocates less, since sort.Slice
needs to go through sort.Interface which causes allocations.

    goos: linux
    goarch: amd64
    pkg: github.com/pelletier/go-toml/v2/benchmark
    cpu: AMD Ryzen 7 PRO 5850U with Radeon Graphics
                              │     old     │                new                 │
                              │   sec/op    │   sec/op     vs base               │
    Marshal/HugoFrontMatter-8   7.612µ ± 1%   6.730µ ± 1%  -11.59% (p=0.002 n=6)

                              │     old      │                 new                 │
                              │     B/s      │     B/s       vs base               │
    Marshal/HugoFrontMatter-8   65.52Mi ± 1%   74.11Mi ± 1%  +13.11% (p=0.002 n=6)

                              │     old      │                new                 │
                              │     B/op     │     B/op      vs base              │
    Marshal/HugoFrontMatter-8   5.672Ki ± 0%   5.266Ki ± 0%  -7.16% (p=0.002 n=6)

                              │    old     │                new                │
                              │ allocs/op  │ allocs/op   vs base               │
    Marshal/HugoFrontMatter-8   85.00 ± 0%   73.00 ± 0%  -14.12% (p=0.002 n=6)
2024-05-24 10:58:39 -04:00
大可 a3d5a0bb53 fix: sync pool race condition (#947) 2024-04-29 06:02:54 -04:00
14 changed files with 284 additions and 70 deletions
+1 -1
View File
@@ -15,6 +15,6 @@ jobs:
- name: Setup go
uses: actions/setup-go@v5
with:
go-version: "1.22"
go-version: "1.23"
- name: Run tests with coverage
run: ./ci.sh coverage -d "${GITHUB_BASE_REF-HEAD}"
+4 -4
View File
@@ -22,7 +22,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.22"
go-version: "1.23"
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
@@ -30,10 +30,10 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v3
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: latest
args: release ${{ inputs.args }} --rm-dist
version: '~> v2'
args: release ${{ inputs.args }} --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+2 -2
View File
@@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
os: [ 'ubuntu-latest', 'windows-latest', 'macos-latest', 'macos-14' ]
go: [ '1.21', '1.22' ]
go: [ '1.22', '1.23' ]
runs-on: ${{ matrix.os }}
name: ${{ matrix.go }}/${{ matrix.os }}
steps:
@@ -27,6 +27,6 @@ jobs:
run: go test -race ./...
release-check:
if: ${{ github.ref != 'refs/heads/v2' }}
uses: pelletier/go-toml/.github/workflows/release.yml@v2
uses: ./.github/workflows/release.yml
with:
args: --snapshot
+1
View File
@@ -1,3 +1,4 @@
version: 2
before:
hooks:
- go mod tidy
+1 -1
View File
@@ -565,7 +565,7 @@ complete solutions exist out there.
## Versioning
Expect for parts explicitely marked otherwise, go-toml follows [Semantic
Expect for parts explicitly marked otherwise, go-toml follows [Semantic
Versioning](https://semver.org). The supported version of
[TOML](https://github.com/toml-lang/toml) is indicated at the beginning of this
document. The last two major versions of Go are supported (see [Go Release
-3
View File
@@ -1,6 +1,3 @@
//go:build go1.18 || go1.19 || go1.20 || go1.21 || go1.22
// +build go1.18 go1.19 go1.20 go1.21 go1.22
package toml_test
import (
+7 -1
View File
@@ -1,5 +1,11 @@
module github.com/pelletier/go-toml/v2
go 1.16
go 1.21.0
require github.com/stretchr/testify v1.9.0
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
-9
View File
@@ -1,19 +1,10 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+5 -7
View File
@@ -57,7 +57,11 @@ type SeenTracker struct {
currentIdx int
}
var pool sync.Pool
var pool = sync.Pool{
New: func() interface{} {
return &SeenTracker{}
},
}
func (s *SeenTracker) reset() {
// Always contains a root element at index 0.
@@ -331,12 +335,6 @@ func (s *SeenTracker) checkArray(node *unstable.Node) (first bool, err error) {
}
func (s *SeenTracker) checkInlineTable(node *unstable.Node) (first bool, err error) {
if pool.New == nil {
pool.New = func() interface{} {
return &SeenTracker{}
}
}
s = pool.Get().(*SeenTracker)
s.reset()
+18 -6
View File
@@ -8,7 +8,7 @@ import (
"io"
"math"
"reflect"
"sort"
"slices"
"strconv"
"strings"
"time"
@@ -280,7 +280,7 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e
}
hasTextMarshaler := v.Type().Implements(textMarshalerType)
if hasTextMarshaler || (v.CanAddr() && reflect.PtrTo(v.Type()).Implements(textMarshalerType)) {
if hasTextMarshaler || (v.CanAddr() && reflect.PointerTo(v.Type()).Implements(textMarshalerType)) {
if !hasTextMarshaler {
v = v.Addr()
}
@@ -631,6 +631,18 @@ func (enc *Encoder) keyToString(k reflect.Value) (string, error) {
return "", fmt.Errorf("toml: error marshalling key %v from text: %w", k, err)
}
return string(keyB), nil
case keyType.Kind() == reflect.Int || keyType.Kind() == reflect.Int8 || keyType.Kind() == reflect.Int16 || keyType.Kind() == reflect.Int32 || keyType.Kind() == reflect.Int64:
return strconv.FormatInt(k.Int(), 10), nil
case keyType.Kind() == reflect.Uint || keyType.Kind() == reflect.Uint8 || keyType.Kind() == reflect.Uint16 || keyType.Kind() == reflect.Uint32 || keyType.Kind() == reflect.Uint64:
return strconv.FormatUint(k.Uint(), 10), nil
case keyType.Kind() == reflect.Float32:
return strconv.FormatFloat(k.Float(), 'f', -1, 32), nil
case keyType.Kind() == reflect.Float64:
return strconv.FormatFloat(k.Float(), 'f', -1, 64), nil
}
return "", fmt.Errorf("toml: type %s is not supported as a map key", keyType.Kind())
}
@@ -668,8 +680,8 @@ func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte
}
func sortEntriesByKey(e []entry) {
sort.Slice(e, func(i, j int) bool {
return e[i].Key < e[j].Key
slices.SortFunc(e, func(a, b entry) int {
return strings.Compare(a.Key, b.Key)
})
}
@@ -732,7 +744,7 @@ func walkStruct(ctx encoderCtx, t *table, v reflect.Value) {
if fieldType.Anonymous {
if fieldType.Type.Kind() == reflect.Struct {
walkStruct(ctx, t, f)
} else if fieldType.Type.Kind() == reflect.Pointer && !f.IsNil() && f.Elem().Kind() == reflect.Struct {
} else if fieldType.Type.Kind() == reflect.Ptr && !f.IsNil() && f.Elem().Kind() == reflect.Struct {
walkStruct(ctx, t, f.Elem())
}
continue
@@ -951,7 +963,7 @@ func willConvertToTable(ctx encoderCtx, v reflect.Value) bool {
if !v.IsValid() {
return false
}
if v.Type() == timeType || v.Type().Implements(textMarshalerType) || (v.Kind() != reflect.Ptr && v.CanAddr() && reflect.PtrTo(v.Type()).Implements(textMarshalerType)) {
if v.Type() == timeType || v.Type().Implements(textMarshalerType) || (v.Kind() != reflect.Ptr && v.CanAddr() && reflect.PointerTo(v.Type()).Implements(textMarshalerType)) {
return false
}
+58 -3
View File
@@ -587,13 +587,69 @@ foo = 42
`,
},
{
desc: "invalid map key",
desc: "int map key",
v: map[int]interface{}{1: "a"},
expected: `1 = 'a'
`,
},
{
desc: "int8 map key",
v: map[int8]interface{}{1: "a"},
expected: `1 = 'a'
`,
},
{
desc: "int64 map key",
v: map[int64]interface{}{1: "a"},
expected: `1 = 'a'
`,
},
{
desc: "uint map key",
v: map[uint]interface{}{1: "a"},
expected: `1 = 'a'
`,
},
{
desc: "uint8 map key",
v: map[uint8]interface{}{1: "a"},
expected: `1 = 'a'
`,
},
{
desc: "uint64 map key",
v: map[uint64]interface{}{1: "a"},
expected: `1 = 'a'
`,
},
{
desc: "float32 map key",
v: map[float32]interface{}{
1.1: "a",
1.0020: "b",
},
expected: `'1.002' = 'b'
'1.1' = 'a'
`,
},
{
desc: "float64 map key",
v: map[float64]interface{}{
1.1: "a",
1.0020: "b",
},
expected: `'1.002' = 'b'
'1.1' = 'a'
`,
},
{
desc: "invalid map key",
v: map[struct{ int }]interface{}{{1}: "a"},
err: true,
},
{
desc: "invalid map key but empty",
v: map[int]interface{}{},
v: map[struct{ int }]interface{}{},
expected: "",
},
{
@@ -1565,7 +1621,6 @@ func ExampleMarshal() {
// configuration file that has commented out sections (example from
// go-graphite/graphite-clickhouse).
func ExampleMarshal_commented() {
type Common struct {
Listen string `toml:"listen" comment:"general listener"`
PprofListen string `toml:"pprof-listen" comment:"listener to serve /debug/pprof requests. '-pprof' argument overrides it"`
-3
View File
@@ -1,6 +1,3 @@
//go:build go1.18 || go1.19 || go1.20 || go1.21 || go1.22
// +build go1.18 go1.19 go1.20 go1.21 go1.22
package ossfuzz
import (
+34 -11
View File
@@ -5,9 +5,9 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"reflect"
"strconv"
"strings"
"sync/atomic"
"time"
@@ -21,10 +21,8 @@ import (
//
// It is a shortcut for Decoder.Decode() with the default options.
func Unmarshal(data []byte, v interface{}) error {
p := unstable.Parser{}
p.Reset(data)
d := decoder{p: &p}
d := decoder{}
d.p.Reset(data)
return d.FromParser(v)
}
@@ -117,27 +115,25 @@ func (d *Decoder) EnableUnmarshalerInterface() *Decoder {
// Inline Table -> same as Table
// Array of Tables -> same as Array and Table
func (d *Decoder) Decode(v interface{}) error {
b, err := ioutil.ReadAll(d.r)
b, err := io.ReadAll(d.r)
if err != nil {
return fmt.Errorf("toml: %w", err)
}
p := unstable.Parser{}
p.Reset(b)
dec := decoder{
p: &p,
strict: strict{
Enabled: d.strict,
},
unmarshalerInterface: d.unmarshalerInterface,
}
dec.p.Reset(b)
return dec.FromParser(v)
}
type decoder struct {
// Which parser instance in use for this decoding session.
p *unstable.Parser
p unstable.Parser
// Flag indicating that the current expression is stashed.
// If set to true, calling nextExpr will not actually pull a new expression
@@ -1078,12 +1074,39 @@ func (d *decoder) keyFromData(keyType reflect.Type, data []byte) (reflect.Value,
}
return mk, nil
case reflect.PtrTo(keyType).Implements(textUnmarshalerType):
case reflect.PointerTo(keyType).Implements(textUnmarshalerType):
mk := reflect.New(keyType)
if err := mk.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil {
return reflect.Value{}, fmt.Errorf("toml: error unmarshalling key type %s from text: %w", stringType, err)
}
return mk.Elem(), nil
case keyType.Kind() == reflect.Int || keyType.Kind() == reflect.Int8 || keyType.Kind() == reflect.Int16 || keyType.Kind() == reflect.Int32 || keyType.Kind() == reflect.Int64:
key, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from integer: %w", stringType, err)
}
return reflect.ValueOf(key).Convert(keyType), nil
case keyType.Kind() == reflect.Uint || keyType.Kind() == reflect.Uint8 || keyType.Kind() == reflect.Uint16 || keyType.Kind() == reflect.Uint32 || keyType.Kind() == reflect.Uint64:
key, err := strconv.ParseUint(string(data), 10, 64)
if err != nil {
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from unsigned integer: %w", stringType, err)
}
return reflect.ValueOf(key).Convert(keyType), nil
case keyType.Kind() == reflect.Float32:
key, err := strconv.ParseFloat(string(data), 32)
if err != nil {
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from float: %w", stringType, err)
}
return reflect.ValueOf(float32(key)), nil
case keyType.Kind() == reflect.Float64:
key, err := strconv.ParseFloat(string(data), 64)
if err != nil {
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from float: %w", stringType, err)
}
return reflect.ValueOf(float64(key)), nil
}
return reflect.Value{}, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", stringType, keyType)
}
+153 -19
View File
@@ -205,7 +205,6 @@ func TestUnmarshal_Floats(t *testing.T) {
testFn func(t *testing.T, v float64)
err bool
}{
{
desc: "float pi",
input: `3.1415`,
@@ -840,8 +839,10 @@ huey = 'dewey'
return test{
target: &doc{},
expected: &doc{A: []interface{}{"0", "1", "2", "3", "4", "5", "6",
"7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17"}},
expected: &doc{A: []interface{}{
"0", "1", "2", "3", "4", "5", "6",
"7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17",
}},
}
},
},
@@ -1696,16 +1697,6 @@ B = "data"`,
}
},
},
{
desc: "empty map into map with invalid key type",
input: ``,
gen: func() test {
return test{
target: &map[int]string{},
expected: &map[int]string{},
}
},
},
{
desc: "into map with convertible key type",
input: `A = "hello"`,
@@ -1942,6 +1933,150 @@ B = "data"`,
}
},
},
{
desc: "into map of int to string",
input: `1 = "a"`,
gen: func() test {
return test{
target: &map[int]string{},
expected: &map[int]string{1: "a"},
assert: func(t *testing.T, test test) {
assert.Equal(t, test.expected, test.target)
},
}
},
},
{
desc: "into map of int8 to string",
input: `1 = "a"`,
gen: func() test {
return test{
target: &map[int8]string{},
expected: &map[int8]string{1: "a"},
assert: func(t *testing.T, test test) {
assert.Equal(t, test.expected, test.target)
},
}
},
},
{
desc: "into map of int64 to string",
input: `1 = "a"`,
gen: func() test {
return test{
target: &map[int64]string{},
expected: &map[int64]string{1: "a"},
assert: func(t *testing.T, test test) {
assert.Equal(t, test.expected, test.target)
},
}
},
},
{
desc: "into map of uint to string",
input: `1 = "a"`,
gen: func() test {
return test{
target: &map[uint]string{},
expected: &map[uint]string{1: "a"},
assert: func(t *testing.T, test test) {
assert.Equal(t, test.expected, test.target)
},
}
},
},
{
desc: "into map of uint8 to string",
input: `1 = "a"`,
gen: func() test {
return test{
target: &map[uint8]string{},
expected: &map[uint8]string{1: "a"},
assert: func(t *testing.T, test test) {
assert.Equal(t, test.expected, test.target)
},
}
},
},
{
desc: "into map of uint64 to string",
input: `1 = "a"`,
gen: func() test {
return test{
target: &map[uint64]string{},
expected: &map[uint64]string{1: "a"},
assert: func(t *testing.T, test test) {
assert.Equal(t, test.expected, test.target)
},
}
},
},
{
desc: "into map of uint with invalid key",
input: `-1 = "a"`,
gen: func() test {
return test{
target: &map[uint]string{},
err: true,
}
},
},
{
desc: "into map of float64 to string",
input: `'1.01' = "a"`,
gen: func() test {
return test{
target: &map[float64]string{},
expected: &map[float64]string{1.01: "a"},
assert: func(t *testing.T, test test) {
assert.Equal(t, test.expected, test.target)
},
}
},
},
{
desc: "into map of float64 with invalid key",
input: `key = "a"`,
gen: func() test {
return test{
target: &map[float64]string{},
err: true,
}
},
},
{
desc: "into map of float32 to string",
input: `'1.01' = "a"`,
gen: func() test {
return test{
target: &map[float32]string{},
expected: &map[float32]string{1.01: "a"},
assert: func(t *testing.T, test test) {
assert.Equal(t, test.expected, test.target)
},
}
},
},
{
desc: "into map of float32 with invalid key",
input: `key = "a"`,
gen: func() test {
return test{
target: &map[float32]string{},
err: true,
}
},
},
{
desc: "invalid map key type",
input: `1 = "a"`,
gen: func() test {
return test{
target: &map[struct{ int }]string{},
err: true,
}
},
},
}
for _, e := range examples {
@@ -2653,7 +2788,7 @@ func TestIssue772(t *testing.T) {
FileHandling `toml:"filehandling"`
}
var defaultConfigFile = []byte(`
defaultConfigFile := []byte(`
[filehandling]
pattern = "reach-masterdev-"`)
@@ -2750,7 +2885,7 @@ func TestIssue866(t *testing.T) {
PipelineMapping map[string]*Pipeline `toml:"pipelines"`
}
var badToml = `
badToml := `
[pipelines.register]
mapping.inst.req = [
["param1", "value1"],
@@ -2768,7 +2903,7 @@ mapping.inst.res = [
t.Fatal("unmarshal failed with mismatch value")
}
var goodTooToml = `
goodTooToml := `
[pipelines.register]
mapping.inst.req = [
["param1", "value1"],
@@ -2783,7 +2918,7 @@ mapping.inst.req = [
t.Fatal("unmarshal failed with mismatch value")
}
var goodToml = `
goodToml := `
[pipelines.register.mapping.inst]
req = [
["param1", "value1"],
@@ -3362,7 +3497,7 @@ func TestOmitEmpty(t *testing.T) {
X []elem `toml:",inline"`
}
d := doc{X: []elem{elem{
d := doc{X: []elem{{
Foo: "test",
Inner: inner{
V: "alue",
@@ -3785,7 +3920,6 @@ func (k *CustomUnmarshalerKey) UnmarshalTOML(value *unstable.Node) error {
}
k.A = item
return nil
}
func TestUnmarshal_CustomUnmarshaler(t *testing.T) {