[dev.typeparams] go/types: replace Sum type with Union type

This is a straightforward port of CL 323274 to go/types.

Change-Id: Ica769d90fd482703f260f105199d2f2229498e95
Reviewed-on: https://go-review.googlesource.com/c/go/+/326677
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Rob Findley 2021-06-09 18:15:10 -04:00 committed by Robert Findley
parent e7451f6616
commit c7a460526e
17 changed files with 185 additions and 191 deletions

View file

@ -10,12 +10,9 @@ import (
type (
Inferred = _Inferred
Sum = _Sum
TypeParam = _TypeParam
)
func NewSum(types []Type) Type { return _NewSum(types) }
// NewTypeParam returns a new TypeParam.
func NewTypeParam(obj *TypeName, index int, bound Type) *TypeParam {
return (*Checker)(nil).newTypeParam(obj, index, bound)

View file

@ -179,9 +179,9 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
mode = value
}
case *_Sum:
if t.is(func(t Type) bool {
switch t := under(t).(type) {
case *Union:
if t.underIs(func(t Type) bool {
switch t := t.(type) {
case *Basic:
if isString(t) && id == _Len {
return true
@ -469,8 +469,8 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
m = 2
case *Map, *Chan:
m = 1
case *_Sum:
return t.is(valid)
case *Union:
return t.underIs(valid)
default:
return false
}
@ -768,10 +768,14 @@ func (check *Checker) applyTypeFunc(f func(Type) Type, x Type) Type {
if tp := asTypeParam(x); tp != nil {
// Test if t satisfies the requirements for the argument
// type and collect possible result types at the same time.
// TODO(gri) This needs to consider the ~ information if we
// have a union type.
var rtypes []Type
var tilde []bool
if !tp.Bound().is(func(x Type) bool {
if r := f(x); r != nil {
rtypes = append(rtypes, r)
tilde = append(tilde, true)
return true
}
return false
@ -782,7 +786,7 @@ func (check *Checker) applyTypeFunc(f func(Type) Type, x Type) Type {
// construct a suitable new type parameter
tpar := NewTypeName(token.NoPos, nil /* = Universe pkg */, "<type parameter>", nil)
ptyp := check.newTypeParam(tpar, 0, &emptyInterface) // assigns type to tpar as a side-effect
tsum := _NewSum(rtypes)
tsum := newUnion(rtypes, tilde)
ptyp.bound = &Interface{allMethods: markComplete, allTypes: tsum}
return ptyp

View file

@ -661,8 +661,8 @@ func (check *Checker) implicitTypeAndValue(x *operand, target Type) (Type, const
default:
return nil, nil, _InvalidUntypedConversion
}
case *_Sum:
ok := t.is(func(t Type) bool {
case *Union:
ok := t.underIs(func(t Type) bool {
target, _, _ := check.implicitTypeAndValue(x, t)
return target != nil
})

View file

@ -91,15 +91,15 @@ func (check *Checker) indexExpr(x *operand, e *ast.IndexExpr) (isFuncInst bool)
x.expr = e
return
case *_Sum:
// A sum type can be indexed if all of the sum's types
case *Union:
// A union type can be indexed if all of the union's terms
// support indexing and have the same index and element
// type. Special rules apply for maps in the sum type.
// type. Special rules apply for maps in the union type.
var tkey, telem Type // key is for map types only
nmaps := 0 // number of map types in sum type
if typ.is(func(t Type) bool {
nmaps := 0 // number of map types in union type
if typ.underIs(func(t Type) bool {
var e Type
switch t := under(t).(type) {
switch t := t.(type) {
case *Basic:
if isString(t) {
e = universeByte
@ -113,7 +113,7 @@ func (check *Checker) indexExpr(x *operand, e *ast.IndexExpr) (isFuncInst bool)
case *Slice:
e = t.elem
case *Map:
// If there are multiple maps in the sum type,
// If there are multiple maps in the union type,
// they must have identical key types.
// TODO(gri) We may be able to relax this rule
// but it becomes complicated very quickly.
@ -148,7 +148,7 @@ func (check *Checker) indexExpr(x *operand, e *ast.IndexExpr) (isFuncInst bool)
// ok to continue even if indexing failed - map element type is known
// If there are only maps, we are done.
if nmaps == len(typ.types) {
if nmaps == typ.NumTerms() {
x.mode = mapindex
x.typ = telem
x.expr = e
@ -246,7 +246,7 @@ func (check *Checker) sliceExpr(x *operand, e *ast.SliceExpr) {
valid = true
// x.typ doesn't change
case *_Sum, *_TypeParam:
case *Union, *_TypeParam:
check.errorf(x, 0, "generic slice expressions not yet implemented")
x.mode = invalid
return

View file

@ -302,7 +302,7 @@ func (w *tpWalker) isParameterized(typ Type) (res bool) {
}
}
case *_Sum:
case *Union:
return w.isParameterizedList(t.types)
case *Signature:
@ -315,9 +315,6 @@ func (w *tpWalker) isParameterized(typ Type) (res bool) {
// Thus, we only need to look at the input and result parameters.
return w.isParameterized(t.params) || w.isParameterized(t.results)
case *Union:
panic("unimplemented")
case *Interface:
if t.allMethods != nil {
// TODO(rFindley) at some point we should enforce completeness here

View file

@ -240,22 +240,26 @@ func completeInterface(check *Checker, pos token.Pos, ityp *Interface) {
}
types = t.allTypes
case *Union:
types = NewSum(t.terms)
// TODO(gri) combine with default case once we have
// converted all tests to new notation and we
// can report an error when we don't have an
// interface before go1.18.
types = typ
case *TypeParam:
if check != nil && !check.allowVersion(check.pkg, 1, 18) {
check.errorf(atPos(pos), _InvalidIfaceEmbed, "%s is a type parameter, not an interface", typ)
continue
}
types = t
types = typ
default:
if t == Typ[Invalid] {
if typ == Typ[Invalid] {
continue
}
if check != nil && !check.allowVersion(check.pkg, 1, 18) {
check.errorf(atPos(pos), _InvalidIfaceEmbed, "%s is not an interface", typ)
continue
}
types = t
types = typ
}
allTypes = intersect(allTypes, types)
}
@ -276,44 +280,6 @@ func completeInterface(check *Checker, pos token.Pos, ityp *Interface) {
ityp.allTypes = allTypes
}
// intersect computes the intersection of the types x and y.
// Note: An incomming nil type stands for the top type. A top
// type result is returned as nil.
func intersect(x, y Type) (r Type) {
defer func() {
if r == theTop {
r = nil
}
}()
switch {
case x == theBottom || y == theBottom:
return theBottom
case x == nil || x == theTop:
return y
case y == nil || x == theTop:
return x
}
xtypes := unpackType(x)
ytypes := unpackType(y)
// Compute the list rtypes which includes only
// types that are in both xtypes and ytypes.
// Quadratic algorithm, but good enough for now.
// TODO(gri) fix this
var rtypes []Type
for _, x := range xtypes {
if includes(ytypes, x) {
rtypes = append(rtypes, x)
}
}
if rtypes == nil {
return theBottom
}
return _NewSum(rtypes)
}
func sortTypes(list []Type) {
sort.Stable(byUniqueTypeName(list))
}

View file

@ -233,6 +233,12 @@ func (x *operand) assignableTo(check *Checker, T Type, reason *string) (bool, er
V := x.typ
const debugAssignableTo = false
if debugAssignableTo && check != nil {
check.dump("V = %s", V)
check.dump("T = %s", T)
}
// x's type is identical to T
if check.identical(V, T) {
return true, 0
@ -241,11 +247,20 @@ func (x *operand) assignableTo(check *Checker, T Type, reason *string) (bool, er
Vu := optype(V)
Tu := optype(T)
if debugAssignableTo && check != nil {
check.dump("Vu = %s", Vu)
check.dump("Tu = %s", Tu)
}
// x is an untyped value representable by a value of type T.
if isUntyped(Vu) {
if t, ok := Tu.(*_Sum); ok {
return t.is(func(t Type) bool {
if t, ok := Tu.(*Union); ok {
return t.is(func(t Type, tilde bool) bool {
// TODO(gri) this could probably be more efficient
if tilde {
// TODO(gri) We need to check assignability
// for the underlying type of x.
}
ok, _ := x.assignableTo(check, t, reason)
return ok
}), _IncompatibleAssign

View file

@ -32,8 +32,8 @@ func is(typ Type, what BasicInfo) bool {
switch t := optype(typ).(type) {
case *Basic:
return t.info&what != 0
case *_Sum:
return t.is(func(typ Type) bool { return is(typ, what) })
case *Union:
return t.underIs(func(typ Type) bool { return is(typ, what) })
}
return false
}
@ -128,11 +128,10 @@ func comparable(T Type, seen map[Type]bool) bool {
return true
case *Array:
return comparable(t.elem, seen)
case *_Sum:
pred := func(t Type) bool {
case *Union:
return t.underIs(func(t Type) bool {
return comparable(t, seen)
}
return t.is(pred)
})
case *_TypeParam:
return t.Bound()._IsComparable()
}
@ -146,8 +145,8 @@ func hasNil(typ Type) bool {
return t.kind == UnsafePointer
case *Slice, *Pointer, *Signature, *Interface, *Map, *Chan:
return true
case *_Sum:
return t.is(hasNil)
case *Union:
return t.underIs(hasNil)
}
return false
}
@ -265,21 +264,20 @@ func (check *Checker) identical0(x, y Type, cmpTags bool, p *ifacePair) bool {
check.identical0(x.results, y.results, cmpTags, p)
}
case *_Sum:
// Two sum types are identical if they contain the same types.
// (Sum types always consist of at least two types. Also, the
// the set (list) of types in a sum type consists of unique
// types - each type appears exactly once. Thus, two sum types
case *Union:
// Two union types are identical if they contain the same terms.
// The set (list) of types in a union type consists of unique
// types - each type appears exactly once. Thus, two union types
// must contain the same number of types to have chance of
// being equal.
if y, ok := y.(*_Sum); ok && len(x.types) == len(y.types) {
if y, ok := y.(*Union); ok && x.NumTerms() == y.NumTerms() {
// Every type in x.types must be in y.types.
// Quadratic algorithm, but probably good enough for now.
// TODO(gri) we need a fast quick type ID/hash for all types.
L:
for _, x := range x.types {
for _, y := range y.types {
if Identical(x, y) {
for i, xt := range x.types {
for j, yt := range y.types {
if Identical(xt, yt) && x.tilde[i] == y.tilde[j] {
continue L // x is in y.types
}
}
@ -288,9 +286,6 @@ func (check *Checker) identical0(x, y Type, cmpTags bool, p *ifacePair) bool {
return true
}
case *Union:
panic("identical0 not implemented for union types")
case *Interface:
// Two interface types are identical if they have the same set of methods with
// the same names and identical function types. Lower-case method names from

View file

@ -107,11 +107,8 @@ func (s sanitizer) typ(typ Type) Type {
s.tuple(t.params)
s.tuple(t.results)
case *_Sum:
s.typeList(t.types)
case *Union:
s.typeList(t.terms)
s.typeList(t.types)
case *Interface:
s.funcList(t.methods)

View file

@ -26,7 +26,6 @@ func TestSizeof(t *testing.T) {
{Pointer{}, 8, 16},
{Tuple{}, 12, 24},
{Signature{}, 44, 88},
{_Sum{}, 12, 24},
{Union{}, 24, 48},
{Interface{}, 52, 104},
{Map{}, 16, 32},

View file

@ -148,10 +148,8 @@ func (s *StdSizes) Sizeof(T Type) int64 {
}
offsets := s.Offsetsof(t.fields)
return offsets[n-1] + s.Sizeof(t.fields[n-1].typ)
case *_Sum:
panic("Sizeof unimplemented for type sum")
case *Union:
panic("Sizeof unimplemented for type union")
panic("Sizeof unimplemented for union")
case *Interface:
return s.WordSize * 2
}

View file

@ -911,12 +911,12 @@ func rangeKeyVal(typ Type, wantKey, wantVal bool) (Type, Type, string) {
msg = "send-only channel"
}
return typ.elem, Typ[Invalid], msg
case *_Sum:
case *Union:
first := true
var key, val Type
var msg string
typ.is(func(t Type) bool {
k, v, m := rangeKeyVal(under(t), wantKey, wantVal)
typ.underIs(func(t Type) bool {
k, v, m := rangeKeyVal(t, wantKey, wantVal)
if k == nil || m != "" {
key, val, msg = k, v, m
return false

View file

@ -302,21 +302,13 @@ func (subst *subster) typ(typ Type) Type {
}
}
case *_Sum:
case *Union:
types, copied := subst.typeList(t.types)
if copied {
// Don't do it manually, with a Sum literal: the new
// types list may not be unique and NewSum may remove
// duplicates.
return _NewSum(types)
}
case *Union:
terms, copied := subst.typeList(t.terms)
if copied {
// TODO(gri) Do we need to remove duplicates that may have
// crept in after substitution? It may not matter.
return newUnion(terms, t.tilde)
// TODO(gri) Remove duplicates that may have crept in after substitution
// (unlikely but possible). This matters for the Identical
// predicate on unions.
return newUnion(types, t.tilde)
}
case *Interface:

View file

@ -255,53 +255,6 @@ func (s *Signature) Results() *Tuple { return s.results }
// Variadic reports whether the signature s is variadic.
func (s *Signature) Variadic() bool { return s.variadic }
// A _Sum represents a set of possible types.
// Sums are currently used to represent type lists of interfaces
// and thus the underlying types of type parameters; they are not
// first class types of Go.
type _Sum struct {
types []Type // types are unique
}
// _NewSum returns a new Sum type consisting of the provided
// types if there are more than one. If there is exactly one
// type, it returns that type. If the list of types is empty
// the result is nil.
func _NewSum(types []Type) Type {
if len(types) == 0 {
return nil
}
// What should happen if types contains a sum type?
// Do we flatten the types list? For now we check
// and panic. This should not be possible for the
// current use case of type lists.
// TODO(gri) Come up with the rules for sum types.
for _, t := range types {
if _, ok := t.(*_Sum); ok {
panic("sum type contains sum type - unimplemented")
}
}
if len(types) == 1 {
return types[0]
}
return &_Sum{types: types}
}
// is reports whether all types in t satisfy pred.
func (s *_Sum) is(pred func(Type) bool) bool {
if s == nil {
return false
}
for _, t := range s.types {
if !pred(t) {
return false
}
}
return true
}
// An Interface represents an interface type.
type Interface struct {
methods []*Func // ordered list of explicitly declared methods
@ -319,8 +272,8 @@ func unpackType(typ Type) []Type {
if typ == nil {
return nil
}
if sum := asSum(typ); sum != nil {
return sum.types
if u := asUnion(typ); u != nil {
return u.types
}
return []Type{typ}
}
@ -709,9 +662,16 @@ func optype(typ Type) Type {
// for a type parameter list of the form:
// (type T interface { type T }).
// See also issue #39680.
if u := t.Bound().allTypes; u != nil && u != typ {
// u != typ and u is a type parameter => under(u) != typ, so this is ok
return under(u)
if a := t.Bound().allTypes; a != nil && a != typ {
// If we have a union with a single entry, ignore
// any tilde because under(~t) == under(t).
if u, _ := a.(*Union); u != nil && u.NumTerms() == 1 {
a = u.types[0]
}
if a != typ {
// a != typ and a is a type parameter => under(a) != typ, so this is ok
return under(a)
}
}
return theTop
}
@ -793,7 +753,6 @@ func (t *Struct) Underlying() Type { return t }
func (t *Pointer) Underlying() Type { return t }
func (t *Tuple) Underlying() Type { return t }
func (t *Signature) Underlying() Type { return t }
func (t *_Sum) Underlying() Type { return t }
func (t *Interface) Underlying() Type { return t }
func (t *Map) Underlying() Type { return t }
func (t *Chan) Underlying() Type { return t }
@ -811,7 +770,6 @@ func (t *Struct) String() string { return TypeString(t, nil) }
func (t *Pointer) String() string { return TypeString(t, nil) }
func (t *Tuple) String() string { return TypeString(t, nil) }
func (t *Signature) String() string { return TypeString(t, nil) }
func (t *_Sum) String() string { return TypeString(t, nil) }
func (t *Interface) String() string { return TypeString(t, nil) }
func (t *Map) String() string { return TypeString(t, nil) }
func (t *Chan) String() string { return TypeString(t, nil) }
@ -826,7 +784,7 @@ func (t *top) String() string { return TypeString(t, nil) }
// under must only be called when a type is known
// to be fully set up.
func under(t Type) Type {
// TODO(gri) is this correct for *Sum?
// TODO(gri) is this correct for *Union?
if n := asNamed(t); n != nil {
return n.under()
}
@ -876,8 +834,8 @@ func asSignature(t Type) *Signature {
return op
}
func asSum(t Type) *_Sum {
op, _ := optype(t).(*_Sum)
func asUnion(t Type) *Union {
op, _ := optype(t).(*Union)
return op
}

View file

@ -158,11 +158,8 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) {
buf.WriteString("func")
writeSignature(buf, t, qf, visited)
case *_Sum:
writeTypeList(buf, t.types, qf, visited)
case *Union:
for i, e := range t.terms {
for i, e := range t.types {
if i > 0 {
buf.WriteString("|")
}

View file

@ -352,10 +352,6 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool {
u.nify(x.results, y.results, p)
}
case *_Sum:
// This should not happen with the current internal use of sum types.
panic("type inference across sum types not implemented")
case *Union:
// This should not happen with the current internal use of union types.
panic("type inference across union types not implemented")

View file

@ -13,16 +13,16 @@ import (
// API
// A Union represents a union of terms.
// A term is a type, possibly with a ~ (tilde) indication.
// A term is a type, possibly with a ~ (tilde) flag.
type Union struct {
terms []Type // terms are unique
types []Type // types are unique
tilde []bool // if tilde[i] is set, terms[i] is of the form ~T
}
func NewUnion(terms []Type, tilde []bool) Type { return newUnion(terms, tilde) }
func NewUnion(types []Type, tilde []bool) Type { return newUnion(types, tilde) }
func (u *Union) NumTerms() int { return len(u.terms) }
func (u *Union) Term(i int) (Type, bool) { return u.terms[i], u.tilde[i] }
func (u *Union) NumTerms() int { return len(u.types) }
func (u *Union) Term(i int) (Type, bool) { return u.types[i], u.tilde[i] }
func (u *Union) Underlying() Type { return u }
func (u *Union) String() string { return TypeString(u, nil) }
@ -30,26 +30,52 @@ func (u *Union) String() string { return TypeString(u, nil) }
// ----------------------------------------------------------------------------
// Implementation
func newUnion(terms []Type, tilde []bool) Type {
assert(len(terms) == len(tilde))
if terms == nil {
func newUnion(types []Type, tilde []bool) Type {
assert(len(types) == len(tilde))
if types == nil {
return nil
}
t := new(Union)
t.terms = terms
t.types = types
t.tilde = tilde
return t
}
// is reports whether f returned true for all terms (type, tilde) of u.
func (u *Union) is(f func(Type, bool) bool) bool {
if u == nil {
return false
}
for i, t := range u.types {
if !f(t, u.tilde[i]) {
return false
}
}
return true
}
// is reports whether f returned true for the underlying types of all terms of u.
func (u *Union) underIs(f func(Type) bool) bool {
if u == nil {
return false
}
for _, t := range u.types {
if !f(under(t)) {
return false
}
}
return true
}
func parseUnion(check *Checker, tlist []ast.Expr) Type {
var terms []Type
var types []Type
var tilde []bool
for _, x := range tlist {
t, d := parseTilde(check, x)
if len(tlist) == 1 && !d {
return t // single type
}
terms = append(terms, t)
types = append(types, t)
tilde = append(tilde, d)
}
@ -58,7 +84,7 @@ func parseUnion(check *Checker, tlist []ast.Expr) Type {
// for correctness of the code.
// Note: This is a quadratic algorithm, but unions tend to be short.
check.later(func() {
for i, t := range terms {
for i, t := range types {
t := expand(t)
if t == Typ[Invalid] {
continue
@ -88,14 +114,14 @@ func parseUnion(check *Checker, tlist []ast.Expr) Type {
}
// Complain about duplicate entries a|a, but also a|~a, and ~a|~a.
if includes(terms[:i], t) {
if includes(types[:i], t) {
// TODO(rfindley) this currently doesn't print the ~ if present
check.softErrorf(atPos(pos), _Todo, "duplicate term %s in union element", t)
}
}
})
return newUnion(terms, tilde)
return newUnion(types, tilde)
}
func parseTilde(check *Checker, x ast.Expr) (Type, bool) {
@ -106,3 +132,60 @@ func parseTilde(check *Checker, x ast.Expr) (Type, bool) {
}
return check.anyType(x), tilde
}
// intersect computes the intersection of the types x and y.
// Note: An incomming nil type stands for the top type. A top
// type result is returned as nil.
func intersect(x, y Type) (r Type) {
defer func() {
if r == theTop {
r = nil
}
}()
switch {
case x == theBottom || y == theBottom:
return theBottom
case x == nil || x == theTop:
return y
case y == nil || x == theTop:
return x
}
// Compute the terms which are in both x and y.
xu, _ := x.(*Union)
yu, _ := y.(*Union)
switch {
case xu != nil && yu != nil:
// Quadratic algorithm, but good enough for now.
// TODO(gri) fix asymptotic performance
var types []Type
var tilde []bool
for _, y := range yu.types {
if includes(xu.types, y) {
types = append(types, y)
tilde = append(tilde, true) // TODO(gri) fix this
}
}
if types != nil {
return newUnion(types, tilde)
}
case xu != nil:
if includes(xu.types, y) {
return y
}
case yu != nil:
if includes(yu.types, x) {
return x
}
default: // xu == nil && yu == nil
if Identical(x, y) {
return x
}
}
return theBottom
}