github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/typeparams/normalize.go (about)

     1  // Copyright 2021 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package typeparams
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"go/types"
    11  	"os"
    12  	"strings"
    13  )
    14  
    15  //go:generate go run copytermlist.go
    16  
    17  const debug = false
    18  
    19  var ErrEmptyTypeSet = errors.New("empty type set")
    20  
    21  // StructuralTerms returns a slice of terms representing the normalized
    22  // structural type restrictions of a type parameter, if any.
    23  //
    24  // Structural type restrictions of a type parameter are created via
    25  // non-interface types embedded in its constraint interface (directly, or via a
    26  // chain of interface embeddings). For example, in the declaration
    27  //  type T[P interface{~int; m()}] int
    28  // the structural restriction of the type parameter P is ~int.
    29  //
    30  // With interface embedding and unions, the specification of structural type
    31  // restrictions may be arbitrarily complex. For example, consider the
    32  // following:
    33  //
    34  //  type A interface{ ~string|~[]byte }
    35  //
    36  //  type B interface{ int|string }
    37  //
    38  //  type C interface { ~string|~int }
    39  //
    40  //  type T[P interface{ A|B; C }] int
    41  //
    42  // In this example, the structural type restriction of P is ~string|int: A|B
    43  // expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int,
    44  // which when intersected with C (~string|~int) yields ~string|int.
    45  //
    46  // StructuralTerms computes these expansions and reductions, producing a
    47  // "normalized" form of the embeddings. A structural restriction is normalized
    48  // if it is a single union containing no interface terms, and is minimal in the
    49  // sense that removing any term changes the set of types satisfying the
    50  // constraint. It is left as a proof for the reader that, modulo sorting, there
    51  // is exactly one such normalized form.
    52  //
    53  // Because the minimal representation always takes this form, StructuralTerms
    54  // returns a slice of tilde terms corresponding to the terms of the union in
    55  // the normalized structural restriction. An error is returned if the
    56  // constraint interface is invalid, exceeds complexity bounds, or has an empty
    57  // type set. In the latter case, StructuralTerms returns ErrEmptyTypeSet.
    58  //
    59  // StructuralTerms makes no guarantees about the order of terms, except that it
    60  // is deterministic.
    61  func StructuralTerms(tparam *TypeParam) ([]*Term, error) {
    62  	constraint := tparam.Constraint()
    63  	if constraint == nil {
    64  		return nil, fmt.Errorf("%s has nil constraint", tparam)
    65  	}
    66  	iface, _ := constraint.Underlying().(*types.Interface)
    67  	if iface == nil {
    68  		return nil, fmt.Errorf("constraint is %T, not *types.Interface", constraint.Underlying())
    69  	}
    70  	return InterfaceTermSet(iface)
    71  }
    72  
    73  // InterfaceTermSet computes the normalized terms for a constraint interface,
    74  // returning an error if the term set cannot be computed or is empty. In the
    75  // latter case, the error will be ErrEmptyTypeSet.
    76  //
    77  // See the documentation of StructuralTerms for more information on
    78  // normalization.
    79  func InterfaceTermSet(iface *types.Interface) ([]*Term, error) {
    80  	return computeTermSet(iface)
    81  }
    82  
    83  // UnionTermSet computes the normalized terms for a union, returning an error
    84  // if the term set cannot be computed or is empty. In the latter case, the
    85  // error will be ErrEmptyTypeSet.
    86  //
    87  // See the documentation of StructuralTerms for more information on
    88  // normalization.
    89  func UnionTermSet(union *Union) ([]*Term, error) {
    90  	return computeTermSet(union)
    91  }
    92  
    93  func computeTermSet(typ types.Type) ([]*Term, error) {
    94  	tset, err := computeTermSetInternal(typ, make(map[types.Type]*termSet), 0)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  	if tset.terms.isEmpty() {
    99  		return nil, ErrEmptyTypeSet
   100  	}
   101  	if tset.terms.isAll() {
   102  		return nil, nil
   103  	}
   104  	var terms []*Term
   105  	for _, term := range tset.terms {
   106  		terms = append(terms, NewTerm(term.tilde, term.typ))
   107  	}
   108  	return terms, nil
   109  }
   110  
   111  // A termSet holds the normalized set of terms for a given type.
   112  //
   113  // The name termSet is intentionally distinct from 'type set': a type set is
   114  // all types that implement a type (and includes method restrictions), whereas
   115  // a term set just represents the structural restrictions on a type.
   116  type termSet struct {
   117  	complete bool
   118  	terms    termlist
   119  }
   120  
   121  func indentf(depth int, format string, args ...interface{}) {
   122  	fmt.Fprintf(os.Stderr, strings.Repeat(".", depth)+format+"\n", args...)
   123  }
   124  
   125  func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth int) (res *termSet, err error) {
   126  	if t == nil {
   127  		panic("nil type")
   128  	}
   129  
   130  	if debug {
   131  		indentf(depth, "%s", t.String())
   132  		defer func() {
   133  			if err != nil {
   134  				indentf(depth, "=> %s", err)
   135  			} else {
   136  				indentf(depth, "=> %s", res.terms.String())
   137  			}
   138  		}()
   139  	}
   140  
   141  	const maxTermCount = 100
   142  	if tset, ok := seen[t]; ok {
   143  		if !tset.complete {
   144  			return nil, fmt.Errorf("cycle detected in the declaration of %s", t)
   145  		}
   146  		return tset, nil
   147  	}
   148  
   149  	// Mark the current type as seen to avoid infinite recursion.
   150  	tset := new(termSet)
   151  	defer func() {
   152  		tset.complete = true
   153  	}()
   154  	seen[t] = tset
   155  
   156  	switch u := t.Underlying().(type) {
   157  	case *types.Interface:
   158  		// The term set of an interface is the intersection of the term sets of its
   159  		// embedded types.
   160  		tset.terms = allTermlist
   161  		for i := 0; i < u.NumEmbeddeds(); i++ {
   162  			embedded := u.EmbeddedType(i)
   163  			if _, ok := embedded.Underlying().(*TypeParam); ok {
   164  				return nil, fmt.Errorf("invalid embedded type %T", embedded)
   165  			}
   166  			tset2, err := computeTermSetInternal(embedded, seen, depth+1)
   167  			if err != nil {
   168  				return nil, err
   169  			}
   170  			tset.terms = tset.terms.intersect(tset2.terms)
   171  		}
   172  	case *Union:
   173  		// The term set of a union is the union of term sets of its terms.
   174  		tset.terms = nil
   175  		for i := 0; i < u.Len(); i++ {
   176  			t := u.Term(i)
   177  			var terms termlist
   178  			switch t.Type().Underlying().(type) {
   179  			case *types.Interface:
   180  				tset2, err := computeTermSetInternal(t.Type(), seen, depth+1)
   181  				if err != nil {
   182  					return nil, err
   183  				}
   184  				terms = tset2.terms
   185  			case *TypeParam, *Union:
   186  				// A stand-alone type parameter or union is not permitted as union
   187  				// term.
   188  				return nil, fmt.Errorf("invalid union term %T", t)
   189  			default:
   190  				if t.Type() == types.Typ[types.Invalid] {
   191  					continue
   192  				}
   193  				terms = termlist{{t.Tilde(), t.Type()}}
   194  			}
   195  			tset.terms = tset.terms.union(terms)
   196  			if len(tset.terms) > maxTermCount {
   197  				return nil, fmt.Errorf("exceeded max term count %d", maxTermCount)
   198  			}
   199  		}
   200  	case *TypeParam:
   201  		panic("unreachable")
   202  	default:
   203  		// For all other types, the term set is just a single non-tilde term
   204  		// holding the type itself.
   205  		if u != types.Typ[types.Invalid] {
   206  			tset.terms = termlist{{false, t}}
   207  		}
   208  	}
   209  	return tset, nil
   210  }
   211  
   212  // under is a facade for the go/types internal function of the same name. It is
   213  // used by typeterm.go.
   214  func under(t types.Type) types.Type {
   215  	return t.Underlying()
   216  }