diff --git a/common/types/list.go b/common/types/list.go index 324c0f96..028770ed 100644 --- a/common/types/list.go +++ b/common/types/list.go @@ -126,16 +126,7 @@ func (l *baseList) Add(other ref.Val) ref.Val { if !ok { return MaybeNoSuchOverloadErr(other) } - if l.Size() == IntZero { - return other - } - if otherList.Size() == IntZero { - return l - } - return &concatList{ - Adapter: l.Adapter, - prevList: l, - nextList: otherList} + return newConcatList(l.Adapter, l, otherList) } // Contains implements the traits.Container interface method. @@ -353,9 +344,27 @@ func (l *mutableList) ToImmutableList() traits.Lister { // The `Adapter` enables native type to CEL type conversions. type concatList struct { Adapter - value any - prevList traits.Lister - nextList traits.Lister + value any + prevList traits.Lister + nextList traits.Lister + cachedSize ref.Val +} + +func newConcatList(adapter Adapter, prevList, nextList traits.Lister) ref.Val { + prevSize := prevList.Size().(Int) + nextSize := nextList.Size().(Int) + if prevSize == IntZero { + return nextList.(ref.Val) + } + if nextSize == IntZero { + return prevList.(ref.Val) + } + return &concatList{ + Adapter: adapter, + prevList: prevList, + nextList: nextList, + cachedSize: prevSize.Add(nextSize), + } } // Add implements the traits.Adder interface method. @@ -364,16 +373,7 @@ func (l *concatList) Add(other ref.Val) ref.Val { if !ok { return MaybeNoSuchOverloadErr(other) } - if l.Size() == IntZero { - return other - } - if otherList.Size() == IntZero { - return l - } - return &concatList{ - Adapter: l.Adapter, - prevList: l, - nextList: otherList} + return newConcatList(l.Adapter, l, otherList) } // Contains implements the traits.Container interface method. @@ -477,7 +477,7 @@ func (l *concatList) Iterator() traits.Iterator { // Size implements the traits.Sizer interface method. func (l *concatList) Size() ref.Val { - return l.prevList.Size().(Int).Add(l.nextList.Size()) + return l.cachedSize } // String converts the concatenated list to a human-readable string. diff --git a/common/types/list_test.go b/common/types/list_test.go index d99cdd00..ca134b71 100644 --- a/common/types/list_test.go +++ b/common/types/list_test.go @@ -910,3 +910,23 @@ func validateIterator123(t *testing.T, list traits.Lister) { t.Errorf("Iterator did not iterate until last value") } } + +func TestConcatListSizeCached(t *testing.T) { + reg := newTestRegistry(t) + // Build a deep chain of concat lists + var list traits.Lister = NewDynamicList(reg, []int64{0}) + for i := 0; i < 200; i++ { + list = list.Add(NewDynamicList(reg, []int64{0})).(traits.Lister) + } + // Size() should return instantly since it's precomputed + size := list.Size() + if size != Int(201) { + t.Errorf("Expected size 201, got %v", size) + } + // Call Size() many times to confirm no quadratic behavior + for i := 0; i < 1000; i++ { + if list.Size() != Int(201) { + t.Errorf("Size() returned inconsistent value on call %d", i) + } + } +}