]> Cypherpunks.ru repositories - gostls13.git/commitdiff
[dev.typeparams] cmd/compile/internal/types2: clean up type set/union intersection
authorRobert Griesemer <gri@golang.org>
Fri, 28 May 2021 02:03:16 +0000 (19:03 -0700)
committerRobert Griesemer <gri@golang.org>
Wed, 2 Jun 2021 23:19:26 +0000 (23:19 +0000)
- Eliminate the need for bottom type: This is now represented by
  an empty union (denoting the set of no types).

- Clean up type set intersection and incorporate tilde information
  in intersection operation and satisfaction tests.

- Minor cleanups along the way.

- Note: The intersection algorithm does not always compute the
        largest possible intersection. To be addressed in a follow-up CL.

Change-Id: I7fa19df5996da36a4d8f29300d30a0aa4d8a3e5c
Reviewed-on: https://go-review.googlesource.com/c/go/+/323354
Trust: Robert Griesemer <gri@golang.org>
Run-TryBot: Robert Griesemer <gri@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>

src/cmd/compile/internal/types2/interface.go
src/cmd/compile/internal/types2/predicates.go
src/cmd/compile/internal/types2/sanitize.go
src/cmd/compile/internal/types2/sizeof_test.go
src/cmd/compile/internal/types2/subst.go
src/cmd/compile/internal/types2/testdata/check/issues.go2
src/cmd/compile/internal/types2/testdata/examples/constraints.go2
src/cmd/compile/internal/types2/type.go
src/cmd/compile/internal/types2/typestring.go
src/cmd/compile/internal/types2/union.go

index db34d0705fd18306766af9d98f0b0762278f078b..770b8ba5cc8ae2340d783750faa4489ca931f8bb 100644 (file)
@@ -109,16 +109,6 @@ func flattenUnion(list []syntax.Expr, x syntax.Expr) []syntax.Expr {
        return append(list, x)
 }
 
-// includes reports whether typ is in list
-func includes(list []Type, typ Type) bool {
-       for _, e := range list {
-               if Identical(typ, e) {
-                       return true
-               }
-       }
-       return false
-}
-
 func (check *Checker) completeInterface(pos syntax.Pos, ityp *Interface) {
        if ityp.allMethods != nil {
                return
index bcb3e221d00b2741aa2a4695f3e1a9dc2ff7da2c..74436836cd86c2823207b48b26d47bb4f84d2234 100644 (file)
@@ -97,9 +97,9 @@ func comparable(T Type, seen map[Type]bool) bool {
        seen[T] = true
 
        // If T is a type parameter not constrained by any type
-       // list (i.e., it's underlying type is the top type),
+       // list (i.e., it's operational type is the top type),
        // T is comparable if it has the == method. Otherwise,
-       // the underlying type "wins". For instance
+       // the operational type "wins". For instance
        //
        //     interface{ comparable; type []byte }
        //
@@ -370,10 +370,9 @@ func (check *Checker) identical0(x, y Type, cmpTags bool, p *ifacePair) bool {
        // case *instance:
        //      unreachable since types are expanded
 
-       case *bottom, *top:
-               // Either both types are theBottom, or both are theTop in which
-               // case the initial x == y check will have caught them. Otherwise
-               // they are not identical.
+       case *top:
+               // Either both types are theTop in which case the initial x == y check
+               // will have caught them. Otherwise they are not identical.
 
        case nil:
                // avoid a crash in case of nil type
index ce26bab186ee148ec462fed5f604b4dbfbc3f9dd..03aef90fe132358cb7cd6b5d5c7a42d7f839e31e 100644 (file)
@@ -77,7 +77,7 @@ func (s sanitizer) typ(typ Type) Type {
        s[typ] = typ
 
        switch t := typ.(type) {
-       case *Basic, *bottom, *top:
+       case *Basic, *top:
                // nothing to do
 
        case *Array:
index d3c391161ed7e080625cc541c48a95a3a796a696..daa039bf92a0b2cbe37d3f36c4a0c7d76785d1b3 100644 (file)
@@ -34,7 +34,6 @@ func TestSizeof(t *testing.T) {
                {Named{}, 68, 136},
                {TypeParam{}, 28, 48},
                {instance{}, 52, 96},
-               {bottom{}, 0, 0},
                {top{}, 0, 0},
 
                // Objects
index bfec61a06563781fac3c31a35248bd275a8e7bcc..617a03ddbc15562214c544646d34413053292372 100644 (file)
@@ -206,7 +206,7 @@ func (check *Checker) satisfies(pos syntax.Pos, targ Type, tpar *TypeParam, smap
 
        // Otherwise, targ's type or underlying type must also be one of the interface types listed, if any.
        if !iface.isSatisfiedBy(targ) {
-               check.softErrorf(pos, "%s does not satisfy %s (%s not found in %s)", targ, tpar.bound, under(targ), iface.allTypes)
+               check.softErrorf(pos, "%s does not satisfy %s (%s not found in %s)", targ, tpar.bound, targ, iface.allTypes)
                return false
        }
 
@@ -249,7 +249,7 @@ func (subst *subster) typ(typ Type) Type {
                // Call typOrNil if it's possible that typ is nil.
                panic("nil typ")
 
-       case *Basic, *bottom, *top:
+       case *Basic, *top:
                // nothing to do
 
        case *Array:
index 1c73b5da9219c04c1b2a479f594fc7d315ab3948..f0a7b247489f29cce0ee2d90bed4fbf3b0cacc02 100644 (file)
@@ -234,7 +234,7 @@ func _[T interface{ type func() }](f T) {
 
 type sliceOf[E any] interface{ type []E }
 
-func append[T interface{}, S sliceOf[T], T2 interface{ type T }](s S, t ...T2) S
+func append[T interface{}, S sliceOf[T], T2 interface{ T }](s S, t ...T2) S
 
 var f           func()
 var cancelSlice []context.CancelFunc
index e8b3912884cb8e40fb7d2011c5c830789b494197..f6291ccf7dd73650dab795bf11881f468e1099f1 100644 (file)
@@ -23,3 +23,17 @@ type (
        _ interface{~ /* ERROR cannot use interface */ interface{}}
        _ interface{int|interface /* ERROR cannot use interface */ {}}
 )
+
+// Multiple embedded union elements are intersected. The order in which they
+// appear in the interface doesn't matter since intersection is a symmetric
+// operation.
+
+type myInt1 int
+type myInt2 int
+
+func _[T interface{ myInt1|myInt2; ~int }]() T { return T(0) }
+func _[T interface{ ~int; myInt1|myInt2 }]() T { return T(0) }
+
+// Here the intersections are empty - there's no type that's in the type set of T.
+func _[T interface{ myInt1|myInt2; int }]() T { return T(0 /* ERROR cannot convert */ ) }
+func _[T interface{ int; myInt1|myInt2 }]() T { return T(0 /* ERROR cannot convert */ ) }
index aab75811b8a6f4fbb4f941c3d1dcf81c90e61f25..990b9d374c620804f232d1a853f20bc5a2a94c27 100644 (file)
@@ -376,7 +376,6 @@ func (t *Interface) Method(i int) *Func { t.Complete(); return t.allMethods[i] }
 // Empty reports whether t is the empty interface.
 func (t *Interface) Empty() bool {
        t.Complete()
-       // A non-nil allTypes may still have length 0 but represents the bottom type.
        return len(t.allMethods) == 0 && t.allTypes == nil
 }
 
@@ -431,11 +430,15 @@ func (t *Interface) iterate(f func(*Interface) bool, seen map[*Interface]bool) b
 //           "implements" predicate.
 func (t *Interface) isSatisfiedBy(typ Type) bool {
        t.Complete()
-       if t.allTypes == nil {
-               return true
+       switch t := t.allTypes.(type) {
+       case nil:
+               return true // no type restrictions
+       case *Union:
+               r, _ := t.intersect(typ, false)
+               return r != nil
+       default:
+               return Identical(t, typ)
        }
-       types := unpack(t.allTypes)
-       return includes(types, typ) || includes(types, under(typ))
 }
 
 // Complete computes the interface's method set. It must be called by users of
@@ -654,13 +657,11 @@ func (t *TypeParam) Bound() *Interface {
        return iface
 }
 
-// optype returns a type's operational type. Except for
-// type parameters, the operational type is the same
-// as the underlying type (as returned by under). For
-// Type parameters, the operational type is determined
-// by the corresponding type bound's type list. The
-// result may be the bottom or top type, but it is never
-// the incoming type parameter.
+// optype returns a type's operational type. Except for type parameters,
+// the operational type is the same as the underlying type (as returned
+// by under). For Type parameters, the operational type is determined
+// by the corresponding type constraint. The result may be the top type,
+// but it is never the incoming type parameter.
 func optype(typ Type) Type {
        if t := asTypeParam(typ); t != nil {
                // If the optype is typ, return the top type as we have
@@ -733,20 +734,11 @@ var expandf func(Type) Type
 
 func init() { expandf = expand }
 
-// bottom represents the bottom of the type lattice.
-// It is the underlying type of a type parameter that
-// cannot be satisfied by any type, usually because
-// the intersection of type constraints left nothing).
-type bottom struct{}
-
-// theBottom is the singleton bottom type.
-var theBottom = &bottom{}
-
 // top represents the top of the type lattice.
 // It is the underlying type of a type parameter that
 // can be satisfied by any type (ignoring methods),
-// usually because the type constraint has no type
-// list.
+// because its type constraint contains no restrictions
+// besides methods.
 type top struct{}
 
 // theTop is the singleton top type.
@@ -766,7 +758,6 @@ func (t *Chan) Underlying() Type      { return t }
 func (t *Named) Underlying() Type     { return t.underlying }
 func (t *TypeParam) Underlying() Type { return t }
 func (t *instance) Underlying() Type  { return t }
-func (t *bottom) Underlying() Type    { return t }
 func (t *top) Underlying() Type       { return t }
 
 // Type-specific implementations of String.
@@ -783,7 +774,6 @@ func (t *Chan) String() string      { return TypeString(t, nil) }
 func (t *Named) String() string     { return TypeString(t, nil) }
 func (t *TypeParam) String() string { return TypeString(t, nil) }
 func (t *instance) String() string  { return TypeString(t, nil) }
-func (t *bottom) String() string    { return TypeString(t, nil) }
 func (t *top) String() string       { return TypeString(t, nil) }
 
 // under returns the true expanded underlying type.
index 466beb239888315ef6b0397d507f1128e45f29b6..28583b62d9a8cb479caf32f18abf390fd765fa1b 100644 (file)
@@ -158,6 +158,10 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) {
                writeSignature(buf, t, qf, visited)
 
        case *Union:
+               if t.IsEmpty() {
+                       buf.WriteString("⊥")
+                       break
+               }
                for i, e := range t.types {
                        if i > 0 {
                                buf.WriteString("|")
@@ -294,9 +298,6 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) {
                writeTypeList(buf, t.targs, qf, visited)
                buf.WriteByte(']')
 
-       case *bottom:
-               buf.WriteString("⊥")
-
        case *top:
                buf.WriteString("⊤")
 
index a5ef721ee6bcde7671f25de17a60926f993701a7..671e36111b14caf470fd8cc921ec1ed67009ba37 100644 (file)
@@ -16,8 +16,12 @@ type Union struct {
        tilde []bool // if tilde[i] is set, terms[i] is of the form ~T
 }
 
-func NewUnion(types []Type, tilde []bool) Type { return newUnion(types, tilde) }
+// NewUnion returns a new Union type with the given terms (types[i], tilde[i]).
+// The lengths of both arguments must match. An empty union represents the set
+// of no types.
+func NewUnion(types []Type, tilde []bool) *Union { return newUnion(types, tilde) }
 
+func (u *Union) IsEmpty() bool           { return len(u.types) == 0 }
 func (u *Union) NumTerms() int           { return len(u.types) }
 func (u *Union) Term(i int) (Type, bool) { return u.types[i], u.tilde[i] }
 
@@ -27,10 +31,12 @@ func (u *Union) String() string   { return TypeString(u, nil) }
 // ----------------------------------------------------------------------------
 // Implementation
 
-func newUnion(types []Type, tilde []bool) Type {
+var emptyUnion = new(Union)
+
+func newUnion(types []Type, tilde []bool) *Union {
        assert(len(types) == len(tilde))
-       if types == nil {
-               return nil
+       if len(types) == 0 {
+               return emptyUnion
        }
        t := new(Union)
        t.types = types
@@ -40,7 +46,7 @@ func newUnion(types []Type, tilde []bool) Type {
 
 // 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 {
+       if u.IsEmpty() {
                return false
        }
        for i, t := range u.types {
@@ -53,7 +59,7 @@ func (u *Union) is(f func(Type, bool) bool) bool {
 
 // 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 {
+       if u.IsEmpty() {
                return false
        }
        for _, t := range u.types {
@@ -130,26 +136,24 @@ func parseTilde(check *Checker, x syntax.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.
+// intersect computes the intersection of the types x and y,
+// A nil type stands for the set of all types; an empty union
+// stands for the set of no types.
 func intersect(x, y Type) (r Type) {
-       defer func() {
-               if r == theTop {
-                       r = nil
-               }
-       }()
-
+       // If one of the types is nil (no restrictions)
+       // the result is the other type.
        switch {
-       case x == theBottom || y == theBottom:
-               return theBottom
-       case x == nil || x == theTop:
+       case x == nil:
                return y
-       case y == nil || x == theTop:
+       case y == nil:
                return x
        }
 
        // Compute the terms which are in both x and y.
+       // TODO(gri) This is not correct as it may not always compute
+       //           the "largest" intersection. For instance, for
+       //           x = myInt|~int, y = ~int
+       //           we get the result myInt but we should get ~int.
        xu, _ := x.(*Union)
        yu, _ := y.(*Union)
        switch {
@@ -158,23 +162,29 @@ func intersect(x, y Type) (r Type) {
                // 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
+               for j, y := range yu.types {
+                       yt := yu.tilde[j]
+                       if r, rt := xu.intersect(y, yt); r != nil {
+                               // Terms x[i] and y[j] match: Select the one that
+                               // is not a ~t because that is the intersection
+                               // type. If both are ~t, they are identical:
+                               //  T ∩  T =  T
+                               //  T ∩ ~t =  T
+                               // ~t ∩  T =  T
+                               // ~t ∩ ~t = ~t
+                               types = append(types, r)
+                               tilde = append(tilde, rt)
                        }
                }
-               if types != nil {
-                       return newUnion(types, tilde)
-               }
+               return newUnion(types, tilde)
 
        case xu != nil:
-               if includes(xu.types, y) {
+               if r, _ := xu.intersect(y, false); r != nil {
                        return y
                }
 
        case yu != nil:
-               if includes(yu.types, x) {
+               if r, _ := yu.intersect(x, false); r != nil {
                        return x
                }
 
@@ -184,5 +194,42 @@ func intersect(x, y Type) (r Type) {
                }
        }
 
-       return theBottom
+       return emptyUnion
+}
+
+// includes reports whether typ is in list.
+func includes(list []Type, typ Type) bool {
+       for _, e := range list {
+               if Identical(typ, e) {
+                       return true
+               }
+       }
+       return false
+}
+
+// intersect computes the intersection of the union u and term (y, yt)
+// and returns the intersection term, if any. Otherwise the result is
+// (nil, false).
+func (u *Union) intersect(y Type, yt bool) (Type, bool) {
+       under_y := under(y)
+       for i, x := range u.types {
+               xt := u.tilde[i]
+               // determine which types xx, yy to compare
+               xx := x
+               if yt {
+                       xx = under(x)
+               }
+               yy := y
+               if xt {
+                       yy = under_y
+               }
+               if Identical(xx, yy) {
+                       //  T ∩  T =  T
+                       //  T ∩ ~t =  T
+                       // ~t ∩  T =  T
+                       // ~t ∩ ~t = ~t
+                       return xx, xt && yt
+               }
+       }
+       return nil, false
 }