|
| 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 types |
| 6 | + |
| 7 | +import ( |
| 8 | + "go/token" |
| 9 | +) |
| 10 | + |
| 11 | +// This file implements a check to validate that a Go package doesn't |
| 12 | +// have unbounded recursive instantiation, which is not compatible |
| 13 | +// with compilers using static instantiation (such as |
| 14 | +// monomorphization). |
| 15 | +// |
| 16 | +// It implements a sort of "type flow" analysis by detecting which |
| 17 | +// type parameters are instantiated with other type parameters (or |
| 18 | +// types derived thereof). A package cannot be statically instantiated |
| 19 | +// if the graph has any cycles involving at least one derived type. |
| 20 | +// |
| 21 | +// Concretely, we construct a directed, weighted graph. Vertices are |
| 22 | +// used to represent type parameters as well as some defined |
| 23 | +// types. Edges are used to represent how types depend on each other: |
| 24 | +// |
| 25 | +// * Everywhere a type-parameterized function or type is instantiated, |
| 26 | +// we add edges to each type parameter from the vertices (if any) |
| 27 | +// representing each type parameter or defined type referenced by |
| 28 | +// the type argument. If the type argument is just the referenced |
| 29 | +// type itself, then the edge has weight 0, otherwise 1. |
| 30 | +// |
| 31 | +// * For every defined type declared within a type-parameterized |
| 32 | +// function or method, we add an edge of weight 1 to the defined |
| 33 | +// type from each ambient type parameter. |
| 34 | +// |
| 35 | +// For example, given: |
| 36 | +// |
| 37 | +// func f[A, B any]() { |
| 38 | +// type T int |
| 39 | +// f[T, map[A]B]() |
| 40 | +// } |
| 41 | +// |
| 42 | +// we construct vertices representing types A, B, and T. Because of |
| 43 | +// declaration "type T int", we construct edges T<-A and T<-B with |
| 44 | +// weight 1; and because of instantiation "f[T, map[A]B]" we construct |
| 45 | +// edges A<-T with weight 0, and B<-A and B<-B with weight 1. |
| 46 | +// |
| 47 | +// Finally, we look for any positive-weight cycles. Zero-weight cycles |
| 48 | +// are allowed because static instantiation will reach a fixed point. |
| 49 | + |
| 50 | +type monoGraph struct { |
| 51 | + vertices []monoVertex |
| 52 | + edges []monoEdge |
| 53 | + |
| 54 | + // canon maps method receiver type parameters to their respective |
| 55 | + // receiver type's type parameters. |
| 56 | + canon map[*TypeParam]*TypeParam |
| 57 | + |
| 58 | + // nameIdx maps a defined type or (canonical) type parameter to its |
| 59 | + // vertex index. |
| 60 | + nameIdx map[*TypeName]int |
| 61 | +} |
| 62 | + |
| 63 | +type monoVertex struct { |
| 64 | + weight int // weight of heaviest known path to this vertex |
| 65 | + pre int // previous edge (if any) in the above path |
| 66 | + len int // length of the above path |
| 67 | + |
| 68 | + // obj is the defined type or type parameter represented by this |
| 69 | + // vertex. |
| 70 | + obj *TypeName |
| 71 | +} |
| 72 | + |
| 73 | +type monoEdge struct { |
| 74 | + dst int |
| 75 | + src int |
| 76 | + weight int |
| 77 | + |
| 78 | + // report emits an error describing why this edge exists. |
| 79 | + // |
| 80 | + // TODO(mdempsky): Avoid requiring a function closure for each edge. |
| 81 | + report func(check *Checker) |
| 82 | +} |
| 83 | + |
| 84 | +func (check *Checker) monomorph() { |
| 85 | + // We detect unbounded instantiation cycles using a variant of |
| 86 | + // Bellman-Ford's algorithm. Namely, instead of always running |V| |
| 87 | + // iterations, we run until we either reach a fixed point or we've |
| 88 | + // found a path of length |V|. This allows us to terminate earlier |
| 89 | + // when there are no cycles, which should be the common case. |
| 90 | + |
| 91 | + again := true |
| 92 | + for again { |
| 93 | + again = false |
| 94 | + |
| 95 | + for i, edge := range check.mono.edges { |
| 96 | + src := &check.mono.vertices[edge.src] |
| 97 | + dst := &check.mono.vertices[edge.dst] |
| 98 | + |
| 99 | + // N.B., we're looking for the greatest weight paths, unlike |
| 100 | + // typical Bellman-Ford. |
| 101 | + w := src.weight + edge.weight |
| 102 | + if w <= dst.weight { |
| 103 | + continue |
| 104 | + } |
| 105 | + |
| 106 | + dst.pre = i |
| 107 | + dst.len = src.len + 1 |
| 108 | + if dst.len == len(check.mono.vertices) { |
| 109 | + check.reportInstanceLoop(edge.dst) |
| 110 | + return |
| 111 | + } |
| 112 | + |
| 113 | + dst.weight = w |
| 114 | + again = true |
| 115 | + } |
| 116 | + } |
| 117 | +} |
| 118 | + |
| 119 | +func (check *Checker) reportInstanceLoop(v int) { |
| 120 | + var stack []int |
| 121 | + seen := make([]bool, len(check.mono.vertices)) |
| 122 | + |
| 123 | + // We have a path that contains a cycle and ends at v, but v may |
| 124 | + // only be reachable from the cycle, not on the cycle itself. We |
| 125 | + // start by walking backwards along the path until we find a vertex |
| 126 | + // that appears twice. |
| 127 | + for !seen[v] { |
| 128 | + stack = append(stack, v) |
| 129 | + seen[v] = true |
| 130 | + v = check.mono.edges[check.mono.vertices[v].pre].src |
| 131 | + } |
| 132 | + |
| 133 | + // Trim any vertices we visited before visiting v the first |
| 134 | + // time. Since v is the first vertex we found within the cycle, any |
| 135 | + // vertices we visited earlier cannot be part of the cycle. |
| 136 | + for stack[0] != v { |
| 137 | + stack = stack[1:] |
| 138 | + } |
| 139 | + |
| 140 | + // TODO(mdempsky): Pivot stack so we report the cycle from the top? |
| 141 | + |
| 142 | + obj := check.mono.vertices[v].obj |
| 143 | + check.errorf(obj, _InvalidInstanceCycle, "instantiation cycle:") |
| 144 | + |
| 145 | + for _, v := range stack { |
| 146 | + edge := check.mono.edges[check.mono.vertices[v].pre] |
| 147 | + edge.report(check) |
| 148 | + } |
| 149 | +} |
| 150 | + |
| 151 | +// recordCanon records that tpar is the canonical type parameter |
| 152 | +// corresponding to method type parameter mpar. |
| 153 | +func (w *monoGraph) recordCanon(mpar, tpar *TypeParam) { |
| 154 | + if w.canon == nil { |
| 155 | + w.canon = make(map[*TypeParam]*TypeParam) |
| 156 | + } |
| 157 | + w.canon[mpar] = tpar |
| 158 | +} |
| 159 | + |
| 160 | +// recordInstance records that the given type parameters were |
| 161 | +// instantiated with the corresponding type arguments. |
| 162 | +func (w *monoGraph) recordInstance(pkg *Package, pos token.Pos, tparams []*TypeParam, targs []Type, posList []token.Pos) { |
| 163 | + for i, tpar := range tparams { |
| 164 | + pos := pos |
| 165 | + if i < len(posList) { |
| 166 | + pos = posList[i] |
| 167 | + } |
| 168 | + w.assign(pkg, pos, tpar, targs[i]) |
| 169 | + } |
| 170 | +} |
| 171 | + |
| 172 | +// assign records that tpar was instantiated as targ at pos. |
| 173 | +func (w *monoGraph) assign(pkg *Package, pos token.Pos, tpar *TypeParam, targ Type) { |
| 174 | + // Go generics do not have an analog to C++`s template-templates, |
| 175 | + // where a template parameter can itself be an instantiable |
| 176 | + // template. So any instantiation cycles must occur within a single |
| 177 | + // package. Accordingly, we can ignore instantiations of imported |
| 178 | + // type parameters. |
| 179 | + // |
| 180 | + // TODO(mdempsky): Push this check up into recordInstance? All type |
| 181 | + // parameters in a list will appear in the same package. |
| 182 | + if tpar.Obj().Pkg() != pkg { |
| 183 | + return |
| 184 | + } |
| 185 | + |
| 186 | + // flow adds an edge from vertex src representing that typ flows to tpar. |
| 187 | + flow := func(src int, typ Type) { |
| 188 | + weight := 1 |
| 189 | + if typ == targ { |
| 190 | + weight = 0 |
| 191 | + } |
| 192 | + |
| 193 | + w.addEdge(w.typeParamVertex(tpar), src, weight, func(check *Checker) { |
| 194 | + qf := RelativeTo(check.pkg) |
| 195 | + check.errorf(atPos(pos), _InvalidInstanceCycle, "\t%s instantiated as %s", tpar.Obj().Name(), TypeString(targ, qf)) // secondary error, \t indented |
| 196 | + }) |
| 197 | + } |
| 198 | + |
| 199 | + // Recursively walk the type argument to find any defined types or |
| 200 | + // type parameters. |
| 201 | + var do func(typ Type) |
| 202 | + do = func(typ Type) { |
| 203 | + switch typ := typ.(type) { |
| 204 | + default: |
| 205 | + panic("unexpected type") |
| 206 | + |
| 207 | + case *TypeParam: |
| 208 | + assert(typ.Obj().Pkg() == pkg) |
| 209 | + flow(w.typeParamVertex(typ), typ) |
| 210 | + |
| 211 | + case *Named: |
| 212 | + if src := w.localNamedVertex(pkg, typ.Origin()); src >= 0 { |
| 213 | + flow(src, typ) |
| 214 | + } |
| 215 | + |
| 216 | + targs := typ.TypeArgs() |
| 217 | + for i := 0; i < targs.Len(); i++ { |
| 218 | + do(targs.At(i)) |
| 219 | + } |
| 220 | + |
| 221 | + case *Array: |
| 222 | + do(typ.Elem()) |
| 223 | + case *Basic: |
| 224 | + // ok |
| 225 | + case *Chan: |
| 226 | + do(typ.Elem()) |
| 227 | + case *Map: |
| 228 | + do(typ.Key()) |
| 229 | + do(typ.Elem()) |
| 230 | + case *Pointer: |
| 231 | + do(typ.Elem()) |
| 232 | + case *Slice: |
| 233 | + do(typ.Elem()) |
| 234 | + |
| 235 | + case *Interface: |
| 236 | + for i := 0; i < typ.NumMethods(); i++ { |
| 237 | + do(typ.Method(i).Type()) |
| 238 | + } |
| 239 | + case *Signature: |
| 240 | + tuple := func(tup *Tuple) { |
| 241 | + for i := 0; i < tup.Len(); i++ { |
| 242 | + do(tup.At(i).Type()) |
| 243 | + } |
| 244 | + } |
| 245 | + tuple(typ.Params()) |
| 246 | + tuple(typ.Results()) |
| 247 | + case *Struct: |
| 248 | + for i := 0; i < typ.NumFields(); i++ { |
| 249 | + do(typ.Field(i).Type()) |
| 250 | + } |
| 251 | + } |
| 252 | + } |
| 253 | + do(targ) |
| 254 | +} |
| 255 | + |
| 256 | +// localNamedVertex returns the index of the vertex representing |
| 257 | +// named, or -1 if named doesn't need representation. |
| 258 | +func (w *monoGraph) localNamedVertex(pkg *Package, named *Named) int { |
| 259 | + obj := named.Obj() |
| 260 | + if obj.Pkg() != pkg { |
| 261 | + return -1 // imported type |
| 262 | + } |
| 263 | + |
| 264 | + root := pkg.Scope() |
| 265 | + if obj.Parent() == root { |
| 266 | + return -1 // package scope, no ambient type parameters |
| 267 | + } |
| 268 | + |
| 269 | + if idx, ok := w.nameIdx[obj]; ok { |
| 270 | + return idx |
| 271 | + } |
| 272 | + |
| 273 | + idx := -1 |
| 274 | + |
| 275 | + // Walk the type definition's scope to find any ambient type |
| 276 | + // parameters that it's implicitly parameterized by. |
| 277 | + for scope := obj.Parent(); scope != root; scope = scope.Parent() { |
| 278 | + for _, elem := range scope.elems { |
| 279 | + if elem, ok := elem.(*TypeName); ok && !elem.IsAlias() && elem.Pos() < obj.Pos() { |
| 280 | + if tpar, ok := elem.Type().(*TypeParam); ok { |
| 281 | + if idx < 0 { |
| 282 | + idx = len(w.vertices) |
| 283 | + w.vertices = append(w.vertices, monoVertex{obj: obj}) |
| 284 | + } |
| 285 | + |
| 286 | + w.addEdge(idx, w.typeParamVertex(tpar), 1, func(check *Checker) { |
| 287 | + check.errorf(obj, _InvalidInstanceCycle, "\t%s implicitly parameterized by %s", obj.Name(), elem.Name()) |
| 288 | + }) |
| 289 | + } |
| 290 | + } |
| 291 | + } |
| 292 | + } |
| 293 | + |
| 294 | + if w.nameIdx == nil { |
| 295 | + w.nameIdx = make(map[*TypeName]int) |
| 296 | + } |
| 297 | + w.nameIdx[obj] = idx |
| 298 | + return idx |
| 299 | +} |
| 300 | + |
| 301 | +// typeParamVertex returns the index of the vertex representing tpar. |
| 302 | +func (w *monoGraph) typeParamVertex(tpar *TypeParam) int { |
| 303 | + if x, ok := w.canon[tpar]; ok { |
| 304 | + tpar = x |
| 305 | + } |
| 306 | + |
| 307 | + obj := tpar.Obj() |
| 308 | + |
| 309 | + if idx, ok := w.nameIdx[obj]; ok { |
| 310 | + return idx |
| 311 | + } |
| 312 | + |
| 313 | + if w.nameIdx == nil { |
| 314 | + w.nameIdx = make(map[*TypeName]int) |
| 315 | + } |
| 316 | + |
| 317 | + idx := len(w.vertices) |
| 318 | + w.vertices = append(w.vertices, monoVertex{obj: obj}) |
| 319 | + w.nameIdx[obj] = idx |
| 320 | + return idx |
| 321 | +} |
| 322 | + |
| 323 | +func (w *monoGraph) addEdge(dst, src, weight int, report func(check *Checker)) { |
| 324 | + // TODO(mdempsky): Deduplicate redundant edges? |
| 325 | + w.edges = append(w.edges, monoEdge{ |
| 326 | + dst: dst, |
| 327 | + src: src, |
| 328 | + weight: weight, |
| 329 | + report: report, |
| 330 | + }) |
| 331 | +} |
0 commit comments