Skip to content

Commit 7962a47

Browse files
authored
Use new slices package available in Go 1.21 (#289)
1 parent c9e94ad commit 7962a47

10 files changed

+69
-88
lines changed

arshal_default.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
"fmt"
1414
"math"
1515
"reflect"
16-
"sort"
16+
"slices"
1717
"strconv"
1818
"sync"
1919
)
@@ -714,9 +714,8 @@ func makeMapArshaler(t reflect.Type) *arshaler {
714714
// TODO: If AllowDuplicateNames is enabled, then sort according
715715
// to reflect.Value as well if the names are equal.
716716
// See internal/fmtsort.
717-
// TODO(https://go.dev/issue/47619): Use slices.SortFunc instead.
718-
sort.Slice(members, func(i, j int) bool {
719-
return lessUTF16(members[i].name, members[j].name)
717+
slices.SortFunc(members, func(x, y member) int {
718+
return compareUTF16(x.name, y.name)
720719
})
721720
for _, member := range members {
722721
if err := enc.WriteToken(String(member.name)); err != nil {

decode.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"errors"
1010
"io"
1111
"math"
12+
"slices"
1213
"strconv"
1314
"unicode/utf16"
1415
"unicode/utf8"
@@ -1301,10 +1302,7 @@ func consumeStringResumable(flags *valueFlags, b []byte, resumeOffset int, valid
13011302
// but the error will be specified as having encountered such an error.
13021303
// The input must be an entire JSON string with no surrounding whitespace.
13031304
func unescapeString[Bytes ~[]byte | ~string](dst []byte, src Bytes) (v []byte, err error) {
1304-
// TODO(https://go.dev/issue/57433): Use slices.Grow.
1305-
if dst == nil {
1306-
dst = make([]byte, 0, len(src))
1307-
}
1305+
dst = slices.Grow(dst, len(src))
13081306

13091307
// Consume the leading double quote.
13101308
var i, n int

encode.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io"
1010
"math"
1111
"math/bits"
12+
"slices"
1213
"strconv"
1314
"unicode/utf16"
1415
"unicode/utf8"
@@ -1002,10 +1003,7 @@ func (e *Encoder) StackPointer() string {
10021003
// except for whether a forward solidus '/' may be formatted as '\/' and
10031004
// the casing of hexadecimal Unicode escape sequences.
10041005
func appendString[Bytes ~[]byte | ~string](dst []byte, src Bytes, validateUTF8 bool, escapeRune func(rune) bool) ([]byte, error) {
1005-
// TODO(https://go.dev/issue/57433): Use slices.Grow.
1006-
if dst == nil {
1007-
dst = make([]byte, 0, len(`"`)+len(src)+len(`"`))
1008-
}
1006+
dst = slices.Grow(dst, len(`"`)+len(src)+len(`"`))
10091007

10101008
var i, n int
10111009
var hasInvalidUTF8 bool

fields.go

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,16 @@
55
package json
66

77
import (
8+
"cmp"
89
"errors"
910
"fmt"
1011
"io"
1112
"reflect"
12-
"sort"
13+
"slices"
1314
"strconv"
1415
"strings"
1516
"unicode"
1617
"unicode/utf8"
17-
18-
"golang.org/x/exp/slices"
1918
)
2019

2120
var errIgnoredField = errors.New("ignored field")
@@ -228,14 +227,18 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) {
228227
// or the one that is uniquely tagged with a JSON name.
229228
// Otherwise, no dominant field exists for the set.
230229
flattened := allFields[:0]
231-
sort.Slice(allFields, func(i, j int) bool {
232-
switch fi, fj := allFields[i], allFields[j]; {
233-
case fi.name != fj.name:
234-
return fi.name < fj.name
235-
case len(fi.index) != len(fj.index):
236-
return len(fi.index) < len(fj.index)
230+
slices.SortFunc(allFields, func(x, y structField) int {
231+
switch {
232+
case x.name != y.name:
233+
return strings.Compare(x.name, y.name)
234+
case len(x.index) != len(y.index):
235+
return cmp.Compare(len(x.index), len(y.index))
236+
case x.hasName && !y.hasName:
237+
return -1
238+
case !x.hasName && y.hasName:
239+
return +1
237240
default:
238-
return fi.hasName && !fj.hasName
241+
return 0 // TODO(https://go.dev/issue/61643): Compare bools better.
239242
}
240243
})
241244
for len(allFields) > 0 {
@@ -252,17 +255,17 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) {
252255
// Sort the fields according to a breadth-first ordering
253256
// so that we can re-number IDs with the smallest possible values.
254257
// This optimizes use of uintSet such that it fits in the 64-entry bit set.
255-
sort.Slice(flattened, func(i, j int) bool {
256-
return flattened[i].id < flattened[j].id
258+
slices.SortFunc(flattened, func(x, y structField) int {
259+
return cmp.Compare(x.id, y.id)
257260
})
258261
for i := range flattened {
259262
flattened[i].id = i
260263
}
261264

262265
// Sort the fields according to a depth-first ordering
263266
// as the typical order that fields are marshaled.
264-
sort.Slice(flattened, func(i, j int) bool {
265-
return slices.Compare(flattened[i].index, flattened[j].index) < 0
267+
slices.SortFunc(flattened, func(x, y structField) int {
268+
return slices.Compare(x.index, y.index)
266269
})
267270

268271
// Compute the mapping of fields in the byActualName map.
@@ -281,8 +284,8 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) {
281284
if len(fields) > 1 {
282285
// The precedence order for conflicting nocase names
283286
// is by breadth-first order, rather than depth-first order.
284-
sort.Slice(fields, func(i, j int) bool {
285-
return fields[i].id < fields[j].id
287+
slices.SortFunc(fields, func(x, y *structField) int {
288+
return cmp.Compare(x.id, y.id)
286289
})
287290
fs.byFoldedName[foldedName] = fields
288291
}

go.mod

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,3 @@
44
module github.com/go-json-experiment/json
55

66
go 1.21
7-
8-
require golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +0,0 @@
1-
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
2-
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=

pools.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"bytes"
99
"io"
1010
"math/bits"
11-
"sort"
11+
"slices"
1212
"sync"
1313
)
1414

@@ -173,10 +173,5 @@ func putStrings(s *stringSlice) {
173173

174174
// Sort sorts the string slice according to RFC 8785, section 3.2.3.
175175
func (ss *stringSlice) Sort() {
176-
// TODO(https://go.dev/issue/47619): Use slices.SortFunc instead.
177-
sort.Sort(ss)
176+
slices.SortFunc(*ss, func(x, y string) int { return compareUTF16(x, y) })
178177
}
179-
180-
func (ss *stringSlice) Len() int { return len(*ss) }
181-
func (ss *stringSlice) Less(i, j int) bool { return lessUTF16((*ss)[i], (*ss)[j]) }
182-
func (ss *stringSlice) Swap(i, j int) { (*ss)[i], (*ss)[j] = (*ss)[j], (*ss)[i] }

testdata_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import (
88
"bytes"
99
"compress/gzip"
1010
"io"
11+
"io/fs"
1112
"os"
1213
"path/filepath"
13-
"sort"
14+
"slices"
1415
"strings"
1516
"sync"
1617
"testing"
@@ -34,7 +35,7 @@ func jsonTestdata() []jsonTestdataEntry {
3435
if err != nil {
3536
panic(err)
3637
}
37-
sort.Slice(fis, func(i, j int) bool { return fis[i].Name() < fis[j].Name() })
38+
slices.SortFunc(fis, func(x, y fs.DirEntry) int { return strings.Compare(x.Name(), y.Name()) })
3839
for _, fi := range fis {
3940
if !strings.HasSuffix(fi.Name(), ".json.gz") {
4041
break

value.go

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ package json
66

77
import (
88
"bytes"
9+
"cmp"
910
"errors"
1011
"io"
11-
"sort"
12+
"slices"
13+
"strings"
1214
"sync"
1315
"unicode/utf16"
1416
"unicode/utf8"
@@ -204,26 +206,20 @@ type memberName struct {
204206
before, after int64
205207
}
206208

207-
var memberNamePool = sync.Pool{New: func() any { return new(memberNames) }}
209+
var memberNamePool = sync.Pool{New: func() any { return new([]memberName) }}
208210

209-
func getMemberNames() *memberNames {
210-
ns := memberNamePool.Get().(*memberNames)
211+
func getMemberNames() *[]memberName {
212+
ns := memberNamePool.Get().(*[]memberName)
211213
*ns = (*ns)[:0]
212214
return ns
213215
}
214-
func putMemberNames(ns *memberNames) {
216+
func putMemberNames(ns *[]memberName) {
215217
if cap(*ns) < 1<<10 {
216218
clear(*ns) // avoid pinning name
217219
memberNamePool.Put(ns)
218220
}
219221
}
220222

221-
type memberNames []memberName
222-
223-
func (m *memberNames) Len() int { return len(*m) }
224-
func (m *memberNames) Less(i, j int) bool { return lessUTF16((*m)[i].name, (*m)[j].name) }
225-
func (m *memberNames) Swap(i, j int) { (*m)[i], (*m)[j] = (*m)[j], (*m)[i] }
226-
227223
// reorderObjects recursively reorders all object members in place
228224
// according to the ordering specified in RFC 8785, section 3.2.3.
229225
//
@@ -258,7 +254,7 @@ func reorderObjects(d *Decoder, scratch *[]byte) {
258254
afterValue := d.InputOffset()
259255

260256
if isSorted && len(*members) > 0 {
261-
isSorted = lessUTF16(prevName, []byte(name))
257+
isSorted = compareUTF16(prevName, []byte(name)) < 0
262258
}
263259
*members = append(*members, memberName{name, beforeName, afterValue})
264260
prevName = name
@@ -270,8 +266,9 @@ func reorderObjects(d *Decoder, scratch *[]byte) {
270266
if isSorted {
271267
return
272268
}
273-
// TODO(https://go.dev/issue/47619): Use slices.Sort.
274-
sort.Sort(members)
269+
slices.SortFunc(*members, func(x, y memberName) int {
270+
return compareUTF16(x.name, y.name)
271+
})
275272

276273
// Append the reordered members to a new buffer,
277274
// then copy the reordered members back over the original members.
@@ -308,11 +305,11 @@ func reorderObjects(d *Decoder, scratch *[]byte) {
308305
}
309306
}
310307

311-
// lessUTF16 reports whether x is lexicographically less than y according
308+
// compareUTF16 lexicographically compares x to y according
312309
// to the UTF-16 codepoints of the UTF-8 encoded input strings.
313310
// This implements the ordering specified in RFC 8785, section 3.2.3.
314311
// The inputs must be valid UTF-8, otherwise this may panic.
315-
func lessUTF16[Bytes ~[]byte | ~string](x, y Bytes) bool {
312+
func compareUTF16[Bytes ~[]byte | ~string](x, y Bytes) int {
316313
// NOTE: This is an optimized, allocation-free implementation
317314
// of lessUTF16Simple in fuzz_test.go. FuzzLessUTF16 verifies that the
318315
// two implementations agree on the result of comparing any two strings.
@@ -326,15 +323,15 @@ func lessUTF16[Bytes ~[]byte | ~string](x, y Bytes) bool {
326323
for {
327324
if len(x) == 0 || len(y) == 0 {
328325
if len(x) == len(y) && invalidUTF8 {
329-
return string(x0) < string(y0)
326+
return strings.Compare(string(x0), string(y0))
330327
}
331-
return len(x) < len(y)
328+
return cmp.Compare(len(x), len(y))
332329
}
333330

334331
// ASCII fast-path.
335332
if x[0] < utf8.RuneSelf || y[0] < utf8.RuneSelf {
336333
if x[0] != y[0] {
337-
return x[0] < y[0]
334+
return cmp.Compare(x[0], y[0])
338335
}
339336
x, y = x[1:], y[1:]
340337
continue
@@ -357,7 +354,7 @@ func lessUTF16[Bytes ~[]byte | ~string](x, y Bytes) bool {
357354
rx, _ = utf16.EncodeRune(rx)
358355
}
359356
if rx != ry {
360-
return rx < ry
357+
return cmp.Compare(rx, ry)
361358
}
362359
invalidUTF8 = invalidUTF8 || (rx == utf8.RuneError && nx == 1) || (ry == utf8.RuneError && ny == 1)
363360
x, y = x[nx:], y[ny:]

value_test.go

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
package json
66

77
import (
8+
"bytes"
9+
"cmp"
810
"io"
911
"reflect"
12+
"slices"
1013
"strings"
1114
"testing"
1215
"unicode/utf16"
@@ -195,54 +198,45 @@ func TestRawValueMethods(t *testing.T) {
195198
}
196199
}
197200

198-
var lessUTF16Testdata = []string{"", "\r", "1", "\u0080", "\u00f6", "\u20ac", "\U0001f600", "\ufb33"}
201+
var compareUTF16Testdata = []string{"", "\r", "1", "\u0080", "\u00f6", "\u20ac", "\U0001f600", "\ufb33"}
199202

200-
func TestLessUTF16(t *testing.T) {
201-
for i, si := range lessUTF16Testdata {
202-
for j, sj := range lessUTF16Testdata {
203-
got := lessUTF16([]byte(si), []byte(sj))
204-
want := i < j
203+
func TestCompareUTF16(t *testing.T) {
204+
for i, si := range compareUTF16Testdata {
205+
for j, sj := range compareUTF16Testdata {
206+
got := compareUTF16([]byte(si), []byte(sj))
207+
want := cmp.Compare(i, j)
205208
if got != want {
206-
t.Errorf("lessUTF16(%q, %q) = %v, want %v", si, sj, got, want)
209+
t.Errorf("compareUTF16(%q, %q) = %v, want %v", si, sj, got, want)
207210
}
208211
}
209212
}
210213
}
211214

212-
func FuzzLessUTF16(f *testing.F) {
213-
for _, td1 := range lessUTF16Testdata {
214-
for _, td2 := range lessUTF16Testdata {
215+
func FuzzCompareUTF16(f *testing.F) {
216+
for _, td1 := range compareUTF16Testdata {
217+
for _, td2 := range compareUTF16Testdata {
215218
f.Add([]byte(td1), []byte(td2))
216219
}
217220
}
218221

219-
// lessUTF16Simple is identical to lessUTF16,
222+
// compareUTF16Simple is identical to compareUTF16,
220223
// but relies on naively converting a string to a []uint16 codepoints.
221224
// It is easy to verify as correct, but is slow.
222-
lessUTF16Simple := func(x, y []byte) bool {
225+
compareUTF16Simple := func(x, y []byte) int {
223226
ux := utf16.Encode([]rune(string(x)))
224227
uy := utf16.Encode([]rune(string(y)))
225-
// TODO(https://go.dev/issue/57433): Use slices.Compare.
226-
for {
227-
if len(ux) == 0 || len(uy) == 0 {
228-
if len(ux) == len(uy) {
229-
return string(x) < string(y)
230-
}
231-
return len(ux) < len(uy)
232-
}
233-
if ux[0] != uy[0] {
234-
return ux[0] < uy[0]
235-
}
236-
ux, uy = ux[1:], uy[1:]
228+
if n := slices.Compare(ux, uy); n != 0 {
229+
return n
237230
}
231+
return bytes.Compare(x, y) // only occurs for strings with invalid UTF-8
238232
}
239233

240234
f.Fuzz(func(t *testing.T, s1, s2 []byte) {
241235
// Compare the optimized and simplified implementations.
242-
got := lessUTF16(s1, s2)
243-
want := lessUTF16Simple(s1, s2)
236+
got := compareUTF16(s1, s2)
237+
want := compareUTF16Simple(s1, s2)
244238
if got != want {
245-
t.Errorf("lessUTF16(%q, %q) = %v, want %v", s1, s2, got, want)
239+
t.Errorf("compareUTF16(%q, %q) = %v, want %v", s1, s2, got, want)
246240
}
247241
})
248242
}

0 commit comments

Comments
 (0)