Skip to content

Commit 70f1489

Browse files
committed
Fixed collection sequence helpers so nil iter.Seq values no longer panic and instead behave like empty sequences.
1 parent 2eb1651 commit 70f1489

13 files changed

Lines changed: 78 additions & 16 deletions

File tree

changes/20260608103100.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed collection sequence helpers so nil `iter.Seq` values no longer panic and instead behave like empty sequences.

utils/collection/conditions.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,7 @@ func Any(slice []bool) bool {
128128

129129
// AnySequence returns true if there is at least one element of the slice which is true.
130130
func AnySequence(seq iter.Seq[bool]) bool {
131-
if seq == nil {
132-
return false
133-
}
134-
for e := range seq {
131+
for e := range sequenceOrEmpty(seq) {
135132
if e {
136133
return true
137134
}
@@ -147,7 +144,7 @@ func AnyTrue(values ...bool) bool {
147144
// AnyFalseSequence returns true if there is at least one element of the sequence which is false. If the sequence is empty, it also returns true.
148145
func AnyFalseSequence(eq iter.Seq[bool]) bool {
149146
hasElements := false
150-
for e := range eq {
147+
for e := range sequenceOrEmpty(eq) {
151148
hasElements = true
152149
if !e {
153150
return true

utils/collection/filter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ func FilterRef[S ~[]E, E any](s S, f FilterRefFunc[E]) S {
129129
// - https://pkg.go.dev/iter
130130
func FilterSequence[E any](s iter.Seq[E], f Predicate[E]) (result iter.Seq[E]) {
131131
return func(yield func(E) bool) {
132-
for v := range s {
132+
for v := range sequenceOrEmpty(s) {
133133
if f(v) && !yield(v) {
134134
return
135135
}

utils/collection/find.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,8 @@ func Find(slice *[]string, val string) (int, bool) {
2929
// element and true when a match is found. If elements is nil or no
3030
// match exists, it returns -1 and false.
3131
func FindInSequence[E any](elements iter.Seq[E], predicate Predicate[E]) (int, bool) {
32-
if elements == nil {
33-
return -1, false
34-
}
3532
idx := atomic.NewUint64(0)
36-
for e := range elements {
33+
for e := range sequenceOrEmpty(elements) {
3734
if predicate(e) {
3835
return safecast.ToInt(idx.Load()), true
3936
}

utils/collection/iterator.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ type OperationWithoutErrorFunc[E any] func(E)
2424
// OperationWithoutErrorRefFunc defines an operation on a pointer that does not return an error.
2525
type OperationWithoutErrorRefFunc[E any] func(*E)
2626

27+
// EmptySequence returns a sequence that yields no values.
28+
func EmptySequence[T any]() iter.Seq[T] {
29+
return func(func(T) bool) {}
30+
}
31+
32+
func sequenceOrEmpty[T any](s iter.Seq[T]) iter.Seq[T] {
33+
if s == nil {
34+
return EmptySequence[T]()
35+
}
36+
return s
37+
}
38+
2739
// toOperationFunc adapts an OperationRefFunc to an OperationFunc by
2840
// converting the value to an optional reference.
2941
func toOperationFunc[E any](f OperationRefFunc[E]) OperationFunc[E] {
@@ -53,7 +65,7 @@ func convertOperationWithoutError[E any](f OperationWithoutErrorFunc[E]) Operati
5365
// returns a non-EOF error, iteration stops and that error is returned.
5466
// If f returns EOF, the EOF is ignored and iteration ends without error.
5567
func Each[T any](s iter.Seq[T], f OperationFunc[T]) error {
56-
for e := range s {
68+
for e := range sequenceOrEmpty(s) {
5769
err := f(e)
5870
if err != nil {
5971
err = commonerrors.Ignore(err, commonerrors.ErrEOF)

utils/collection/mapping.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func MapSequence[T1 any, T2 any](s iter.Seq[T1], f MapFunc[T1, T2]) iter.Seq[T2]
6060
// value.
6161
func MapSequenceWithError[T1 any, T2 any](s iter.Seq[T1], f MapWithErrorFunc[T1, T2]) iter.Seq[T2] {
6262
return func(yield func(T2) bool) {
63-
for v := range s {
63+
for v := range sequenceOrEmpty(s) {
6464
mapped, err := f(v)
6565
if err != nil || !yield(mapped) {
6666
return
@@ -89,7 +89,7 @@ func MapSequenceRef[T1 any, T2 any](s iter.Seq[T1], f MapRefFunc[T1, T2]) iter.S
8989
// combined with validation or other error-producing transformations.
9090
func MapSequenceRefWithError[T1 any, T2 any](s iter.Seq[T1], f MapRefWithErrorFunc[T1, T2]) iter.Seq[T2] {
9191
return func(yield func(T2) bool) {
92-
for v := range s {
92+
for v := range sequenceOrEmpty(s) {
9393
mapped, err := f(field.ToOptionalOrNilIfEmpty(v))
9494
if err != nil || mapped == nil || !yield(*mapped) {
9595
return

utils/collection/queue/channel_queue.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"iter"
55
"slices"
66

7+
"github.com/ARM-software/golang-utils/utils/collection"
78
"github.com/ARM-software/golang-utils/utils/commonerrors"
89
)
910

@@ -38,6 +39,9 @@ func (q *ChanQueue[T]) Enqueue(values ...T) {
3839
// EnqueueSequence adds a sequence to the queue.
3940
// This blocks if the queue is full.
4041
func (q *ChanQueue[T]) EnqueueSequence(seq iter.Seq[T]) {
42+
if seq == nil {
43+
seq = collection.EmptySequence[T]()
44+
}
4145
for v := range seq {
4246
q.ch <- v
4347
}

utils/collection/queue/queue.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package queue
33
import (
44
"iter"
55
"slices"
6+
7+
"github.com/ARM-software/golang-utils/utils/collection"
68
)
79

810
// NewQueue returns a Queue which is not thread safe
@@ -78,6 +80,9 @@ func (s *Queue[T]) Enqueue(value ...T) {
7880
}
7981

8082
func (s *Queue[T]) EnqueueSequence(seq iter.Seq[T]) {
83+
if seq == nil {
84+
seq = collection.EmptySequence[T]()
85+
}
8186
for v := range seq {
8287
s.enqueue(v)
8388
}

utils/collection/range.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func RangeSequence[T safecast.IConvertible](start, stop T, step *T) iter.Seq[T]
4545
func rangeSequence[T safecast.IConvertible](start, stop T, step *T) (it iter.Seq[T], length int) {
4646
s := field.Optional[T](step, 1)
4747
if s == 0 {
48-
it = func(yield func(T) bool) {}
48+
it = EmptySequence[T]()
4949
return
5050
}
5151
length = determineRangeLength[T](start, stop, s)

utils/collection/reduce.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func Reduce[T1, T2 any](s []T1, accumulator T2, f ReduceFunc[T1, T2]) T2 {
4747
// - https://pkg.go.dev/iter
4848
func ReducesSequence[T1, T2 any](s iter.Seq[T1], accumulator T2, f ReduceFunc[T1, T2]) T2 {
4949
result := accumulator
50-
for e := range s {
50+
for e := range sequenceOrEmpty(s) {
5151
result = f(result, e)
5252
}
5353
return result

0 commit comments

Comments
 (0)