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