diff --git a/cue/types.go b/cue/types.go index e2e426b62..c9cd38725 100644 --- a/cue/types.go +++ b/cue/types.go @@ -39,6 +39,7 @@ import ( "cuelang.org/go/internal/core/runtime" "cuelang.org/go/internal/core/subsume" internaljson "cuelang.org/go/internal/encoding/json" + "cuelang.org/go/internal/iterutil" "cuelang.org/go/internal/types" ) @@ -1125,7 +1126,7 @@ func (v Value) Len() Value { case *adt.Vertex: if x.IsList() { n := &adt.Num{K: adt.IntKind} - n.X.SetInt64(int64(len(x.Elems()))) + n.X.SetInt64(int64(iterutil.Count(x.Elems()))) if x.IsClosedList() { return remakeFinal(v, n) } @@ -1177,7 +1178,7 @@ func (v Value) List() (Iterator, error) { // mustList is like [Value.List], but reusing ctx and leaving it to the caller // to apply defaults and check the kind. func (v Value) mustList(ctx *adt.OpContext) Iterator { - return Iterator{idx: v.idx, ctx: ctx, val: v, arcs: v.v.Elems()} + return Iterator{idx: v.idx, ctx: ctx, val: v, arcs: slices.Collect(v.v.Elems())} } // Null reports an error if v is not null. diff --git a/internal/core/adt/composite.go b/internal/core/adt/composite.go index 6f5977cbd..d7fa791eb 100644 --- a/internal/core/adt/composite.go +++ b/internal/core/adt/composite.go @@ -16,11 +16,13 @@ package adt import ( "fmt" + "iter" "slices" "cuelang.org/go/cue/ast" "cuelang.org/go/cue/errors" "cuelang.org/go/cue/token" + "cuelang.org/go/internal/iterutil" ) // TODO: unanswered questions about structural cycles: @@ -1162,7 +1164,7 @@ func (v *Vertex) Accept(ctx *OpContext, f Feature) bool { switch v.BaseValue.(type) { case *ListMarker: // TODO(perf): use precomputed length. - if f.Index() < len(v.Elems()) { + if f.Index() < iterutil.Count(v.Elems()) { return true } return !v.IsClosedList() @@ -1262,15 +1264,17 @@ func (v *Vertex) LookupRaw(f Feature) *Vertex { } // Elems returns the regular elements of a list. -func (v *Vertex) Elems() []*Vertex { - // TODO: add bookkeeping for where list arcs start and end. - a := make([]*Vertex, 0, len(v.Arcs)) - for _, x := range v.Arcs { - if x.Label.IsInt() { - a = append(a, x) +func (v *Vertex) Elems() iter.Seq[*Vertex] { + return func(yield func(*Vertex) bool) { + // TODO: add bookkeeping for where list arcs start and end. + for _, x := range v.Arcs { + if x.Label.IsInt() { + if !yield(x) { + break + } + } } } - return a } func (v *Vertex) Init(c *OpContext) { diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go index 76a9f36eb..dc5fb08c5 100644 --- a/internal/core/adt/context.go +++ b/internal/core/adt/context.go @@ -16,6 +16,7 @@ package adt import ( "fmt" + "iter" "reflect" "regexp" "sync/atomic" @@ -1042,14 +1043,14 @@ func (c *OpContext) node(orig Node, x Expr, scalar bool, state Flags) *Vertex { } // Elems returns the evaluated elements of a list. -func (c *OpContext) Elems(v Value) []*Vertex { +func (c *OpContext) Elems(v Value) iter.Seq[*Vertex] { list := c.list(v) list.Finalize(c) return list.Elems() } // RawElems returns the elements of the list without evaluating them. -func (c *OpContext) RawElems(v Value) []*Vertex { +func (c *OpContext) RawElems(v Value) iter.Seq[*Vertex] { list := c.list(v) return list.Elems() } diff --git a/internal/core/adt/errors.go b/internal/core/adt/errors.go index 94aa7274a..3c9034607 100644 --- a/internal/core/adt/errors.go +++ b/internal/core/adt/errors.go @@ -36,6 +36,7 @@ import ( "cuelang.org/go/cue/errors" cueformat "cuelang.org/go/cue/format" "cuelang.org/go/cue/token" + "cuelang.org/go/internal/iterutil" ) // ErrorCode indicates the type of error. The type of error may influence @@ -287,7 +288,7 @@ func (v *Vertex) reportFieldError(c *OpContext, pos token.Pos, f Feature, intMsg var err errors.Error if f.IsInt() { - err = c.NewPosf(pos, intMsg, f.Index(), len(v.Elems())) + err = c.NewPosf(pos, intMsg, f.Index(), iterutil.Count(v.Elems())) } else { err = c.NewPosf(pos, stringMsg, label) } diff --git a/internal/core/adt/tasks.go b/internal/core/adt/tasks.go index d4ac100eb..399b15d8e 100644 --- a/internal/core/adt/tasks.go +++ b/internal/core/adt/tasks.go @@ -16,6 +16,7 @@ package adt import ( "fmt" + "slices" "cuelang.org/go/cue/ast" "cuelang.org/go/cue/token" @@ -330,7 +331,7 @@ func processListVertex(c *OpContext, t *task, mode runMode) { l := t.x.(*Vertex) - elems := l.Elems() + elems := slices.Collect(l.Elems()) isClosed := l.IsClosedList() // TODO: Share with code above. diff --git a/internal/core/compile/builtin.go b/internal/core/compile/builtin.go index 81b36df3a..e098242b3 100644 --- a/internal/core/compile/builtin.go +++ b/internal/core/compile/builtin.go @@ -19,6 +19,7 @@ import ( "cuelang.org/go/cue/errors" "cuelang.org/go/internal/core/adt" + "cuelang.org/go/internal/iterutil" ) // This file contains predeclared builtins. @@ -96,7 +97,7 @@ var lenBuiltin = &adt.Builtin{ // This should not happen, but be defensive. return c.NewErrf("unevaluated vertex") case *adt.ListMarker: - return c.NewInt64(int64(len(x.Elems())), v) + return c.NewInt64(int64(iterutil.Count(x.Elems())), v) case *adt.StructMarker: n := 0 @@ -192,14 +193,14 @@ var andBuiltin = &adt.Builtin{ c := call.OpContext() arg := call.Arg(0) - list := c.RawElems(arg) - if len(list) == 0 { - return &adt.Top{} - } + seq := c.RawElems(arg) a := []adt.Value{} - for _, c := range list { + for c := range seq { a = append(a, c) } + if len(a) == 0 { + return &adt.Top{} + } return &adt.Conjunction{Values: a} }, } @@ -214,7 +215,7 @@ var orBuiltin = &adt.Builtin{ args := call.Args() d := []adt.Disjunct{} - for _, c := range c.RawElems(args[0]) { + for c := range c.RawElems(args[0]) { d = append(d, adt.Disjunct{Val: c, Default: false}) } if len(d) == 0 { diff --git a/internal/core/compile/validator.go b/internal/core/compile/validator.go index dbfa6b6fe..f417623fc 100644 --- a/internal/core/compile/validator.go +++ b/internal/core/compile/validator.go @@ -43,11 +43,8 @@ var matchNBuiltin = &adt.Builtin{ } var errs []*adt.Bottom - - constraints := c.Elems(args[2]) - var count, possibleCount int64 - for _, check := range constraints { + for check := range c.Elems(args[2]) { v := adt.Unify(c, self, check) if err := adt.Validate(c, v, finalCfg); err == nil { // TODO: is it always true that the lack of an error signifies diff --git a/internal/core/subsume/vertex.go b/internal/core/subsume/vertex.go index fa7e49648..c5ed81b98 100644 --- a/internal/core/subsume/vertex.go +++ b/internal/core/subsume/vertex.go @@ -16,6 +16,7 @@ package subsume import ( "fmt" + "slices" "cuelang.org/go/internal/core/adt" ) @@ -276,8 +277,8 @@ func (s *subsumer) listVertices(x, y *adt.Vertex) bool { return false } - xElems := x.Elems() - yElems := y.Elems() + xElems := slices.Collect(x.Elems()) + yElems := slices.Collect(y.Elems()) switch { case len(xElems) == len(yElems): diff --git a/internal/iterutil/iter.go b/internal/iterutil/iter.go new file mode 100644 index 000000000..991678b89 --- /dev/null +++ b/internal/iterutil/iter.go @@ -0,0 +1,25 @@ +// Copyright 2025 CUE Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package iterutil + +import "iter" + +func Count[E any](seq iter.Seq[E]) int { + n := 0 + for range seq { + n++ + } + return n +} diff --git a/internal/pkg/context.go b/internal/pkg/context.go index f5e8d21ca..991767d52 100644 --- a/internal/pkg/context.go +++ b/internal/pkg/context.go @@ -311,7 +311,8 @@ func (c *CallCtxt) DecimalList(i int) (a []*apd.Decimal) { return nil } - for j, w := range v.Elems() { + j := 0 + for w := range v.Elems() { w.Finalize(c.ctx) // defensive switch x := adt.Unwrap(adt.Default(w.Value())).(type) { case *adt.Num: @@ -339,6 +340,7 @@ func (c *CallCtxt) DecimalList(i int) (a []*apd.Decimal) { c.Err = &callError{err} return nil } + j++ } return a } @@ -349,7 +351,8 @@ func (c *CallCtxt) StringList(i int) (a []string) { return nil } - for j, w := range v.Elems() { + j := 0 + for w := range v.Elems() { w.Finalize(c.ctx) // defensive switch x := adt.Unwrap(adt.Default(w.Value())).(type) { case *adt.String: @@ -377,6 +380,7 @@ func (c *CallCtxt) StringList(i int) (a []string) { c.Err = &callError{err} return nil } + j++ } return a } diff --git a/internal/pkg/types.go b/internal/pkg/types.go index ef2bc5c94..63fc40923 100644 --- a/internal/pkg/types.go +++ b/internal/pkg/types.go @@ -15,6 +15,8 @@ package pkg import ( + "iter" + "cuelang.org/go/cue" "cuelang.org/go/internal/core/adt" ) @@ -31,7 +33,7 @@ type List struct { } // Elems returns the elements of a list. -func (l *List) Elems() []*adt.Vertex { +func (l *List) Elems() iter.Seq[*adt.Vertex] { return l.node.Elems() } diff --git a/pkg/list/list.go b/pkg/list/list.go index 46ca0789b..08a96fdb0 100644 --- a/pkg/list/list.go +++ b/pkg/list/list.go @@ -24,6 +24,7 @@ import ( "cuelang.org/go/cue/token" "cuelang.org/go/internal/core/adt" "cuelang.org/go/internal/core/eval" + "cuelang.org/go/internal/iterutil" "cuelang.org/go/internal/pkg" "cuelang.org/go/internal/types" "cuelang.org/go/internal/value" @@ -230,7 +231,7 @@ func Reverse(x []cue.Value) []cue.Value { // MinItems reports whether a has at least n items. func MinItems(list pkg.List, n int) (bool, error) { - count := len(list.Elems()) + count := iterutil.Count(list.Elems()) if count >= n { return true, nil } @@ -246,7 +247,7 @@ func MinItems(list pkg.List, n int) (bool, error) { // MaxItems reports whether a has at most n items. func MaxItems(list pkg.List, n int) (bool, error) { - count := len(list.Elems()) + count := iterutil.Count(list.Elems()) if count > n { return false, pkg.ValidationError{B: &adt.Bottom{ Code: adt.EvalError,