go/types: make typeset return an iterator

typeset(t) now returns a func equivalent to iter.Seq2[Type, Type]
for the sequence over (type, underlying) pairs in the typeset of t.

underIs was modified to take advantage of the underlying iteration
primitive, all, which computes the desired boolean conjunction
directly.

Change-Id: I3e17d5970fd2908c5dca0754db3e251bf1200af2
Reviewed-on: https://go-review.googlesource.com/c/go/+/688876
Auto-Submit: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Alan Donovan 2025-07-18 14:19:26 -04:00 committed by Gopher Robot
parent fbba930271
commit 6b32c613ca
12 changed files with 129 additions and 160 deletions

View file

@ -98,17 +98,17 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
if ok, _ := x.assignableTo(check, NewSlice(universeByte), nil); ok {
y := args[1]
hasString := false
typeset(y.typ, func(_, u Type) bool {
for _, u := range typeset(y.typ) {
if s, _ := u.(*Slice); s != nil && Identical(s.elem, universeByte) {
return true
}
if isString(u) {
// typeset ⊇ {[]byte}
} else if isString(u) {
// typeset ⊇ {string}
hasString = true
return true
} else {
y = nil
break
}
y = nil
return false
})
}
if y != nil && hasString {
// setting the signature also signals that we're done
sig = makeSig(x.typ, x.typ, y.typ)
@ -368,16 +368,16 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
var special bool
if ok, _ := x.assignableTo(check, NewSlice(universeByte), nil); ok {
special = true
typeset(y.typ, func(_, u Type) bool {
for _, u := range typeset(y.typ) {
if s, _ := u.(*Slice); s != nil && Identical(s.elem, universeByte) {
return true
// typeset ⊇ {[]byte}
} else if isString(u) {
// typeset ⊇ {string}
} else {
special = false
break
}
if isString(u) {
return true
}
special = false
return false
})
}
}
// general case
@ -980,29 +980,22 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
// or a type error if x is not a slice (or a type set of slices).
func sliceElem(x *operand) (Type, *typeError) {
var E Type
var err *typeError
typeset(x.typ, func(_, u Type) bool {
for _, u := range typeset(x.typ) {
s, _ := u.(*Slice)
if s == nil {
if x.isNil() {
// Printing x in this case would just print "nil".
// Special case this so we can emphasize "untyped".
err = typeErrorf("argument must be a slice; have untyped nil")
return nil, typeErrorf("argument must be a slice; have untyped nil")
} else {
err = typeErrorf("argument must be a slice; have %s", x)
return nil, typeErrorf("argument must be a slice; have %s", x)
}
return false
}
if E == nil {
E = s.elem
} else if !Identical(E, s.elem) {
err = typeErrorf("mismatched slice element types %s and %s in %s", E, s.elem, x)
return false
return nil, typeErrorf("mismatched slice element types %s and %s in %s", E, s.elem, x)
}
return true
})
if err != nil {
return nil, err
}
return E, nil
}

View file

@ -216,11 +216,11 @@ func (check *Checker) sliceExpr(x *operand, e *syntax.SliceExpr) {
// determine common underlying type cu
var ct, cu Type // type and respective common underlying type
var hasString bool
typeset(x.typ, func(t, u Type) bool {
for t, u := range typeset(x.typ) {
if u == nil {
check.errorf(x, NonSliceableOperand, "cannot slice %s: no specific type in %s", x, x.typ)
cu = nil
return false
break
}
// Treat strings like byte slices but remember that we saw a string.
@ -232,18 +232,16 @@ func (check *Checker) sliceExpr(x *operand, e *syntax.SliceExpr) {
// If this is the first type we're seeing, we're done.
if cu == nil {
ct, cu = t, u
return true
continue
}
// Otherwise, the current type must have the same underlying type as all previous types.
if !Identical(cu, u) {
check.errorf(x, NonSliceableOperand, "cannot slice %s: %s and %s have different underlying types", x, ct, t)
cu = nil
return false
break
}
return true
})
}
if hasString {
// If we saw a string, proceed with string type,
// but don't go from untyped string to string.

View file

@ -49,7 +49,7 @@ func NewSignatureType(recv *Var, recvTypeParams, typeParams []*TypeParam, params
}
last := params.At(n - 1).typ
var S *Slice
typeset(last, func(t, _ Type) bool {
for t := range typeset(last) {
var s *Slice
if isString(t) {
s = NewSlice(universeByte)
@ -60,10 +60,9 @@ func NewSignatureType(recv *Var, recvTypeParams, typeParams []*TypeParam, params
S = s
} else if !Identical(S, s) {
S = nil
return false
break
}
return true
})
}
if S == nil {
panic(fmt.Sprintf("got %s, want variadic parameter of unnamed slice or string type", last))
}

View file

@ -155,10 +155,10 @@ func (t *TypeParam) is(f func(*term) bool) bool {
return t.iface().typeSet().is(f)
}
// typeset is an iterator over the (type/underlying type) pairs of the
// typeset reports whether f(t, y) is true for all (type/underlying type) pairs of the
// specific type terms of t's constraint.
// If there are no specific terms, typeset calls yield with (nil, nil).
// In any case, typeset is guaranteed to call yield at least once.
func (t *TypeParam) typeset(yield func(t, u Type) bool) {
t.iface().typeSet().typeset(yield)
// If there are no specific terms, typeset returns f(nil, nil).
// In any case, typeset is guaranteed to call f at least once.
func (t *TypeParam) typeset(f func(t, u Type) bool) bool {
return t.iface().typeSet().all(f)
}

View file

@ -104,13 +104,12 @@ func (s *_TypeSet) hasTerms() bool { return !s.terms.isEmpty() && !s.terms.isAll
// subsetOf reports whether s1 ⊆ s2.
func (s1 *_TypeSet) subsetOf(s2 *_TypeSet) bool { return s1.terms.subsetOf(s2.terms) }
// typeset is an iterator over the (type/underlying type) pairs in s.
// If s has no specific terms, typeset calls yield with (nil, nil).
// In any case, typeset is guaranteed to call yield at least once.
func (s *_TypeSet) typeset(yield func(t, u Type) bool) {
// all reports whether f(t, u) is true for each (type/underlying type) pairs in s.
// If s has no specific terms, all calls f(nil, nil).
// In any case, all is guaranteed to call f at least once.
func (s *_TypeSet) all(f func(t, u Type) bool) bool {
if !s.hasTerms() {
yield(nil, nil)
return
return f(nil, nil)
}
for _, t := range s.terms {
@ -123,10 +122,11 @@ func (s *_TypeSet) typeset(yield func(t, u Type) bool) {
if debug {
assert(Identical(u, under(u)))
}
if !yield(t.typ, u) {
break
if !f(t.typ, u) {
return false
}
}
return true
}
// is calls f with the specific type terms of s and reports whether

View file

@ -4,6 +4,8 @@
package types2
import "iter"
// under returns the true expanded underlying type.
// If it doesn't exist, the result is Typ[Invalid].
// under must only be called when a type is known
@ -18,12 +20,18 @@ func under(t Type) Type {
// If typ is a type parameter, underIs returns the result of typ.underIs(f).
// Otherwise, underIs returns the result of f(under(typ)).
func underIs(typ Type, f func(Type) bool) bool {
var ok bool
typeset(typ, func(_, u Type) bool {
ok = f(u)
return ok
return all(typ, func(_, u Type) bool {
return f(u)
})
return ok
}
// all reports whether f(t, u) is true for all (type/underlying type)
// pairs in the typeset of t. See [typeset] for details of sequence.
func all(t Type, f func(t, u Type) bool) bool {
if p, _ := Unalias(t).(*TypeParam); p != nil {
return p.typeset(f)
}
return f(t, under(t))
}
// typeset is an iterator over the (type/underlying type) pairs of the
@ -32,12 +40,10 @@ func underIs(typ Type, f func(Type) bool) bool {
// In that case, if there are no specific terms, typeset calls yield with (nil, nil).
// If t is not a type parameter, the implied type set consists of just t.
// In any case, typeset is guaranteed to call yield at least once.
func typeset(t Type, yield func(t, u Type) bool) {
if p, _ := Unalias(t).(*TypeParam); p != nil {
p.typeset(yield)
return
func typeset(t Type) iter.Seq2[Type, Type] {
return func(yield func(t, u Type) bool) {
_ = all(t, yield)
}
yield(t, under(t))
}
// A typeError describes a type error.
@ -80,35 +86,28 @@ func (err *typeError) format(check *Checker) string {
// with the single type t in its type set.
func commonUnder(t Type, cond func(t, u Type) *typeError) (Type, *typeError) {
var ct, cu Type // type and respective common underlying type
var err *typeError
bad := func(format string, args ...any) bool {
err = typeErrorf(format, args...)
return false
}
typeset(t, func(t, u Type) bool {
for t, u := range typeset(t) {
if cond != nil {
if err = cond(t, u); err != nil {
return false
if err := cond(t, u); err != nil {
return nil, err
}
}
if u == nil {
return bad("no specific type")
return nil, typeErrorf("no specific type")
}
// If this is the first type we're seeing, we're done.
if cu == nil {
ct, cu = t, u
return true
continue
}
// If we've seen a channel before, and we have a channel now, they must be compatible.
if chu, _ := cu.(*Chan); chu != nil {
if ch, _ := u.(*Chan); ch != nil {
if !Identical(chu.elem, ch.elem) {
return bad("channels %s and %s have different element types", ct, t)
return nil, typeErrorf("channels %s and %s have different element types", ct, t)
}
// If we have different channel directions, keep the restricted one
// and complain if they conflict.
@ -118,22 +117,16 @@ func commonUnder(t Type, cond func(t, u Type) *typeError) (Type, *typeError) {
case chu.dir == SendRecv:
ct, cu = t, u // switch to restricted channel
case ch.dir != SendRecv:
return bad("channels %s and %s have conflicting directions", ct, t)
return nil, typeErrorf("channels %s and %s have conflicting directions", ct, t)
}
return true
continue
}
}
// Otherwise, the current type must have the same underlying type as all previous types.
if !Identical(cu, u) {
return bad("%s and %s have different underlying types", ct, t)
return nil, typeErrorf("%s and %s have different underlying types", ct, t)
}
return true
})
if err != nil {
return nil, err
}
return cu, nil
}

View file

@ -101,17 +101,17 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
if ok, _ := x.assignableTo(check, NewSlice(universeByte), nil); ok {
y := args[1]
hasString := false
typeset(y.typ, func(_, u Type) bool {
for _, u := range typeset(y.typ) {
if s, _ := u.(*Slice); s != nil && Identical(s.elem, universeByte) {
return true
}
if isString(u) {
// typeset ⊇ {[]byte}
} else if isString(u) {
// typeset ⊇ {string}
hasString = true
return true
} else {
y = nil
break
}
y = nil
return false
})
}
if y != nil && hasString {
// setting the signature also signals that we're done
sig = makeSig(x.typ, x.typ, y.typ)
@ -371,16 +371,16 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
var special bool
if ok, _ := x.assignableTo(check, NewSlice(universeByte), nil); ok {
special = true
typeset(y.typ, func(_, u Type) bool {
for _, u := range typeset(y.typ) {
if s, _ := u.(*Slice); s != nil && Identical(s.elem, universeByte) {
return true
// typeset ⊇ {[]byte}
} else if isString(u) {
// typeset ⊇ {string}
} else {
special = false
break
}
if isString(u) {
return true
}
special = false
return false
})
}
}
// general case
@ -983,29 +983,22 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
// or a type error if x is not a slice (or a type set of slices).
func sliceElem(x *operand) (Type, *typeError) {
var E Type
var err *typeError
typeset(x.typ, func(_, u Type) bool {
for _, u := range typeset(x.typ) {
s, _ := u.(*Slice)
if s == nil {
if x.isNil() {
// Printing x in this case would just print "nil".
// Special case this so we can emphasize "untyped".
err = typeErrorf("argument must be a slice; have untyped nil")
return nil, typeErrorf("argument must be a slice; have untyped nil")
} else {
err = typeErrorf("argument must be a slice; have %s", x)
return nil, typeErrorf("argument must be a slice; have %s", x)
}
return false
}
if E == nil {
E = s.elem
} else if !Identical(E, s.elem) {
err = typeErrorf("mismatched slice element types %s and %s in %s", E, s.elem, x)
return false
return nil, typeErrorf("mismatched slice element types %s and %s in %s", E, s.elem, x)
}
return true
})
if err != nil {
return nil, err
}
return E, nil
}

View file

@ -218,7 +218,8 @@ func (check *Checker) sliceExpr(x *operand, e *ast.SliceExpr) {
// determine common underlying type cu
var ct, cu Type // type and respective common underlying type
var hasString bool
typeset(x.typ, func(t, u Type) bool {
// TODO(adonovan): use go1.23 "range typeset()".
typeset(x.typ)(func(t, u Type) bool {
if u == nil {
check.errorf(x, NonSliceableOperand, "cannot slice %s: no specific type in %s", x, x.typ)
cu = nil

View file

@ -62,7 +62,7 @@ func NewSignatureType(recv *Var, recvTypeParams, typeParams []*TypeParam, params
}
last := params.At(n - 1).typ
var S *Slice
typeset(last, func(t, _ Type) bool {
for t := range typeset(last) {
var s *Slice
if isString(t) {
s = NewSlice(universeByte)
@ -73,10 +73,9 @@ func NewSignatureType(recv *Var, recvTypeParams, typeParams []*TypeParam, params
S = s
} else if !Identical(S, s) {
S = nil
return false
break
}
return true
})
}
if S == nil {
panic(fmt.Sprintf("got %s, want variadic parameter of unnamed slice or string type", last))
}

View file

@ -158,10 +158,10 @@ func (t *TypeParam) is(f func(*term) bool) bool {
return t.iface().typeSet().is(f)
}
// typeset is an iterator over the (type/underlying type) pairs of the
// typeset reports whether f(t, y) is true for all (type/underlying type) pairs of the
// specific type terms of t's constraint.
// If there are no specific terms, typeset calls yield with (nil, nil).
// In any case, typeset is guaranteed to call yield at least once.
func (t *TypeParam) typeset(yield func(t, u Type) bool) {
t.iface().typeSet().typeset(yield)
// If there are no specific terms, typeset returns f(nil, nil).
// In any case, typeset is guaranteed to call f at least once.
func (t *TypeParam) typeset(f func(t, u Type) bool) bool {
return t.iface().typeSet().all(f)
}

View file

@ -107,13 +107,12 @@ func (s *_TypeSet) hasTerms() bool { return !s.terms.isEmpty() && !s.terms.isAll
// subsetOf reports whether s1 ⊆ s2.
func (s1 *_TypeSet) subsetOf(s2 *_TypeSet) bool { return s1.terms.subsetOf(s2.terms) }
// typeset is an iterator over the (type/underlying type) pairs in s.
// If s has no specific terms, typeset calls yield with (nil, nil).
// In any case, typeset is guaranteed to call yield at least once.
func (s *_TypeSet) typeset(yield func(t, u Type) bool) {
// all reports whether f(t, u) is true for each (type/underlying type) pairs in s.
// If s has no specific terms, all calls f(nil, nil).
// In any case, all is guaranteed to call f at least once.
func (s *_TypeSet) all(f func(t, u Type) bool) bool {
if !s.hasTerms() {
yield(nil, nil)
return
return f(nil, nil)
}
for _, t := range s.terms {
@ -126,10 +125,11 @@ func (s *_TypeSet) typeset(yield func(t, u Type) bool) {
if debug {
assert(Identical(u, under(u)))
}
if !yield(t.typ, u) {
break
if !f(t.typ, u) {
return false
}
}
return true
}
// is calls f with the specific type terms of s and reports whether

View file

@ -7,6 +7,8 @@
package types
import "iter"
// under returns the true expanded underlying type.
// If it doesn't exist, the result is Typ[Invalid].
// under must only be called when a type is known
@ -21,12 +23,18 @@ func under(t Type) Type {
// If typ is a type parameter, underIs returns the result of typ.underIs(f).
// Otherwise, underIs returns the result of f(under(typ)).
func underIs(typ Type, f func(Type) bool) bool {
var ok bool
typeset(typ, func(_, u Type) bool {
ok = f(u)
return ok
return all(typ, func(_, u Type) bool {
return f(u)
})
return ok
}
// all reports whether f(t, u) is true for all (type/underlying type)
// pairs in the typeset of t. See [typeset] for details of sequence.
func all(t Type, f func(t, u Type) bool) bool {
if p, _ := Unalias(t).(*TypeParam); p != nil {
return p.typeset(f)
}
return f(t, under(t))
}
// typeset is an iterator over the (type/underlying type) pairs of the
@ -35,12 +43,10 @@ func underIs(typ Type, f func(Type) bool) bool {
// In that case, if there are no specific terms, typeset calls yield with (nil, nil).
// If t is not a type parameter, the implied type set consists of just t.
// In any case, typeset is guaranteed to call yield at least once.
func typeset(t Type, yield func(t, u Type) bool) {
if p, _ := Unalias(t).(*TypeParam); p != nil {
p.typeset(yield)
return
func typeset(t Type) iter.Seq2[Type, Type] {
return func(yield func(t, u Type) bool) {
_ = all(t, yield)
}
yield(t, under(t))
}
// A typeError describes a type error.
@ -83,35 +89,28 @@ func (err *typeError) format(check *Checker) string {
// with the single type t in its type set.
func commonUnder(t Type, cond func(t, u Type) *typeError) (Type, *typeError) {
var ct, cu Type // type and respective common underlying type
var err *typeError
bad := func(format string, args ...any) bool {
err = typeErrorf(format, args...)
return false
}
typeset(t, func(t, u Type) bool {
for t, u := range typeset(t) {
if cond != nil {
if err = cond(t, u); err != nil {
return false
if err := cond(t, u); err != nil {
return nil, err
}
}
if u == nil {
return bad("no specific type")
return nil, typeErrorf("no specific type")
}
// If this is the first type we're seeing, we're done.
if cu == nil {
ct, cu = t, u
return true
continue
}
// If we've seen a channel before, and we have a channel now, they must be compatible.
if chu, _ := cu.(*Chan); chu != nil {
if ch, _ := u.(*Chan); ch != nil {
if !Identical(chu.elem, ch.elem) {
return bad("channels %s and %s have different element types", ct, t)
return nil, typeErrorf("channels %s and %s have different element types", ct, t)
}
// If we have different channel directions, keep the restricted one
// and complain if they conflict.
@ -121,22 +120,16 @@ func commonUnder(t Type, cond func(t, u Type) *typeError) (Type, *typeError) {
case chu.dir == SendRecv:
ct, cu = t, u // switch to restricted channel
case ch.dir != SendRecv:
return bad("channels %s and %s have conflicting directions", ct, t)
return nil, typeErrorf("channels %s and %s have conflicting directions", ct, t)
}
return true
continue
}
}
// Otherwise, the current type must have the same underlying type as all previous types.
if !Identical(cu, u) {
return bad("%s and %s have different underlying types", ct, t)
return nil, typeErrorf("%s and %s have different underlying types", ct, t)
}
return true
})
if err != nil {
return nil, err
}
return cu, nil
}