blob: 84e1e971b67471d3adb8976ab6fe98bb1b9dfbdb [file] [log] [blame]
// 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 types
import (
"go/ast"
"go/token"
)
// This file implements a check to validate that a Go package doesn't
// have unbounded recursive instantiation, which is not compatible
// with compilers using static instantiation (such as
// monomorphization).
//
// It implements a sort of "type flow" analysis by detecting which
// type parameters are instantiated with other type parameters (or
// types derived thereof). A package cannot be statically instantiated
// if the graph has any cycles involving at least one derived type.
//
// Concretely, we construct a directed, weighted graph. Vertices are
// used to represent type parameters as well as some defined
// types. Edges are used to represent how types depend on each other:
//
// * Everywhere a type-parameterized function or type is instantiated,
// we add edges to each type parameter from the vertices (if any)
// representing each type parameter or defined type referenced by
// the type argument. If the type argument is just the referenced
// type itself, then the edge has weight 0, otherwise 1.
//
// * For every defined type declared within a type-parameterized
// function or method, we add an edge of weight 1 to the defined
// type from each ambient type parameter.
//
// For example, given:
//
// func f[A, B any]() {
// type T int
// f[T, map[A]B]()
// }
//
// we construct vertices representing types A, B, and T. Because of
// declaration "type T int", we construct edges T<-A and T<-B with
// weight 1; and because of instantiation "f[T, map[A]B]" we construct
// edges A<-T with weight 0, and B<-A and B<-B with weight 1.
//
// Finally, we look for any positive-weight cycles. Zero-weight cycles
// are allowed because static instantiation will reach a fixed point.
type monoGraph struct {
vertices []monoVertex
edges []monoEdge
// canon maps method receiver type parameters to their respective
// receiver type's type parameters.
canon map[*TypeParam]*TypeParam
// nameIdx maps a defined type or (canonical) type parameter to its
// vertex index.
nameIdx map[*TypeName]int
}
type monoVertex struct {
weight int // weight of heaviest known path to this vertex
pre int // previous edge (if any) in the above path
len int // length of the above path
// obj is the defined type or type parameter represented by this
// vertex.
obj *TypeName
}
type monoEdge struct {
dst, src int
weight int
pos token.Pos
typ Type
}
func (check *Checker) monomorph() {
// We detect unbounded instantiation cycles using a variant of
// Bellman-Ford's algorithm. Namely, instead of always running |V|
// iterations, we run until we either reach a fixed point or we've
// found a path of length |V|. This allows us to terminate earlier
// when there are no cycles, which should be the common case.
again := true
for again {
again = false
for i, edge := range check.mono.edges {
src := &check.mono.vertices[edge.src]
dst := &check.mono.vertices[edge.dst]
// N.B., we're looking for the greatest weight paths, unlike
// typical Bellman-Ford.
w := src.weight + edge.weight
if w <= dst.weight {
continue
}
dst.pre = i
dst.len = src.len + 1
if dst.len == len(check.mono.vertices) {
check.reportInstanceLoop(edge.dst)
return
}
dst.weight = w
again = true
}
}
}
func (check *Checker) reportInstanceLoop(v int) {
var stack []int
seen := make([]bool, len(check.mono.vertices))
// We have a path that contains a cycle and ends at v, but v may
// only be reachable from the cycle, not on the cycle itself. We
// start by walking backwards along the path until we find a vertex
// that appears twice.
for !seen[v] {
stack = append(stack, v)
seen[v] = true
v = check.mono.edges[check.mono.vertices[v].pre].src
}
// Trim any vertices we visited before visiting v the first
// time. Since v is the first vertex we found within the cycle, any
// vertices we visited earlier cannot be part of the cycle.
for stack[0] != v {
stack = stack[1:]
}
// TODO(mdempsky): Pivot stack so we report the cycle from the top?
obj0 := check.mono.vertices[v].obj
check.errorf(obj0, _InvalidInstanceCycle, "instantiation cycle:")
qf := RelativeTo(check.pkg)
for _, v := range stack {
edge := check.mono.edges[check.mono.vertices[v].pre]
obj := check.mono.vertices[edge.dst].obj
switch obj.Type().(type) {
default:
panic("unexpected type")
case *Named:
check.errorf(atPos(edge.pos), _InvalidInstanceCycle, "\t%s implicitly parameterized by %s", obj.Name(), TypeString(edge.typ, qf)) // secondary error, \t indented
case *TypeParam:
check.errorf(atPos(edge.pos), _InvalidInstanceCycle, "\t%s instantiated as %s", obj.Name(), TypeString(edge.typ, qf)) // secondary error, \t indented
}
}
}
// recordCanon records that tpar is the canonical type parameter
// corresponding to method type parameter mpar.
func (w *monoGraph) recordCanon(mpar, tpar *TypeParam) {
if w.canon == nil {
w.canon = make(map[*TypeParam]*TypeParam)
}
w.canon[mpar] = tpar
}
// recordInstance records that the given type parameters were
// instantiated with the corresponding type arguments.
func (w *monoGraph) recordInstance(pkg *Package, pos token.Pos, tparams []*TypeParam, targs []Type, xlist []ast.Expr) {
for i, tpar := range tparams {
pos := pos
if i < len(xlist) {
pos = xlist[i].Pos()
}
w.assign(pkg, pos, tpar, targs[i])
}
}
// assign records that tpar was instantiated as targ at pos.
func (w *monoGraph) assign(pkg *Package, pos token.Pos, tpar *TypeParam, targ Type) {
// Go generics do not have an analog to C++`s template-templates,
// where a template parameter can itself be an instantiable
// template. So any instantiation cycles must occur within a single
// package. Accordingly, we can ignore instantiations of imported
// type parameters.
//
// TODO(mdempsky): Push this check up into recordInstance? All type
// parameters in a list will appear in the same package.
if tpar.Obj().Pkg() != pkg {
return
}
// flow adds an edge from vertex src representing that typ flows to tpar.
flow := func(src int, typ Type) {
weight := 1
if typ == targ {
weight = 0
}
w.addEdge(w.typeParamVertex(tpar), src, weight, pos, targ)
}
// Recursively walk the type argument to find any defined types or
// type parameters.
var do func(typ Type)
do = func(typ Type) {
switch typ := typ.(type) {
default:
panic("unexpected type")
case *TypeParam:
assert(typ.Obj().Pkg() == pkg)
flow(w.typeParamVertex(typ), typ)
case *Named:
if src := w.localNamedVertex(pkg, typ.Origin()); src >= 0 {
flow(src, typ)
}
targs := typ.TypeArgs()
for i := 0; i < targs.Len(); i++ {
do(targs.At(i))
}
case *Array:
do(typ.Elem())
case *Basic:
// ok
case *Chan:
do(typ.Elem())
case *Map:
do(typ.Key())
do(typ.Elem())
case *Pointer:
do(typ.Elem())
case *Slice:
do(typ.Elem())
case *Interface:
for i := 0; i < typ.NumMethods(); i++ {
do(typ.Method(i).Type())
}
case *Signature:
tuple := func(tup *Tuple) {
for i := 0; i < tup.Len(); i++ {
do(tup.At(i).Type())
}
}
tuple(typ.Params())
tuple(typ.Results())
case *Struct:
for i := 0; i < typ.NumFields(); i++ {
do(typ.Field(i).Type())
}
}
}
do(targ)
}
// localNamedVertex returns the index of the vertex representing
// named, or -1 if named doesn't need representation.
func (w *monoGraph) localNamedVertex(pkg *Package, named *Named) int {
obj := named.Obj()
if obj.Pkg() != pkg {
return -1 // imported type
}
root := pkg.Scope()
if obj.Parent() == root {
return -1 // package scope, no ambient type parameters
}
if idx, ok := w.nameIdx[obj]; ok {
return idx
}
idx := -1
// Walk the type definition's scope to find any ambient type
// parameters that it's implicitly parameterized by.
for scope := obj.Parent(); scope != root; scope = scope.Parent() {
for _, elem := range scope.elems {
if elem, ok := elem.(*TypeName); ok && !elem.IsAlias() && elem.Pos() < obj.Pos() {
if tpar, ok := elem.Type().(*TypeParam); ok {
if idx < 0 {
idx = len(w.vertices)
w.vertices = append(w.vertices, monoVertex{obj: obj})
}
w.addEdge(idx, w.typeParamVertex(tpar), 1, obj.Pos(), tpar)
}
}
}
}
if w.nameIdx == nil {
w.nameIdx = make(map[*TypeName]int)
}
w.nameIdx[obj] = idx
return idx
}
// typeParamVertex returns the index of the vertex representing tpar.
func (w *monoGraph) typeParamVertex(tpar *TypeParam) int {
if x, ok := w.canon[tpar]; ok {
tpar = x
}
obj := tpar.Obj()
if idx, ok := w.nameIdx[obj]; ok {
return idx
}
if w.nameIdx == nil {
w.nameIdx = make(map[*TypeName]int)
}
idx := len(w.vertices)
w.vertices = append(w.vertices, monoVertex{obj: obj})
w.nameIdx[obj] = idx
return idx
}
func (w *monoGraph) addEdge(dst, src, weight int, pos token.Pos, typ Type) {
// TODO(mdempsky): Deduplicate redundant edges?
w.edges = append(w.edges, monoEdge{
dst: dst,
src: src,
weight: weight,
pos: pos,
typ: typ,
})
}