// Generally, cycles may occur across multiple type parameters and inferred types
        // (for instance, consider [P interface{ *Q }, Q interface{ func(P) }]).
        // We eliminate cycles by walking the graphs for all type parameters. If a cycle
-       // through a type parameter is detected, cycleFinder nils out the respective type
-       // which kills the cycle; this also means that the respective type could not be
-       // inferred.
+       // through a type parameter is detected, killCycles nils out the respective type
+       // (in the inferred list) which kills the cycle, and marks the corresponding type
+       // parameter as not inferred.
        //
        // TODO(gri) If useful, we could report the respective cycle as an error. We don't
        //           do this now because type inference will fail anyway, and furthermore,
        //           constraints with cycles of this kind cannot currently be satisfied by
        //           any user-supplied type. But should that change, reporting an error
        //           would be wrong.
-       w := cycleFinder{tparams, inferred, make(map[Type]bool)}
-       for _, t := range tparams {
-               w.typ(t) // t != nil
-       }
+       killCycles(tparams, inferred)
 
        // dirty tracks the indices of all types that may still contain type parameters.
        // We know that nil type entries and entries corresponding to provided (non-nil)
        return nil, false
 }
 
+// killCycles walks through the given type parameters and looks for cycles
+// created by type parameters whose inferred types refer back to that type
+// parameter, either directly or indirectly. If such a cycle is detected,
+// it is killed by setting the corresponding inferred type to nil.
+//
+// TODO(gri) Determine if we can simply abort inference as soon as we have
+// found a single cycle.
+func killCycles(tparams []*TypeParam, inferred []Type) {
+       w := cycleFinder{tparams, inferred, make(map[Type]bool)}
+       for _, t := range tparams {
+               w.typ(t) // t != nil
+       }
+}
+
 type cycleFinder struct {
        tparams []*TypeParam
        types   []Type
 func (w *cycleFinder) typ(typ Type) {
        if w.seen[typ] {
                // We have seen typ before. If it is one of the type parameters
-               // in tparams, iterative substitution will lead to infinite expansion.
+               // in w.tparams, iterative substitution will lead to infinite expansion.
                // Nil out the corresponding type which effectively kills the cycle.
                if tpar, _ := typ.(*TypeParam); tpar != nil {
                        if i := tparamIndex(w.tparams, tpar); i >= 0 {
 
        // Generally, cycles may occur across multiple type parameters and inferred types
        // (for instance, consider [P interface{ *Q }, Q interface{ func(P) }]).
        // We eliminate cycles by walking the graphs for all type parameters. If a cycle
-       // through a type parameter is detected, cycleFinder nils out the respective type
-       // which kills the cycle; this also means that the respective type could not be
-       // inferred.
+       // through a type parameter is detected, killCycles nils out the respective type
+       // (in the inferred list) which kills the cycle, and marks the corresponding type
+       // parameter as not inferred.
        //
        // TODO(gri) If useful, we could report the respective cycle as an error. We don't
        //           do this now because type inference will fail anyway, and furthermore,
        //           constraints with cycles of this kind cannot currently be satisfied by
        //           any user-supplied type. But should that change, reporting an error
        //           would be wrong.
-       w := cycleFinder{tparams, inferred, make(map[Type]bool)}
-       for _, t := range tparams {
-               w.typ(t) // t != nil
-       }
+       killCycles(tparams, inferred)
 
        // dirty tracks the indices of all types that may still contain type parameters.
        // We know that nil type entries and entries corresponding to provided (non-nil)
        return nil, false
 }
 
+// killCycles walks through the given type parameters and looks for cycles
+// created by type parameters whose inferred types refer back to that type
+// parameter, either directly or indirectly. If such a cycle is detected,
+// it is killed by setting the corresponding inferred type to nil.
+//
+// TODO(gri) Determine if we can simply abort inference as soon as we have
+// found a single cycle.
+func killCycles(tparams []*TypeParam, inferred []Type) {
+       w := cycleFinder{tparams, inferred, make(map[Type]bool)}
+       for _, t := range tparams {
+               w.typ(t) // t != nil
+       }
+}
+
 type cycleFinder struct {
        tparams []*TypeParam
        types   []Type
 func (w *cycleFinder) typ(typ Type) {
        if w.seen[typ] {
                // We have seen typ before. If it is one of the type parameters
-               // in tparams, iterative substitution will lead to infinite expansion.
+               // in w.tparams, iterative substitution will lead to infinite expansion.
                // Nil out the corresponding type which effectively kills the cycle.
                if tpar, _ := typ.(*TypeParam); tpar != nil {
                        if i := tparamIndex(w.tparams, tpar); i >= 0 {