diff --git a/constructor.go b/constructor.go index 208659d9..034c41c2 100644 --- a/constructor.go +++ b/constructor.go @@ -129,6 +129,11 @@ func (n *constructorNode) CType() reflect.Type { return n.ctype } func (n *constructorNode) Order(s *Scope) int { return n.orders[s] } func (n *constructorNode) OrigScope() *Scope { return n.origS } +// CopyOrder copies the order for the given parent scope to the given child scope. +func (n *constructorNode) CopyOrder(parent, child *Scope) { + n.orders[child] = n.orders[parent] +} + func (n *constructorNode) String() string { return fmt.Sprintf("deps: %v, ctor: %v", n.paramList, n.ctype) } diff --git a/scope.go b/scope.go index 216cf18a..dc0c7ac5 100644 --- a/scope.go +++ b/scope.go @@ -121,7 +121,12 @@ func (s *Scope) Scope(name string, opts ...ScopeOption) *Scope { child.recoverFromPanics = s.recoverFromPanics // child copies the parent's graph nodes. - child.gh.nodes = append(child.gh.nodes, s.gh.nodes...) + for _, node := range s.gh.nodes { + child.gh.nodes = append(child.gh.nodes, node) + if ctrNode, ok := node.Wrapped.(*constructorNode); ok { + ctrNode.CopyOrder(s, child) + } + } for _, opt := range opts { opt.noScopeOption() diff --git a/scope_test.go b/scope_test.go index 433f036e..ff5db69d 100644 --- a/scope_test.go +++ b/scope_test.go @@ -397,3 +397,20 @@ func TestScopeValueGroups(t *testing.T) { child.RequireInvoke(func(T1) {}) }) } + +// This tests that a child scope correctly copies its parent's graph, +// including information about the order of each node. +// Otherwise, during cycle detection, constructor nodes might +// return 0 as the order for all functions in the root scope, +// causing cycle detection to detect cycles that don't exist. +func TestFalsePositiveScopeCycleDetection(t *testing.T) { + t.Run("single provide", func(t *testing.T) { + root := digtest.New(t) + root.RequireProvide(func(val string) int { return 0 }) + root.RequireProvide(func() string { return "sample" }) + child := root.Scope("child") + // Cycle detection would error here because previous two provides + // would both have order 0 for child scope. + child.RequireProvide(func() float32 { return 0 }) + }) +}