// Copyright 2021 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 typeparams import ( "errors" "fmt" "go/types" "os" "strings" ) //go:generate go run copytermlist.go const debug = false var ErrEmptyTypeSet = errors.New("empty type set") // StructuralTerms returns a slice of terms representing the normalized // structural type restrictions of a type parameter, if any. // // Structural type restrictions of a type parameter are created via // non-interface types embedded in its constraint interface (directly, or via a // chain of interface embeddings). For example, in the declaration // type T[P interface{~int; m()}] int // the structural restriction of the type parameter P is ~int. // // With interface embedding and unions, the specification of structural type // restrictions may be arbitrarily complex. For example, consider the // following: // // type A interface{ ~string|~[]byte } // // type B interface{ int|string } // // type C interface { ~string|~int } // // type T[P interface{ A|B; C }] int // // In this example, the structural type restriction of P is ~string|int: A|B // expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int, // which when intersected with C (~string|~int) yields ~string|int. // // StructuralTerms computes these expansions and reductions, producing a // "normalized" form of the embeddings. A structural restriction is normalized // if it is a single union containing no interface terms, and is minimal in the // sense that removing any term changes the set of types satisfying the // constraint. It is left as a proof for the reader that, modulo sorting, there // is exactly one such normalized form. // // Because the minimal representation always takes this form, StructuralTerms // returns a slice of tilde terms corresponding to the terms of the union in // the normalized structural restriction. An error is returned if the // constraint interface is invalid, exceeds complexity bounds, or has an empty // type set. In the latter case, StructuralTerms returns ErrEmptyTypeSet. // // StructuralTerms makes no guarantees about the order of terms, except that it // is deterministic. func StructuralTerms(tparam *TypeParam) ([]*Term, error) { constraint := tparam.Constraint() if constraint == nil { return nil, fmt.Errorf("%s has nil constraint", tparam) } iface, _ := constraint.Underlying().(*types.Interface) if iface == nil { return nil, fmt.Errorf("constraint is %T, not *types.Interface", constraint.Underlying()) } return InterfaceTermSet(iface) } // InterfaceTermSet computes the normalized terms for a constraint interface, // returning an error if the term set cannot be computed or is empty. In the // latter case, the error will be ErrEmptyTypeSet. // // See the documentation of StructuralTerms for more information on // normalization. func InterfaceTermSet(iface *types.Interface) ([]*Term, error) { return computeTermSet(iface) } // UnionTermSet computes the normalized terms for a union, returning an error // if the term set cannot be computed or is empty. In the latter case, the // error will be ErrEmptyTypeSet. // // See the documentation of StructuralTerms for more information on // normalization. func UnionTermSet(union *Union) ([]*Term, error) { return computeTermSet(union) } func computeTermSet(typ types.Type) ([]*Term, error) { tset, err := computeTermSetInternal(typ, make(map[types.Type]*termSet), 0) if err != nil { return nil, err } if tset.terms.isEmpty() { return nil, ErrEmptyTypeSet } if tset.terms.isAll() { return nil, nil } var terms []*Term for _, term := range tset.terms { terms = append(terms, NewTerm(term.tilde, term.typ)) } return terms, nil } // A termSet holds the normalized set of terms for a given type. // // The name termSet is intentionally distinct from 'type set': a type set is // all types that implement a type (and includes method restrictions), whereas // a term set just represents the structural restrictions on a type. type termSet struct { complete bool terms termlist } func indentf(depth int, format string, args ...interface{}) { fmt.Fprintf(os.Stderr, strings.Repeat(".", depth)+format+"\n", args...) } func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth int) (res *termSet, err error) { if t == nil { panic("nil type") } if debug { indentf(depth, "%s", t.String()) defer func() { if err != nil { indentf(depth, "=> %s", err) } else { indentf(depth, "=> %s", res.terms.String()) } }() } const maxTermCount = 100 if tset, ok := seen[t]; ok { if !tset.complete { return nil, fmt.Errorf("cycle detected in the declaration of %s", t) } return tset, nil } // Mark the current type as seen to avoid infinite recursion. tset := new(termSet) defer func() { tset.complete = true }() seen[t] = tset switch u := t.Underlying().(type) { case *types.Interface: // The term set of an interface is the intersection of the term sets of its // embedded types. tset.terms = allTermlist for i := 0; i < u.NumEmbeddeds(); i++ { embedded := u.EmbeddedType(i) if _, ok := embedded.Underlying().(*TypeParam); ok { return nil, fmt.Errorf("invalid embedded type %T", embedded) } tset2, err := computeTermSetInternal(embedded, seen, depth+1) if err != nil { return nil, err } tset.terms = tset.terms.intersect(tset2.terms) } case *Union: // The term set of a union is the union of term sets of its terms. tset.terms = nil for i := 0; i < u.Len(); i++ { t := u.Term(i) var terms termlist switch t.Type().Underlying().(type) { case *types.Interface: tset2, err := computeTermSetInternal(t.Type(), seen, depth+1) if err != nil { return nil, err } terms = tset2.terms case *TypeParam, *Union: // A stand-alone type parameter or union is not permitted as union // term. return nil, fmt.Errorf("invalid union term %T", t) default: if t.Type() == types.Typ[types.Invalid] { continue } terms = termlist{{t.Tilde(), t.Type()}} } tset.terms = tset.terms.union(terms) if len(tset.terms) > maxTermCount { return nil, fmt.Errorf("exceeded max term count %d", maxTermCount) } } case *TypeParam: panic("unreachable") default: // For all other types, the term set is just a single non-tilde term // holding the type itself. if u != types.Typ[types.Invalid] { tset.terms = termlist{{false, t}} } } return tset, nil } // under is a facade for the go/types internal function of the same name. It is // used by typeterm.go. func under(t types.Type) types.Type { return t.Underlying() }