Repository: uber-go/dig
Pull request #325 (2022-02-24 11:40:49)

Allow providing types that depend on decorated types from child modules
This fixes the case where a function that's Invoked depends on a type that's decorated. 

Specifically, if a type has a decorator in a child Scope, and there's another exported constructor consuming that type within the same Scope, and a function is invoked outside of that Scope, we still should see the decorated type.

Implementation-wise, this means that the constructors should know which Scope it was originally provided to, and invoke it from that Scope if and only if that Scope has a more "specific" Scope than the Scope it was invoked from. To make this comparison of "specific-ness" of the Scopes cheap, a new field was added to Scope to keep track of its distance from the root Container. A Scope with higher degree is the more "specific" Scope than one with a lower degree. 



codecov[bot]: # [Codecov](https://codecov.io/gh/uber-go/dig/pull/325?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) Report
> Merging [#325](https://codecov.io/gh/uber-go/dig/pull/325?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) (523d94e) into [master](https://codecov.io/gh/uber-go/dig/commit/3a148a28d5453a4527f72b23dc44366ac39ca3db?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) (3a148a2) will **increase** coverage by `0.01%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/uber-go/dig/pull/325/graphs/tree.svg?width=650&height=150&src=pr&token=ARuH7Frwgw&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go)](https://codecov.io/gh/uber-go/dig/pull/325?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go)

```diff
@@            Coverage Diff             @@
##           master     #325      +/-   ##
==========================================
+ Coverage   98.16%   98.18%   +0.01%     
==========================================
  Files          21       21              
  Lines        1419     1429      +10     
==========================================
+ Hits         1393     1403      +10     
  Misses         16       16              
  Partials       10       10              
```


| [Impacted Files](https://codecov.io/gh/uber-go/dig/pull/325?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) | Coverage Δ | |
|---|---|---|
| [constructor.go](https://codecov.io/gh/uber-go/dig/pull/325/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go#diff-Y29uc3RydWN0b3IuZ28=) | `97.05% <100.00%> (+0.18%)` | :arrow_up: |
| [param.go](https://codecov.io/gh/uber-go/dig/pull/325/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go#diff-cGFyYW0uZ28=) | `96.52% <100.00%> (+0.05%)` | :arrow_up: |
| [provide.go](https://codecov.io/gh/uber-go/dig/pull/325/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go#diff-cHJvdmlkZS5nbw==) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/uber-go/dig/pull/325?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/uber-go/dig/pull/325?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go). Last update [3a148a2...523d94e](https://codecov.io/gh/uber-go/dig/pull/325?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go).


Patch:
diff --git a/constructor.go b/constructor.go
index bab711a..86edc67 100644
--- a/constructor.go
+++ b/constructor.go
@@ -57,8 +57,12 @@ type constructorNode struct {
 	// order of this node in each Scopes' graphHolders.
 	orders map[*Scope]int
 
-	// scope this node was originally provided to.
+	// scope this node is part of
 	s *Scope
+
+	// scope this node was originally provided to.
+	// This is different from s if and only if the constructor was Provided with ExportOption.
+	origS *Scope
 }
 
 type constructorOptions struct {
@@ -70,7 +74,7 @@ type constructorOptions struct {
 	Location    *digreflect.Func
 }
 
-func newConstructorNode(ctor interface{}, s *Scope, opts constructorOptions) (*constructorNode, error) {
+func newConstructorNode(ctor interface{}, s *Scope, origS *Scope, opts constructorOptions) (*constructorNode, error) {
 	cval := reflect.ValueOf(ctor)
 	ctype := cval.Type()
 	cptr := cval.Pointer()
@@ -106,6 +110,7 @@ func newConstructorNode(ctor interface{}, s *Scope, opts constructorOptions) (*c
 		resultList: results,
 		orders:     make(map[*Scope]int),
 		s:          s,
+		origS:      origS,
 	}
 	s.newGraphNode(n, n.orders)
 	return n, nil
@@ -117,6 +122,7 @@ func (n *constructorNode) ResultList() resultList     { return n.resultList }
 func (n *constructorNode) ID() dot.CtorID             { return n.id }
 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 }
 
 func (n *constructorNode) String() string {
 	return fmt.Sprintf("deps: %v, ctor: %v", n.paramList, n.ctype)
diff --git a/param.go b/param.go
index a97eacc..0d7698d 100644
--- a/param.go
+++ b/param.go
@@ -299,7 +299,7 @@ func (ps paramSingle) Build(c containerStore, decorating bool) (reflect.Value, e
 	}
 
 	for _, n := range providers {
-		err := n.Call(c)
+		err := n.Call(n.OrigScope())
 		if err == nil {
 			continue
 		}
diff --git a/provide.go b/provide.go
index 012919c..a706637 100644
--- a/provide.go
+++ b/provide.go
@@ -358,6 +358,8 @@ type provider interface {
 	Call(containerStore) error
 
 	CType() reflect.Type
+
+	OrigScope() *Scope
 }
 
 // Provide teaches the container how to build values of one or more types and
@@ -426,6 +428,7 @@ func (s *Scope) Provide(constructor interface{}, opts ...ProvideOption) error {
 func (s *Scope) provide(ctor interface{}, opts provideOptions) (err error) {
 	// If Export option is provided to the constructor, this should be injected to the
 	// root-level Scope (Container) to allow it to propagate to all other Scopes.
+	origScope := s
 	if opts.Exported {
 		s = s.rootScope()
 	}
@@ -448,6 +451,7 @@ func (s *Scope) provide(ctor interface{}, opts provideOptions) (err error) {
 	n, err := newConstructorNode(
 		ctor,
 		s,
+		origScope,
 		constructorOptions{
 			ResultName:  opts.Name,
 			ResultGroup: opts.Group,


Test patch:
diff --git a/constructor_test.go b/constructor_test.go
index 16fc2af..e17a543 100644
--- a/constructor_test.go
+++ b/constructor_test.go
@@ -32,7 +32,8 @@ func TestNewDotCtor(t *testing.T) {
 	type t1 struct{}
 	type t2 struct{}
 
-	n, err := newConstructorNode(func(A t1) t2 { return t2{} }, newScope(), constructorOptions{})
+	s := newScope()
+	n, err := newConstructorNode(func(A t1) t2 { return t2{} }, s, s, constructorOptions{})
 	require.NoError(t, err)
 
 	n.location = &digreflect.Func{
@@ -54,7 +55,8 @@ func TestNodeAlreadyCalled(t *testing.T) {
 	type type1 struct{}
 	f := func() type1 { return type1{} }
 
-	n, err := newConstructorNode(f, newScope(), constructorOptions{})
+	s := newScope()
+	n, err := newConstructorNode(f, s, s, constructorOptions{})
 	require.NoError(t, err, "failed to build node")
 	require.False(t, n.called, "node must not have been called")
 
diff --git a/decorate_test.go b/decorate_test.go
index b60ec99..7ef0f2c 100644
--- a/decorate_test.go
+++ b/decorate_test.go
@@ -391,6 +391,38 @@ func TestDecorateSuccess(t *testing.T) {
 			assert.Nil(t, a.Values)
 		})
 	})
+
+	t.Run("invoke with a transitive dependency on child-decorated exported type", func(t *testing.T) {
+		type Inner struct {
+			Int int
+		}
+
+		type Next struct {
+			MyInner *Inner
+		}
+
+		c := digtest.New(t)
+		child := c.Scope("child")
+
+		child.RequireProvide(func() *Inner {
+			return &Inner{Int: 42}
+		}, dig.Export(true))
+		child.RequireProvide(func(i *Inner) *Next {
+			return &Next{MyInner: i}
+		}, dig.Export(true))
+		child.RequireDecorate(func() *Inner {
+			return &Inner{Int: 5678}
+		})
+		c.RequireInvoke(func(n *Next) {
+			assert.Equal(t, 5678, n.MyInner.Int)
+		})
+		child.RequireInvoke(func(n *Next) {
+			assert.Equal(t, 5678, n.MyInner.Int)
+		})
+		c.RequireInvoke(func(i *Inner) {
+			assert.Equal(t, 42, i.Int)
+		})
+	})
 }
 
 func TestDecorateFailure(t *testing.T) {
diff --git a/scope_test.go b/scope_test.go
index cd7ec10..3710f75 100644
--- a/scope_test.go
+++ b/scope_test.go
@@ -371,9 +371,9 @@ func TestScopeValueGroups(t *testing.T) {
 		})
 
 		child := root.Scope("child")
-		child.RequireProvide(func() string { return "a" }, dig.Group("foo"))
-		child.RequireProvide(func() string { return "b" }, dig.Group("foo"))
-		child.RequireProvide(func() string { return "c" }, dig.Group("foo"))
+		child.RequireProvide(func() string { return "a" }, dig.Group("foo"), dig.Export(true))
+		child.RequireProvide(func() string { return "b" }, dig.Group("foo"), dig.Export(true))
+		child.RequireProvide(func() string { return "c" }, dig.Group("foo"), dig.Export(true))
 
 		// Invocation in child should see values provided to the child,
 		// even though the constructor we're invoking is provided in