wire: don't fail at first error (google/go-cloud#204)
This changes both Generate and Load to try to advance as far as possible while collecting errors. A small helper type, errorCollector, is used to make control flow easier to read. Fixes google/go-cloud#5
This commit is contained in:
@@ -80,10 +80,11 @@ type call struct {
|
||||
// solve finds the sequence of calls required to produce an output type
|
||||
// with an optional set of provided inputs.
|
||||
func solve(fset *token.FileSet, out types.Type, given []types.Type, set *ProviderSet) ([]call, []error) {
|
||||
ec := new(errorCollector)
|
||||
for i, g := range given {
|
||||
for _, h := range given[:i] {
|
||||
if types.Identical(g, h) {
|
||||
return nil, []error{fmt.Errorf("multiple inputs of the same type %s", types.TypeString(g, nil))}
|
||||
ec.add(fmt.Errorf("multiple inputs of the same type %s", types.TypeString(g, nil)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,27 +96,35 @@ func solve(fset *token.FileSet, out types.Type, given []types.Type, set *Provide
|
||||
if pv := set.For(g); !pv.IsNil() {
|
||||
switch {
|
||||
case pv.IsProvider():
|
||||
return nil, []error{fmt.Errorf("input of %s conflicts with provider %s at %s",
|
||||
types.TypeString(g, nil), pv.Provider().Name, fset.Position(pv.Provider().Pos))}
|
||||
ec.add(fmt.Errorf("input of %s conflicts with provider %s at %s",
|
||||
types.TypeString(g, nil), pv.Provider().Name, fset.Position(pv.Provider().Pos)))
|
||||
case pv.IsValue():
|
||||
return nil, []error{fmt.Errorf("input of %s conflicts with value at %s",
|
||||
types.TypeString(g, nil), fset.Position(pv.Value().Pos))}
|
||||
ec.add(fmt.Errorf("input of %s conflicts with value at %s",
|
||||
types.TypeString(g, nil), fset.Position(pv.Value().Pos)))
|
||||
default:
|
||||
panic("unknown return value from ProviderSet.For")
|
||||
}
|
||||
} else {
|
||||
index.Set(g, i)
|
||||
}
|
||||
index.Set(g, i)
|
||||
}
|
||||
|
||||
if len(ec.errors) > 0 {
|
||||
return nil, ec.errors
|
||||
}
|
||||
|
||||
// Topological sort of the directed graph defined by the providers
|
||||
// using a depth-first search using a stack. Provider set graphs are
|
||||
// guaranteed to be acyclic.
|
||||
// guaranteed to be acyclic. An index value of errAbort indicates that
|
||||
// the type was visited, but failed due to an error added to ec.
|
||||
errAbort := errors.New("failed to visit")
|
||||
var calls []call
|
||||
type frame struct {
|
||||
t types.Type
|
||||
from types.Type
|
||||
}
|
||||
stk := []frame{{t: out}}
|
||||
dfs:
|
||||
for len(stk) > 0 {
|
||||
curr := stk[len(stk)-1]
|
||||
stk = stk[:len(stk)-1]
|
||||
@@ -126,10 +135,14 @@ func solve(fset *token.FileSet, out types.Type, given []types.Type, set *Provide
|
||||
switch pv := set.For(curr.t); {
|
||||
case pv.IsNil():
|
||||
if curr.from == nil {
|
||||
return nil, []error{fmt.Errorf("no provider found for %s (output of injector)", types.TypeString(curr.t, nil))}
|
||||
ec.add(fmt.Errorf("no provider found for %s (output of injector)", types.TypeString(curr.t, nil)))
|
||||
index.Set(curr.t, errAbort)
|
||||
continue
|
||||
}
|
||||
// TODO(light): Give name of provider.
|
||||
return nil, []error{fmt.Errorf("no provider found for %s (required by provider of %s)", types.TypeString(curr.t, nil), types.TypeString(curr.from, nil))}
|
||||
ec.add(fmt.Errorf("no provider found for %s (required by provider of %s)", types.TypeString(curr.t, nil), types.TypeString(curr.from, nil)))
|
||||
index.Set(curr.t, errAbort)
|
||||
continue
|
||||
case pv.IsProvider():
|
||||
p := pv.Provider()
|
||||
if !types.Identical(p.Out, curr.t) {
|
||||
@@ -164,7 +177,12 @@ func solve(fset *token.FileSet, out types.Type, given []types.Type, set *Provide
|
||||
ins := make([]types.Type, len(p.Args))
|
||||
for i := range p.Args {
|
||||
ins[i] = p.Args[i].Type
|
||||
args[i] = index.At(p.Args[i].Type).(int)
|
||||
v := index.At(p.Args[i].Type)
|
||||
if v == errAbort {
|
||||
index.Set(curr.t, errAbort)
|
||||
continue dfs
|
||||
}
|
||||
args[i] = v.(int)
|
||||
}
|
||||
index.Set(curr.t, len(given)+len(calls))
|
||||
kind := funcProviderCall
|
||||
@@ -205,6 +223,9 @@ func solve(fset *token.FileSet, out types.Type, given []types.Type, set *Provide
|
||||
panic("unknown return value from ProviderSet.For")
|
||||
}
|
||||
}
|
||||
if len(ec.errors) > 0 {
|
||||
return nil, ec.errors
|
||||
}
|
||||
return calls, nil
|
||||
}
|
||||
|
||||
@@ -217,47 +238,62 @@ func buildProviderMap(fset *token.FileSet, hasher typeutil.Hasher, set *Provider
|
||||
setMap.SetHasher(hasher)
|
||||
|
||||
// Process imports first, verifying that there are no conflicts between sets.
|
||||
ec := new(errorCollector)
|
||||
for _, imp := range set.Imports {
|
||||
for _, k := range imp.providerMap.Keys() {
|
||||
if providerMap.At(k) != nil {
|
||||
return nil, []error{bindingConflictError(fset, imp.Pos, k, setMap.At(k).(*ProviderSet))}
|
||||
ec.add(bindingConflictError(fset, imp.Pos, k, setMap.At(k).(*ProviderSet)))
|
||||
continue
|
||||
}
|
||||
providerMap.Set(k, imp.providerMap.At(k))
|
||||
setMap.Set(k, imp)
|
||||
}
|
||||
}
|
||||
if len(ec.errors) > 0 {
|
||||
return nil, ec.errors
|
||||
}
|
||||
|
||||
// Process non-binding providers in new set.
|
||||
for _, p := range set.Providers {
|
||||
if providerMap.At(p.Out) != nil {
|
||||
return nil, []error{bindingConflictError(fset, p.Pos, p.Out, setMap.At(p.Out).(*ProviderSet))}
|
||||
ec.add(bindingConflictError(fset, p.Pos, p.Out, setMap.At(p.Out).(*ProviderSet)))
|
||||
continue
|
||||
}
|
||||
providerMap.Set(p.Out, p)
|
||||
setMap.Set(p.Out, set)
|
||||
}
|
||||
for _, v := range set.Values {
|
||||
if providerMap.At(v.Out) != nil {
|
||||
return nil, []error{bindingConflictError(fset, v.Pos, v.Out, setMap.At(v.Out).(*ProviderSet))}
|
||||
ec.add(bindingConflictError(fset, v.Pos, v.Out, setMap.At(v.Out).(*ProviderSet)))
|
||||
continue
|
||||
}
|
||||
providerMap.Set(v.Out, v)
|
||||
setMap.Set(v.Out, set)
|
||||
}
|
||||
if len(ec.errors) > 0 {
|
||||
return nil, ec.errors
|
||||
}
|
||||
|
||||
// Process bindings in set. Must happen after the other providers to
|
||||
// ensure the concrete type is being provided.
|
||||
for _, b := range set.Bindings {
|
||||
if providerMap.At(b.Iface) != nil {
|
||||
return nil, []error{bindingConflictError(fset, b.Pos, b.Iface, setMap.At(b.Iface).(*ProviderSet))}
|
||||
ec.add(bindingConflictError(fset, b.Pos, b.Iface, setMap.At(b.Iface).(*ProviderSet)))
|
||||
continue
|
||||
}
|
||||
concrete := providerMap.At(b.Provided)
|
||||
if concrete == nil {
|
||||
pos := fset.Position(b.Pos)
|
||||
typ := types.TypeString(b.Provided, nil)
|
||||
return nil, []error{fmt.Errorf("%v: no binding for %s", pos, typ)}
|
||||
ec.add(notePosition(pos, fmt.Errorf("no binding for %s", typ)))
|
||||
continue
|
||||
}
|
||||
providerMap.Set(b.Iface, concrete)
|
||||
setMap.Set(b.Iface, set)
|
||||
}
|
||||
if len(ec.errors) > 0 {
|
||||
return nil, ec.errors
|
||||
}
|
||||
return providerMap, nil
|
||||
}
|
||||
|
||||
@@ -269,6 +305,7 @@ func verifyAcyclic(providerMap *typeutil.Map, hasher typeutil.Hasher) []error {
|
||||
// duplicating work.
|
||||
visited := new(typeutil.Map) // to bool
|
||||
visited.SetHasher(hasher)
|
||||
ec := new(errorCollector)
|
||||
for _, root := range providerMap.Keys() {
|
||||
// Depth-first search using a stack of trails through the provider map.
|
||||
stk := [][]types.Type{{root}}
|
||||
@@ -288,6 +325,7 @@ func verifyAcyclic(providerMap *typeutil.Map, hasher typeutil.Hasher) []error {
|
||||
case *Provider:
|
||||
for _, arg := range x.Args {
|
||||
a := arg.Type
|
||||
hasCycle := false
|
||||
for i, b := range curr {
|
||||
if types.Identical(a, b) {
|
||||
sb := new(strings.Builder)
|
||||
@@ -297,30 +335,35 @@ func verifyAcyclic(providerMap *typeutil.Map, hasher typeutil.Hasher) []error {
|
||||
fmt.Fprintf(sb, "%s (%s.%s) ->\n", types.TypeString(curr[j], nil), p.ImportPath, p.Name)
|
||||
}
|
||||
fmt.Fprintf(sb, "%s\n", types.TypeString(a, nil))
|
||||
return []error{errors.New(sb.String())}
|
||||
ec.add(errors.New(sb.String()))
|
||||
hasCycle = true
|
||||
break
|
||||
}
|
||||
}
|
||||
next := append(append([]types.Type(nil), curr...), a)
|
||||
stk = append(stk, next)
|
||||
if !hasCycle {
|
||||
next := append(append([]types.Type(nil), curr...), a)
|
||||
stk = append(stk, next)
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic("invalid provider map value")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return ec.errors
|
||||
}
|
||||
|
||||
// bindingConflictError creates a new error describing multiple bindings
|
||||
// for the same output type.
|
||||
func bindingConflictError(fset *token.FileSet, pos token.Pos, typ types.Type, prevSet *ProviderSet) error {
|
||||
position := fset.Position(pos)
|
||||
typString := types.TypeString(typ, nil)
|
||||
var err error
|
||||
if prevSet.Name == "" {
|
||||
prevPosition := fset.Position(prevSet.Pos)
|
||||
return fmt.Errorf("%v: multiple bindings for %s (previous binding at %v)",
|
||||
position, typString, prevPosition)
|
||||
err = fmt.Errorf("multiple bindings for %s (previous binding at %v)",
|
||||
typString, fset.Position(prevSet.Pos))
|
||||
} else {
|
||||
err = fmt.Errorf("multiple bindings for %s (previous binding in %q.%s)",
|
||||
typString, prevSet.PkgPath, prevSet.Name)
|
||||
}
|
||||
return fmt.Errorf("%v: multiple bindings for %s (previous binding in %q.%s)",
|
||||
position, typString, prevSet.PkgPath, prevSet.Name)
|
||||
return notePosition(fset.Position(pos), err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user