Robert Findley | 058d147 | 2022-02-04 18:34:11 -0500 | [diff] [blame] | 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 | const debug = false |
| 16 | |
| 17 | // ErrEmptyTypeSet is returned if a type set computation results in a type set |
| 18 | // with no types. |
| 19 | var 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 Findley | bbda1ea | 2022-02-27 21:41:42 -0500 | [diff] [blame] | 24 | // 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 Findley | 058d147 | 2022-02-04 18:34:11 -0500 | [diff] [blame] | 28 | // |
| 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 Findley | bbda1ea | 2022-02-27 21:41:42 -0500 | [diff] [blame] | 31 | // 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 Findley | 058d147 | 2022-02-04 18:34:11 -0500 | [diff] [blame] | 33 | // 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 Cox | bcd2187 | 2022-04-11 13:08:42 -0400 | [diff] [blame] | 39 | // type A interface{ ~string|~[]byte } |
Robert Findley | 058d147 | 2022-02-04 18:34:11 -0500 | [diff] [blame] | 40 | // |
Russ Cox | bcd2187 | 2022-04-11 13:08:42 -0400 | [diff] [blame] | 41 | // type B interface{ int|string } |
Robert Findley | 058d147 | 2022-02-04 18:34:11 -0500 | [diff] [blame] | 42 | // |
Russ Cox | bcd2187 | 2022-04-11 13:08:42 -0400 | [diff] [blame] | 43 | // type C interface { ~string|~int } |
Robert Findley | 058d147 | 2022-02-04 18:34:11 -0500 | [diff] [blame] | 44 | // |
Russ Cox | bcd2187 | 2022-04-11 13:08:42 -0400 | [diff] [blame] | 45 | // type T[P interface{ A|B; C }] int |
Robert Findley | 058d147 | 2022-02-04 18:34:11 -0500 | [diff] [blame] | 46 | // |
| 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. |
| 66 | func 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. |
| 100 | type termSet struct { |
| 101 | complete bool |
| 102 | terms termlist |
| 103 | } |
| 104 | |
| 105 | func indentf(depth int, format string, args ...interface{}) { |
| 106 | fmt.Fprintf(os.Stderr, strings.Repeat(".", depth)+format+"\n", args...) |
| 107 | } |
| 108 | |
| 109 | func 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. |
| 198 | func under(t types.Type) types.Type { |
| 199 | return t.Underlying() |
| 200 | } |