blob: ee1341be210daf7b39e5d9066c7074d29df8b707 [file] [log] [blame]
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package types
// Implementation of structural type computation for types.
// TODO: we would like to depend only on the types2 computation of structural type,
// but we can only do that the next time we change the export format and export
// structural type info along with each constraint type, since the compiler imports
// types directly into types1 format.
// A term describes elementary type sets:
//
// term{false, T} set of type T
// term{true, T} set of types with underlying type t
// term{} empty set (we specifically check for typ == nil)
type term struct {
tilde bool
typ *Type
}
// StructuralType returns the structural type of an interface, or nil if it has no
// structural type.
func (t *Type) StructuralType() *Type {
sts, _ := specificTypes(t)
var su *Type
for _, st := range sts {
u := st.typ.Underlying()
if su != nil {
u = match(su, u)
if u == nil {
return nil
}
}
// su == nil || match(su, u) != nil
su = u
}
return su
}
// If x and y are identical, match returns x.
// If x and y are identical channels but for their direction
// and one of them is unrestricted, match returns the channel
// with the restricted direction.
// In all other cases, match returns nil.
// x and y are assumed to be underlying types, hence are not named types.
func match(x, y *Type) *Type {
if IdenticalStrict(x, y) {
return x
}
if x.IsChan() && y.IsChan() && IdenticalStrict(x.Elem(), y.Elem()) {
// We have channels that differ in direction only.
// If there's an unrestricted channel, select the restricted one.
// If both have the same direction, return x (either is fine).
switch {
case x.ChanDir().CanSend() && x.ChanDir().CanRecv():
return y
case y.ChanDir().CanSend() && y.ChanDir().CanRecv():
return x
}
}
return nil
}
// specificTypes returns the list of specific types of an interface type or nil if
// there are none. It also returns a flag that indicates, for an empty term list
// result, whether it represents the empty set, or the infinite set of all types (in
// both cases, there are no specific types).
func specificTypes(t *Type) (list []term, inf bool) {
t.wantEtype(TINTER)
// We have infinite term list before processing any type elements
// (or if there are no type elements).
inf = true
for _, m := range t.Methods().Slice() {
var r2 []term
inf2 := false
switch {
case m.IsMethod():
inf2 = true
case m.Type.IsUnion():
nt := m.Type.NumTerms()
for i := 0; i < nt; i++ {
t, tilde := m.Type.Term(i)
if t.IsInterface() {
r3, r3inf := specificTypes(t)
if r3inf {
// Union with an infinite set of types is
// infinite, so skip remaining terms.
r2 = nil
inf2 = true
break
}
// Add the elements of r3 to r2.
for _, r3e := range r3 {
r2 = insertType(r2, r3e)
}
} else {
r2 = insertType(r2, term{tilde, t})
}
}
case m.Type.IsInterface():
r2, inf2 = specificTypes(m.Type)
default:
// m.Type is a single non-interface type, so r2 is just a
// one-element list, inf2 is false.
r2 = []term{{false, m.Type}}
}
if inf2 {
// If the current type element has infinite types,
// its intersection with r is just r, so skip this type element.
continue
}
if inf {
// If r is infinite, then the intersection of r and r2 is just r2.
list = r2
inf = false
continue
}
// r and r2 are finite, so intersect r and r2.
var r3 []term
for _, re := range list {
for _, r2e := range r2 {
if tm := intersect(re, r2e); tm.typ != nil {
r3 = append(r3, tm)
}
}
}
list = r3
}
return
}
// insertType adds t to the returned list if it is not already in list.
func insertType(list []term, tm term) []term {
for i, elt := range list {
if new := union(elt, tm); new.typ != nil {
// Replace existing elt with the union of elt and new.
list[i] = new
return list
}
}
return append(list, tm)
}
// If x and y are disjoint, return term with nil typ (which means the union should
// include both types). If x and y are not disjoint, return the single type which is
// the union of x and y.
func union(x, y term) term {
if disjoint(x, y) {
return term{false, nil}
}
if x.tilde || !y.tilde {
return x
}
return y
}
// intersect returns the intersection x ∩ y.
func intersect(x, y term) term {
if disjoint(x, y) {
return term{false, nil}
}
if !x.tilde || y.tilde {
return x
}
return y
}
// disjoint reports whether x ∩ y == ∅.
func disjoint(x, y term) bool {
ux := x.typ
if y.tilde {
ux = ux.Underlying()
}
uy := y.typ
if x.tilde {
uy = uy.Underlying()
}
return !IdenticalStrict(ux, uy)
}