Repository: uber-go/fx (go)

Issue:
bug: Decorator is not applied properly
**Describe the bug**
Decorator is not applied properly when it is not required through a "direct" dependency.

**To Reproduce**
See https://github.com/uber-go/fx/pull/939.

**Expected behavior**
The test added in https://github.com/uber-go/fx/pull/939 

Internal Ref: GO-1668


Pull request description:
Fix decorator not being applied to transient dependencies
There is a bug that only manifests when decorating objects in Fx where transient dependency types are not properly decorated. For example, if type A depends on type B, and type B depends on type C which has a decorator, it fails to recognize that.

This only occurs when all constructors and the decorators for all these types are provided at the "root" level fx.App.

This happens because fx injects a fake "root" Scope between the actual root Container (which is where constructors are provided to by default).

Fixed by making fx stop create this fake root Scope and use the dig.Container directly.

Fixes #940.

Patch:
diff --git a/module.go b/module.go
index c75ba79..4a769d4 100644
--- a/module.go
+++ b/module.go
@@ -81,7 +81,7 @@ func (o moduleOption) apply(mod *module) {
 type module struct {
 	parent     *module
 	name       string
-	scope      *dig.Scope
+	scope      scope
 	provides   []provide
 	invokes    []invoke
 	decorators []decorator
@@ -89,15 +89,24 @@ type module struct {
 	app        *App
 }
 
+// scope is a private wrapper interface for dig.Container and dig.Scope.
+// We can consider moving this into Fx using type constraints after Go 1.20
+// is released and 1.17 is deprecated.
+type scope interface {
+	Decorate(f interface{}, opts ...dig.DecorateOption) error
+	Invoke(f interface{}, opts ...dig.InvokeOption) error
+	Provide(f interface{}, opts ...dig.ProvideOption) error
+	Scope(name string, opts ...dig.ScopeOption) *dig.Scope
+	String() string
+}
+
 // builds the Scopes using the App's Container. Note that this happens
 // after applyModules' are called because the App's Container needs to
 // be built for any Scopes to be initialized, and applys' should be called
 // before the Container can get initialized.
 func (m *module) build(app *App, root *dig.Container) {
 	if m.parent == nil {
-		m.scope = root.Scope(m.name)
-		// TODO: Once fx.Decorate is in-place,
-		// use the root container instead of subscope.
+		m.scope = root
 	} else {
 		parentScope := m.parent.scope
 		m.scope = parentScope.Scope(m.name)


Test patch:
diff --git a/decorate_test.go b/decorate_test.go
index 49c0a7d..83c3550 100644
--- a/decorate_test.go
+++ b/decorate_test.go
@@ -313,6 +313,39 @@ func TestDecorateSuccess(t *testing.T) {
 		)
 		defer app.RequireStart().RequireStop()
 	})
+
+	t.Run("decoration must execute when required by a member of group", func(t *testing.T) {
+		type Drinks interface {
+		}
+		type Coffee struct {
+			Type  string
+			Name  string
+			Price int
+		}
+		type PriceService struct {
+			DefaultPrice int
+		}
+		app := fxtest.New(t,
+			fx.Provide(func() *PriceService {
+				return &PriceService{DefaultPrice: 3}
+			}),
+			fx.Decorate(func(service *PriceService) *PriceService {
+				service.DefaultPrice = 10
+				return service
+			}),
+			fx.Provide(fx.Annotate(func(service *PriceService) Drinks {
+				assert.Equal(t, 10, service.DefaultPrice)
+				return &Coffee{Type: "coffee", Name: "Americano", Price: service.DefaultPrice}
+			}, fx.ResultTags(`group:"drinks"`))),
+			fx.Provide(fx.Annotated{Group: "drinks", Target: func() Drinks {
+				return &Coffee{Type: "coffee", Name: "Cold Brew", Price: 4}
+			}}),
+			fx.Invoke(fx.Annotate(func(drinks []Drinks) {
+				assert.Len(t, drinks, 2)
+			}, fx.ParamTags(`group:"drinks"`))),
+		)
+		defer app.RequireStart().RequireStop()
+	})
 }
 
 func TestDecorateFailure(t *testing.T) {