Repository: uber-go/fx
Pull request #700 (2020-03-31 18:42:39)

Add support for flattened value groups
Update to `go.uber.org/dig@v1.9.0` for https://github.com/uber-go/dig/pull/264 to resolve https://github.com/uber-go/fx/issues/693.

codecov[bot]: # [Codecov](https://codecov.io/gh/uber-go/fx/pull/700?src=pr&el=h1) Report
> Merging [#700](https://codecov.io/gh/uber-go/fx/pull/700?src=pr&el=desc) into [master](https://codecov.io/gh/uber-go/fx/commit/3f21605c56509d73941437cd4d10c622eada29bb?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/uber-go/fx/pull/700/graphs/tree.svg?width=650&token=PYY9PSfaC6&height=150&src=pr)](https://codecov.io/gh/uber-go/fx/pull/700?src=pr&el=tree)

```diff
@@          Coverage Diff           @@
##           master    #700   +/-   ##
======================================
  Coverage    98.1%   98.1%           
======================================
  Files          15      15           
  Lines         528     528           
======================================
  Hits          518     518           
  Misses          8       8           
  Partials        2       2
```


| [Impacted Files](https://codecov.io/gh/uber-go/fx/pull/700?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [annotated.go](https://codecov.io/gh/uber-go/fx/pull/700/diff?src=pr&el=tree#diff-YW5ub3RhdGVkLmdv) | `100% <ø> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/uber-go/fx/pull/700?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/uber-go/fx/pull/700?src=pr&el=footer). Last update [3f21605...9144152](https://codecov.io/gh/uber-go/fx/pull/700?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b05005..882d7ab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
 
 ## [Unreleased]
-- Back to development.
+### Added
+- Value groups can use the `flatten` option to indicate values in a slice should
+  be provided individually rather than providing the slice itself. See package
+  documentation for details.
 
 ## [1.10.0] - 2019-11-20
 ### Added
diff --git a/annotated.go b/annotated.go
index c0c4775..b0cb29f 100644
--- a/annotated.go
+++ b/annotated.go
@@ -64,6 +64,10 @@ type Annotated struct {
 	// by the constructor. For more information on value groups, see the package documentation.
 	//
 	// A group option may not be provided if a name option is provided.
+	//
+	// Similar to group tags, the group name may be followed by a `,flatten`
+	// option to indicate that each element in the slice returned by the
+	// constructor should be injected into the value group individually.
 	Group string
 
 	// Target is the constructor being annotated with fx.Annotated.
diff --git a/go.mod b/go.mod
index 31fdede..1eb289d 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.13
 
 require (
 	github.com/stretchr/testify v1.4.0
-	go.uber.org/dig v1.8.0
+	go.uber.org/dig v1.9.0
 	go.uber.org/goleak v0.10.0
 	go.uber.org/multierr v1.4.0
 	golang.org/x/lint v0.0.0-20190930215403-16217165b5de
diff --git a/go.sum b/go.sum
index 281f903..f304185 100644
--- a/go.sum
+++ b/go.sum
@@ -20,8 +20,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
-go.uber.org/dig v1.8.0 h1:1rR6hnL/bu1EVcjnRDN5kx1vbIjEJDTGhSQ2B3ddpcI=
-go.uber.org/dig v1.8.0/go.mod h1:X34SnWGr8Fyla9zQNO2GSO2D+TIuqB14OS8JhYocIyw=
+go.uber.org/dig v1.9.0 h1:pJTDXKEhRqBI8W7rU7kwT5EgyRZuSMVSFcZolOvKK9U=
+go.uber.org/dig v1.9.0/go.mod h1:X34SnWGr8Fyla9zQNO2GSO2D+TIuqB14OS8JhYocIyw=
 go.uber.org/goleak v0.10.0 h1:G3eWbSNIskeRqtsN/1uI5B+eP73y3JUuBsv9AZjehb4=
 go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI=
 go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E=
diff --git a/inout.go b/inout.go
index c515586..72f86bb 100644
--- a/inout.go
+++ b/inout.go
@@ -251,4 +251,15 @@ type In struct{ dig.In }
 // value groups require parameter and result structs to use fields with
 // different types: if a group of constructors each returns type T, parameter
 // structs consuming the group must use a field of type []T.
+//
+// To provide multiple values for a group from a result struct, produce a
+// slice and use the `,flatten` option on the group tag. This indicates that
+// each element in the slice should be injected into the group individually.
+//
+//   type IntResult struct {
+//     fx.Out
+//
+//     Handler []int `group:"server"`         // Consume as [][]int
+//     Handler []int `group:"server,flatten"` // Consume as []int
+//   }
 type Out struct{ dig.Out }


Test patch:
diff --git a/app_test.go b/app_test.go
index 8682785..2092c42 100644
--- a/app_test.go
+++ b/app_test.go
@@ -141,6 +141,26 @@ func TestNewApp(t *testing.T) {
 		require.NoError(t, app.Err())
 	})
 
+	t.Run("ProvidesWithAnnotateFlattened", func(t *testing.T) {
+		app := fxtest.New(t,
+			Provide(Annotated{
+				Target: func() []int { return []int{1} },
+				Group:  "foo,flatten",
+			}),
+			Invoke(
+				func(b struct {
+					In
+					Foos []int `group:"foo"`
+				}) {
+					assert.Len(t, b.Foos, 1)
+				},
+			),
+		)
+
+		defer app.RequireStart().RequireStop()
+		require.NoError(t, app.Err())
+	})
+
 	t.Run("ProvidesWithEmptyAnnotate", func(t *testing.T) {
 		type A struct{}
 
@@ -250,7 +270,7 @@ func TestInvokes(t *testing.T) {
 		)
 		err := app.Err()
 		require.Error(t, err)
-		assert.Contains(t, err.Error(), "fx_test.A is not in the container")
+		assert.Contains(t, err.Error(), "missing type: fx_test.A")
 	})
 
 	t.Run("ErrorHooksAreCalled", func(t *testing.T) {
diff --git a/populate_test.go b/populate_test.go
index 4ab90c7..8f2f880 100644
--- a/populate_test.go
+++ b/populate_test.go
@@ -208,7 +208,7 @@ func TestPopulateErrors(t *testing.T) {
 		{
 			msg:     "container pointer without fx.In",
 			opt:     Populate(&containerNoIn{}),
-			wantErr: "is not in the container",
+			wantErr: "missing type: fx_test.containerNoIn",
 		},
 		{
 			msg:     "function",
@@ -218,7 +218,7 @@ func TestPopulateErrors(t *testing.T) {
 		{
 			msg:     "function pointer",
 			opt:     Populate(&fn),
-			wantErr: "is not in the container",
+			wantErr: "missing type: func()",
 		},
 		{
 			msg:     "invalid last argument",