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 }