mirror of
https://github.com/golang/go
synced 2024-11-26 17:56:55 -07:00
[dev.typeparams] cmd/compile/internal/types2: use type terms to represent unions
This is just an internal representation change for now. Change-Id: I7e0126e9b17850ec020c2a60db13582761557bea Reviewed-on: https://go-review.googlesource.com/c/go/+/338092 Trust: Robert Griesemer <gri@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
parent
2fa8f00915
commit
ff0c0dbca6
@ -308,7 +308,7 @@ func (w *tpWalker) isParameterized(typ Type) (res bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case *Union:
|
case *Union:
|
||||||
return w.isParameterizedList(t.types)
|
return w.isParameterizedTermList(t.terms)
|
||||||
|
|
||||||
case *Signature:
|
case *Signature:
|
||||||
// t.tparams may not be nil if we are looking at a signature
|
// t.tparams may not be nil if we are looking at a signature
|
||||||
@ -336,7 +336,7 @@ func (w *tpWalker) isParameterized(typ Type) (res bool) {
|
|||||||
return w.isParameterized(t.elem)
|
return w.isParameterized(t.elem)
|
||||||
|
|
||||||
case *Named:
|
case *Named:
|
||||||
return w.isParameterizedList(t.targs)
|
return w.isParameterizedTypeList(t.targs)
|
||||||
|
|
||||||
case *TypeParam:
|
case *TypeParam:
|
||||||
// t must be one of w.tparams
|
// t must be one of w.tparams
|
||||||
@ -349,7 +349,7 @@ func (w *tpWalker) isParameterized(typ Type) (res bool) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *tpWalker) isParameterizedList(list []Type) bool {
|
func (w *tpWalker) isParameterizedTypeList(list []Type) bool {
|
||||||
for _, t := range list {
|
for _, t := range list {
|
||||||
if w.isParameterized(t) {
|
if w.isParameterized(t) {
|
||||||
return true
|
return true
|
||||||
@ -358,6 +358,15 @@ func (w *tpWalker) isParameterizedList(list []Type) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *tpWalker) isParameterizedTermList(list []*term) bool {
|
||||||
|
for _, t := range list {
|
||||||
|
if w.isParameterized(t.typ) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// inferB returns the list of actual type arguments inferred from the type parameters'
|
// inferB returns the list of actual type arguments inferred from the type parameters'
|
||||||
// bounds and an initial set of type arguments. If type inference is impossible because
|
// bounds and an initial set of type arguments. If type inference is impossible because
|
||||||
// unification fails, an error is reported if report is set to true, the resulting types
|
// unification fails, an error is reported if report is set to true, the resulting types
|
||||||
@ -466,7 +475,8 @@ func (check *Checker) structuralType(constraint Type) Type {
|
|||||||
if u, _ := types.(*Union); u != nil {
|
if u, _ := types.(*Union); u != nil {
|
||||||
if u.NumTerms() == 1 {
|
if u.NumTerms() == 1 {
|
||||||
// TODO(gri) do we need to respect tilde?
|
// TODO(gri) do we need to respect tilde?
|
||||||
return u.types[0]
|
t, _ := u.Term(0)
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ func (t *Interface) is(f func(Type, bool) bool) bool {
|
|||||||
// TODO(gri) should settle on top or nil to represent this case
|
// TODO(gri) should settle on top or nil to represent this case
|
||||||
return false // we must have at least one type! (was bug)
|
return false // we must have at least one type! (was bug)
|
||||||
case *Union:
|
case *Union:
|
||||||
return t.is(func(typ Type, tilde bool) bool { return f(typ, tilde) })
|
return t.is(func(t *term) bool { return f(t.typ, t.tilde) })
|
||||||
default:
|
default:
|
||||||
return f(t, false)
|
return f(t, false)
|
||||||
}
|
}
|
||||||
@ -260,8 +260,8 @@ func (check *Checker) interfaceType(ityp *Interface, iface *syntax.InterfaceType
|
|||||||
sortMethods(ityp.methods)
|
sortMethods(ityp.methods)
|
||||||
|
|
||||||
// Compute type set with a non-nil *Checker as soon as possible
|
// Compute type set with a non-nil *Checker as soon as possible
|
||||||
// to report any errors. Subsequent uses of type sets should be
|
// to report any errors. Subsequent uses of type sets will use
|
||||||
// using this computed type set and won't need to pass in a *Checker.
|
// this computed type set and won't need to pass in a *Checker.
|
||||||
check.later(func() { computeTypeSet(check, iface.Pos(), ityp) })
|
check.later(func() { computeTypeSet(check, iface.Pos(), ityp) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,13 +270,13 @@ func (x *operand) assignableTo(check *Checker, T Type, reason *string) (bool, er
|
|||||||
// x is an untyped value representable by a value of type T.
|
// x is an untyped value representable by a value of type T.
|
||||||
if isUntyped(Vu) {
|
if isUntyped(Vu) {
|
||||||
if t, ok := Tu.(*Union); ok {
|
if t, ok := Tu.(*Union); ok {
|
||||||
return t.is(func(t Type, tilde bool) bool {
|
return t.is(func(t *term) bool {
|
||||||
// TODO(gri) this could probably be more efficient
|
// TODO(gri) this could probably be more efficient
|
||||||
if tilde {
|
if t.tilde {
|
||||||
// TODO(gri) We need to check assignability
|
// TODO(gri) We need to check assignability
|
||||||
// for the underlying type of x.
|
// for the underlying type of x.
|
||||||
}
|
}
|
||||||
ok, _ := x.assignableTo(check, t, reason)
|
ok, _ := x.assignableTo(check, t.typ, reason)
|
||||||
return ok
|
return ok
|
||||||
}), _IncompatibleAssign
|
}), _IncompatibleAssign
|
||||||
}
|
}
|
||||||
|
@ -238,20 +238,8 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool {
|
|||||||
// types - each type appears exactly once. Thus, two union types
|
// types - each type appears exactly once. Thus, two union types
|
||||||
// must contain the same number of types to have chance of
|
// must contain the same number of types to have chance of
|
||||||
// being equal.
|
// being equal.
|
||||||
if y, ok := y.(*Union); ok && x.NumTerms() == y.NumTerms() {
|
if y, ok := y.(*Union); ok {
|
||||||
// Every type in x.types must be in y.types.
|
return identicalTerms(x.terms, y.terms)
|
||||||
// Quadratic algorithm, but probably good enough for now.
|
|
||||||
// TODO(gri) we need a fast quick type ID/hash for all types.
|
|
||||||
L:
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false // x is not in y.types
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case *Interface:
|
case *Interface:
|
||||||
|
@ -27,12 +27,13 @@ func TestSizeof(t *testing.T) {
|
|||||||
{Pointer{}, 8, 16},
|
{Pointer{}, 8, 16},
|
||||||
{Tuple{}, 12, 24},
|
{Tuple{}, 12, 24},
|
||||||
{Signature{}, 44, 88},
|
{Signature{}, 44, 88},
|
||||||
{Union{}, 24, 48},
|
{Union{}, 12, 24},
|
||||||
{Interface{}, 40, 80},
|
{Interface{}, 40, 80},
|
||||||
{Map{}, 16, 32},
|
{Map{}, 16, 32},
|
||||||
{Chan{}, 12, 24},
|
{Chan{}, 12, 24},
|
||||||
{Named{}, 88, 168},
|
{Named{}, 88, 168},
|
||||||
{TypeParam{}, 28, 48},
|
{TypeParam{}, 28, 48},
|
||||||
|
{term{}, 12, 24},
|
||||||
{top{}, 0, 0},
|
{top{}, 0, 0},
|
||||||
|
|
||||||
// Objects
|
// Objects
|
||||||
|
@ -145,12 +145,12 @@ func (subst *subster) typ(typ Type) Type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case *Union:
|
case *Union:
|
||||||
types, copied := subst.typeList(t.types)
|
terms, copied := subst.termList(t.terms)
|
||||||
if copied {
|
if copied {
|
||||||
// TODO(gri) Remove duplicates that may have crept in after substitution
|
// TODO(gri) Remove duplicates that may have crept in after substitution
|
||||||
// (unlikely but possible). This matters for the Identical
|
// (unlikely but possible). This matters for the Identical
|
||||||
// predicate on unions.
|
// predicate on unions.
|
||||||
return newUnion(types, t.tilde)
|
return &Union{terms}
|
||||||
}
|
}
|
||||||
|
|
||||||
case *Interface:
|
case *Interface:
|
||||||
@ -386,3 +386,21 @@ func (subst *subster) typeList(in []Type) (out []Type, copied bool) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (subst *subster) termList(in []*term) (out []*term, copied bool) {
|
||||||
|
out = in
|
||||||
|
for i, t := range in {
|
||||||
|
if u := subst.typ(t.typ); u != t.typ {
|
||||||
|
if !copied {
|
||||||
|
// first function that got substituted => allocate new out slice
|
||||||
|
// and copy all functions
|
||||||
|
new := make([]*term, len(in))
|
||||||
|
copy(new, out)
|
||||||
|
out = new
|
||||||
|
copied = true
|
||||||
|
}
|
||||||
|
out[i] = &term{t.tilde, u}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -31,9 +31,9 @@ type (
|
|||||||
_ interface{int|~ /* ERROR duplicate term int */ int }
|
_ interface{int|~ /* ERROR duplicate term int */ int }
|
||||||
_ interface{~int|~ /* ERROR duplicate term int */ int }
|
_ interface{~int|~ /* ERROR duplicate term int */ int }
|
||||||
|
|
||||||
// For now we do not permit interfaces with ~ or in unions.
|
// For now we do not permit interfaces with methods in unions.
|
||||||
_ interface{~ /* ERROR cannot use interface */ interface{}}
|
_ interface{~ /* ERROR invalid use of ~ */ interface{}}
|
||||||
_ interface{int|interface /* ERROR cannot use interface */ {}}
|
_ interface{int|interface /* ERROR cannot use .* in union */ { m() }}
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -60,7 +60,7 @@ func optype(typ Type) Type {
|
|||||||
// If we have a union with a single entry, ignore
|
// If we have a union with a single entry, ignore
|
||||||
// any tilde because under(~t) == under(t).
|
// any tilde because under(~t) == under(t).
|
||||||
if u, _ := a.(*Union); u != nil && u.NumTerms() == 1 {
|
if u, _ := a.(*Union); u != nil && u.NumTerms() == 1 {
|
||||||
a = u.types[0]
|
a, _ = u.Term(0)
|
||||||
}
|
}
|
||||||
if a != typ {
|
if a != typ {
|
||||||
// a != typ and a is a type parameter => under(a) != typ, so this is ok
|
// a != typ and a is a type parameter => under(a) != typ, so this is ok
|
||||||
|
@ -42,6 +42,13 @@ func (s *TypeSet) IsComparable() bool {
|
|||||||
return s.comparable && tcomparable
|
return s.comparable && tcomparable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(gri) IsTypeSet is not a great name. Find a better one.
|
||||||
|
|
||||||
|
// IsTypeSet reports whether the type set s is represented by a finite set of underlying types.
|
||||||
|
func (s *TypeSet) IsTypeSet() bool {
|
||||||
|
return !s.comparable && len(s.methods) == 0
|
||||||
|
}
|
||||||
|
|
||||||
// NumMethods returns the number of methods available.
|
// NumMethods returns the number of methods available.
|
||||||
func (s *TypeSet) NumMethods() int { return len(s.methods) }
|
func (s *TypeSet) NumMethods() int { return len(s.methods) }
|
||||||
|
|
||||||
|
@ -162,14 +162,14 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) {
|
|||||||
buf.WriteString("⊥")
|
buf.WriteString("⊥")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
for i, e := range t.types {
|
for i, t := range t.terms {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
buf.WriteByte('|')
|
buf.WriteByte('|')
|
||||||
}
|
}
|
||||||
if t.tilde[i] {
|
if t.tilde {
|
||||||
buf.WriteByte('~')
|
buf.WriteByte('~')
|
||||||
}
|
}
|
||||||
writeType(buf, e, qf, visited)
|
writeType(buf, t.typ, qf, visited)
|
||||||
}
|
}
|
||||||
|
|
||||||
case *Interface:
|
case *Interface:
|
||||||
|
@ -10,10 +10,8 @@ import "cmd/compile/internal/syntax"
|
|||||||
// API
|
// API
|
||||||
|
|
||||||
// A Union represents a union of terms.
|
// A Union represents a union of terms.
|
||||||
// A term is a type with a ~ (tilde) flag.
|
|
||||||
type Union struct {
|
type Union struct {
|
||||||
types []Type // types are unique
|
terms []*term
|
||||||
tilde []bool // if tilde[i] is set, terms[i] is of the form ~T
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUnion returns a new Union type with the given terms (types[i], tilde[i]).
|
// NewUnion returns a new Union type with the given terms (types[i], tilde[i]).
|
||||||
@ -21,9 +19,9 @@ type Union struct {
|
|||||||
// of no types.
|
// of no types.
|
||||||
func NewUnion(types []Type, tilde []bool) *Union { return newUnion(types, tilde) }
|
func NewUnion(types []Type, tilde []bool) *Union { return newUnion(types, tilde) }
|
||||||
|
|
||||||
func (u *Union) IsEmpty() bool { return len(u.types) == 0 }
|
func (u *Union) IsEmpty() bool { return len(u.terms) == 0 }
|
||||||
func (u *Union) NumTerms() int { return len(u.types) }
|
func (u *Union) NumTerms() int { return len(u.terms) }
|
||||||
func (u *Union) Term(i int) (Type, bool) { return u.types[i], u.tilde[i] }
|
func (u *Union) Term(i int) (Type, bool) { t := u.terms[i]; return t.typ, t.tilde }
|
||||||
|
|
||||||
func (u *Union) Underlying() Type { return u }
|
func (u *Union) Underlying() Type { return u }
|
||||||
func (u *Union) String() string { return TypeString(u, nil) }
|
func (u *Union) String() string { return TypeString(u, nil) }
|
||||||
@ -39,18 +37,20 @@ func newUnion(types []Type, tilde []bool) *Union {
|
|||||||
return emptyUnion
|
return emptyUnion
|
||||||
}
|
}
|
||||||
t := new(Union)
|
t := new(Union)
|
||||||
t.types = types
|
t.terms = make([]*term, len(types))
|
||||||
t.tilde = tilde
|
for i, typ := range types {
|
||||||
|
t.terms[i] = &term{tilde[i], typ}
|
||||||
|
}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
// is reports whether f returned true for all terms (type, tilde) of u.
|
// is reports whether f returns true for all terms of u.
|
||||||
func (u *Union) is(f func(Type, bool) bool) bool {
|
func (u *Union) is(f func(*term) bool) bool {
|
||||||
if u.IsEmpty() {
|
if u.IsEmpty() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for i, t := range u.types {
|
for _, t := range u.terms {
|
||||||
if !f(t, u.tilde[i]) {
|
if !f(t) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,8 +62,8 @@ func (u *Union) underIs(f func(Type) bool) bool {
|
|||||||
if u.IsEmpty() {
|
if u.IsEmpty() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, t := range u.types {
|
for _, t := range u.terms {
|
||||||
if !f(under(t)) {
|
if !f(under(t.typ)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,7 +83,7 @@ func parseUnion(check *Checker, tlist []syntax.Expr) Type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that each type is only present once in the type list.
|
// Ensure that each type is only present once in the type list.
|
||||||
// It's ok to do this check at the end because it's not a requirement
|
// It's ok to do this check later because it's not a requirement
|
||||||
// for correctness of the code.
|
// for correctness of the code.
|
||||||
// Note: This is a quadratic algorithm, but unions tend to be short.
|
// Note: This is a quadratic algorithm, but unions tend to be short.
|
||||||
check.later(func() {
|
check.later(func() {
|
||||||
@ -96,7 +96,7 @@ func parseUnion(check *Checker, tlist []syntax.Expr) Type {
|
|||||||
x := tlist[i]
|
x := tlist[i]
|
||||||
pos := syntax.StartPos(x)
|
pos := syntax.StartPos(x)
|
||||||
// We may not know the position of x if it was a typechecker-
|
// We may not know the position of x if it was a typechecker-
|
||||||
// introduced ~T type of a type list entry T. Use the position
|
// introduced ~T term for a type list entry T. Use the position
|
||||||
// of T instead.
|
// of T instead.
|
||||||
// TODO(gri) remove this test once we don't support type lists anymore
|
// TODO(gri) remove this test once we don't support type lists anymore
|
||||||
if !pos.IsKnown() {
|
if !pos.IsKnown() {
|
||||||
@ -106,13 +106,24 @@ func parseUnion(check *Checker, tlist []syntax.Expr) Type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
u := under(t)
|
u := under(t)
|
||||||
if tilde[i] && !Identical(u, t) {
|
f, _ := u.(*Interface)
|
||||||
|
if tilde[i] {
|
||||||
|
if f != nil {
|
||||||
|
check.errorf(x, "invalid use of ~ (%s is an interface)", t)
|
||||||
|
continue // don't report another error for t
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Identical(u, t) {
|
||||||
check.errorf(x, "invalid use of ~ (underlying type of %s is %s)", t, u)
|
check.errorf(x, "invalid use of ~ (underlying type of %s is %s)", t, u)
|
||||||
continue // don't report another error for t
|
continue // don't report another error for t
|
||||||
}
|
}
|
||||||
if _, ok := u.(*Interface); ok {
|
}
|
||||||
// A single type with a ~ is a single-term union.
|
|
||||||
check.errorf(pos, "cannot use interface %s with ~ or inside a union (implementation restriction)", t)
|
// Stand-alone embedded interfaces are ok and are handled by the single-type case
|
||||||
|
// in the beginning. Embedded interfaces with tilde are excluded above. If we reach
|
||||||
|
// here, we must have at least two terms in the union.
|
||||||
|
if f != nil && !f.typeSet().IsTypeSet() {
|
||||||
|
check.errorf(pos, "cannot use %s in union (interface contains methods)", t)
|
||||||
continue // don't report another error for t
|
continue // don't report another error for t
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,25 +175,7 @@ func intersect(x, y Type) (r Type) {
|
|||||||
yu, _ := y.(*Union)
|
yu, _ := y.(*Union)
|
||||||
switch {
|
switch {
|
||||||
case xu != nil && yu != nil:
|
case xu != nil && yu != nil:
|
||||||
// Quadratic algorithm, but good enough for now.
|
return &Union{intersectTerms(xu.terms, yu.terms)}
|
||||||
// TODO(gri) fix asymptotic performance
|
|
||||||
var types []Type
|
|
||||||
var tilde []bool
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newUnion(types, tilde)
|
|
||||||
|
|
||||||
case xu != nil:
|
case xu != nil:
|
||||||
if r, _ := xu.intersect(y, false); r != nil {
|
if r, _ := xu.intersect(y, false); r != nil {
|
||||||
@ -216,14 +209,16 @@ func includes(list []Type, typ Type) bool {
|
|||||||
// intersect computes the intersection of the union u and term (y, yt)
|
// intersect computes the intersection of the union u and term (y, yt)
|
||||||
// and returns the intersection term, if any. Otherwise the result is
|
// and returns the intersection term, if any. Otherwise the result is
|
||||||
// (nil, false).
|
// (nil, false).
|
||||||
|
// TODO(gri) this needs to cleaned up/removed once we switch to lazy
|
||||||
|
// union type set computation.
|
||||||
func (u *Union) intersect(y Type, yt bool) (Type, bool) {
|
func (u *Union) intersect(y Type, yt bool) (Type, bool) {
|
||||||
under_y := under(y)
|
under_y := under(y)
|
||||||
for i, x := range u.types {
|
for _, x := range u.terms {
|
||||||
xt := u.tilde[i]
|
xt := x.tilde
|
||||||
// determine which types xx, yy to compare
|
// determine which types xx, yy to compare
|
||||||
xx := x
|
xx := x.typ
|
||||||
if yt {
|
if yt {
|
||||||
xx = under(x)
|
xx = under(xx)
|
||||||
}
|
}
|
||||||
yy := y
|
yy := y
|
||||||
if xt {
|
if xt {
|
||||||
@ -239,3 +234,35 @@ func (u *Union) intersect(y Type, yt bool) (Type, bool) {
|
|||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func identicalTerms(list1, list2 []*term) bool {
|
||||||
|
if len(list1) != len(list2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Every term in list1 must be in list2.
|
||||||
|
// 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 list1 {
|
||||||
|
for _, y := range list2 {
|
||||||
|
if x.equal(y) {
|
||||||
|
continue L // x is in list2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func intersectTerms(list1, list2 []*term) (list []*term) {
|
||||||
|
// Quadratic algorithm, but good enough for now.
|
||||||
|
// TODO(gri) fix asymptotic performance
|
||||||
|
for _, x := range list1 {
|
||||||
|
for _, y := range list2 {
|
||||||
|
if r := x.intersect(y); r != nil {
|
||||||
|
list = append(list, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user