Skip to content

Commit

Permalink
starlark: require go1.23 for new iterators (#551)
Browse files Browse the repository at this point in the history
The correct type for new iterators is iter.Seq, which
depends on a go1.23-only package, so we must limit the
new functions to new toolchains.

In principle this is (yet another) breaking change,
but I suspect only users of (pre-)go1.23 will notice,
and they are living on the bleeding edge by choice.

Also, fix a couple of doc links.
  • Loading branch information
adonovan authored May 17, 2024
1 parent f457c4c commit 3792562
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 111 deletions.
118 changes: 118 additions & 0 deletions starlark/iter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright 2024 The Bazel Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build go1.23

package starlark

import (
"fmt"
"iter"
)

func (d *Dict) Entries() iter.Seq2[Value, Value] { return d.ht.entries }

// Elements returns a go1.23 iterator over the elements of the list.
//
// Example:
//
// for elem := range list.Elements() { ... }
func (l *List) Elements() iter.Seq[Value] {
return func(yield func(Value) bool) {
if !l.frozen {
l.itercount++
defer func() { l.itercount-- }()
}
for _, x := range l.elems {
if !yield(x) {
break
}
}
}
}

// Elements returns a go1.23 iterator over the elements of the tuple.
//
// (A Tuple is a slice, so it is of course directly iterable. This
// method exists to provide a fast path for the [Elements] standalone
// function.)
func (t Tuple) Elements() iter.Seq[Value] {
return func(yield func(Value) bool) {
for _, x := range t {
if !yield(x) {
break
}
}
}
}

func (s *Set) Elements() iter.Seq[Value] {
return func(yield func(k Value) bool) {
s.ht.entries(func(k, _ Value) bool { return yield(k) })
}
}

// Elements returns an iterator for the elements of the iterable value.
//
// Example of go1.23 iteration:
//
// for elem := range Elements(iterable) { ... }
//
// Push iterators are provided as a convenience for Go client code. The
// core iteration behavior of Starlark for-loops is defined by the
// [Iterable] interface.
func Elements(iterable Iterable) iter.Seq[Value] {
// Use specialized push iterator if available (*List, Tuple, *Set).
type hasElements interface {
Elements() iter.Seq[Value]
}
if iterable, ok := iterable.(hasElements); ok {
return iterable.Elements()
}

iter := iterable.Iterate()
return func(yield func(Value) bool) {
defer iter.Done()
var x Value
for iter.Next(&x) && yield(x) {
}
}
}

// Entries returns an iterator over the entries (key/value pairs) of
// the iterable mapping.
//
// Example of go1.23 iteration:
//
// for k, v := range Entries(mapping) { ... }
//
// Push iterators are provided as a convenience for Go client code. The
// core iteration behavior of Starlark for-loops is defined by the
// [Iterable] interface.
func Entries(mapping IterableMapping) iter.Seq2[Value, Value] {
// If available (e.g. *Dict), use specialized push iterator,
// as it gets k and v in one shot.
type hasEntries interface {
Entries() iter.Seq2[Value, Value]
}
if mapping, ok := mapping.(hasEntries); ok {
return mapping.Entries()
}

iter := mapping.Iterate()
return func(yield func(k, v Value) bool) {
defer iter.Done()
var k Value
for iter.Next(&k) {
v, found, err := mapping.Get(k)
if err != nil || !found {
panic(fmt.Sprintf("Iterate and Get are inconsistent (mapping=%v, key=%v)",
mapping.Type(), k.Type()))
}
if !yield(k, v) {
break
}
}
}
}
108 changes: 0 additions & 108 deletions starlark/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,6 @@ func (d *Dict) Type() string { return "dict"
func (d *Dict) Freeze() { d.ht.freeze() }
func (d *Dict) Truth() Bool { return d.Len() > 0 }
func (d *Dict) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable type: dict") }
func (d *Dict) Entries() func(yield func(k, v Value) bool) { return d.ht.entries }

func (x *Dict) Union(y *Dict) *Dict {
z := new(Dict)
Expand Down Expand Up @@ -971,25 +970,6 @@ func (l *List) Iterate() Iterator {
return &listIterator{l: l}
}

// Elements returns a go1.23 iterator over the elements of the list.
//
// Example:
//
// for elem := range list.Elements() { ... }
func (l *List) Elements() func(yield func(Value) bool) {
return func(yield func(Value) bool) {
if !l.frozen {
l.itercount++
defer func() { l.itercount-- }()
}
for _, x := range l.elems {
if !yield(x) {
break
}
}
}
}

func (x *List) CompareSameType(op syntax.Token, y_ Value, depth int) (bool, error) {
y := y_.(*List)
// It's tempting to check x == y as an optimization here,
Expand Down Expand Up @@ -1090,21 +1070,6 @@ func (t Tuple) Slice(start, end, step int) Value {

func (t Tuple) Iterate() Iterator { return &tupleIterator{elems: t} }

// Elements returns a go1.23 iterator over the elements of the tuple.
//
// (A Tuple is a slice, so it is of course directly iterable. This
// method exists to provide a fast path for the [Elements] standalone
// function.)
func (t Tuple) Elements() func(yield func(Value) bool) {
return func(yield func(Value) bool) {
for _, x := range t {
if !yield(x) {
break
}
}
}
}

func (t Tuple) Freeze() {
for _, elem := range t {
elem.Freeze()
Expand Down Expand Up @@ -1176,11 +1141,6 @@ func (s *Set) Truth() Bool { return s.Len() > 0 }

func (s *Set) Attr(name string) (Value, error) { return builtinAttr(s, name, setMethods) }
func (s *Set) AttrNames() []string { return builtinAttrNames(setMethods) }
func (s *Set) Elements() func(yield func(k Value) bool) {
return func(yield func(k Value) bool) {
s.ht.entries(func(k, _ Value) bool { return yield(k) })
}
}

func (x *Set) CompareSameType(op syntax.Token, y_ Value, depth int) (bool, error) {
y := y_.(*Set)
Expand Down Expand Up @@ -1618,74 +1578,6 @@ func Iterate(x Value) Iterator {
return nil
}

// Elements returns an iterator for the elements of the iterable value.
//
// Example of go1.23 iteration:
//
// for elem := range Elements(iterable) { ... }
//
// Push iterators are provided as a convenience for Go client code. The
// core iteration behavior of Starlark for-loops is defined by the
// [Iterable] interface.
//
// TODO(adonovan): change return type to go1.23 iter.Seq[Value].
func Elements(iterable Iterable) func(yield func(Value) bool) {
// Use specialized push iterator if available (*List, Tuple, *Set).
type hasElements interface {
Elements() func(yield func(k Value) bool)
}
if iterable, ok := iterable.(hasElements); ok {
return iterable.Elements()
}

iter := iterable.Iterate()
return func(yield func(Value) bool) {
defer iter.Done()
var x Value
for iter.Next(&x) && yield(x) {
}
}
}

// Entries returns an iterator over the entries (key/value pairs) of
// the iterable mapping.
//
// Example of go1.23 iteration:
//
// for k, v := range Entries(mapping) { ... }
//
// Push iterators are provided as a convenience for Go client code. The
// core iteration behavior of Starlark for-loops is defined by the
// [Iterable] interface.
//
// TODO(adonovan): change return type to go1.23 iter.Seq2[Value, Value].
func Entries(mapping IterableMapping) func(yield func(k, v Value) bool) {
// If available (e.g. *Dict), use specialized push iterator,
// as it gets k and v in one shot.
type hasEntries interface {
Entries() func(yield func(k, v Value) bool)
}
if mapping, ok := mapping.(hasEntries); ok {
return mapping.Entries()
}

iter := mapping.Iterate()
return func(yield func(k, v Value) bool) {
defer iter.Done()
var k Value
for iter.Next(&k) {
v, found, err := mapping.Get(k)
if err != nil || !found {
panic(fmt.Sprintf("Iterate and Get are inconsistent (mapping=%v, key=%v)",
mapping.Type(), k.Type()))
}
if !yield(k, v) {
break
}
}
}
}

// Bytes is the type of a Starlark binary string.
//
// A Bytes encapsulates an immutable sequence of bytes.
Expand Down
6 changes: 3 additions & 3 deletions syntax/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ import _ "unsafe" // for linkname
// FileOptions specifies various per-file options that affect static
// aspects of an individual file such as parsing, name resolution, and
// code generation. (Options that affect global dynamics are typically
// controlled through [starlark.Thread].)
// controlled through [go.starlark.net/starlark.Thread].)
//
// The zero value of FileOptions is the default behavior.
//
// Many functions in this package come in two versions: the legacy
// standalone function (such as [Parse]) uses [LegacyFileOptions],
// whereas the more recent method (such as [Options.Parse]) honors the
// whereas the more recent method (such as [FileOptions.Parse]) honors the
// provided options. The second form is preferred. In other packages,
// the modern version is a standalone function with a leading
// FileOptions parameter and the name suffix "Options", such as
// [starlark.ExecFileOptions].
// [go.starlark.net/starlark.ExecFileOptions].
type FileOptions struct {
// resolver
Set bool // allow references to the 'set' built-in function
Expand Down

0 comments on commit 3792562

Please sign in to comment.