Repository: uber-go/dig
Pull request #383 (2023-04-14 19:47:43)

Add support for getting info from Invoke statements
This commit exposes `InvokeInfo` and `FillInvokeInfo` to help users extract the types requested by an Invoke statement.

Example usage -

```
var info InvokeInfo
c.Invoke(func(typ1 type1, typ2 type2){}, FillInvokeInfo(&info))

// Will print the types requested by the Invoke statement
for _, typ := range info.ReqTypes {
    fmt.Println(typ) 
}

``` 

Refs: GO-669

codecov[bot]: ## [Codecov](https://codecov.io/gh/uber-go/dig/pull/383?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) Report
> Merging [#383](https://codecov.io/gh/uber-go/dig/pull/383?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) (f168247) into [master](https://codecov.io/gh/uber-go/dig/commit/87cf89e9865ae340333b523007b0ecbeae6c6da2?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) (87cf89e) will **decrease** coverage by `0.06%`.
> The diff coverage is `91.66%`.

```diff
@@            Coverage Diff             @@
##           master     #383      +/-   ##
==========================================
- Coverage   98.35%   98.29%   -0.06%     
==========================================
  Files          21       21              
  Lines        1458     1470      +12     
==========================================
+ Hits         1434     1445      +11     
- Misses         15       16       +1     
  Partials        9        9              
```


| [Impacted Files](https://codecov.io/gh/uber-go/dig/pull/383?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) | Coverage Δ | |
|---|---|---|
| [invoke.go](https://codecov.io/gh/uber-go/dig/pull/383?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go#diff-aW52b2tlLmdv) | `98.43% <91.66%> (-1.57%)` | :arrow_down: |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go)


Patch:
diff --git a/invoke.go b/invoke.go
index 432210c..8a70121 100644
--- a/invoke.go
+++ b/invoke.go
@@ -22,16 +22,48 @@ package dig
 
 import (
 	"fmt"
-	"reflect"
-
 	"go.uber.org/dig/internal/digreflect"
 	"go.uber.org/dig/internal/graph"
+	"reflect"
 )
 
-// An InvokeOption modifies the default behavior of Invoke. It's included for
-// future functionality; currently, there are no concrete implementations.
+// An InvokeOption modifies the default behavior of Invoke.
 type InvokeOption interface {
-	unimplemented()
+	applyInvokeOption(*invokeOptions)
+}
+
+type invokeOptions struct {
+	Info *InvokeInfo
+}
+
+// InvokeInfo provides information about an Invoke.
+type InvokeInfo struct {
+	Inputs []*Input
+}
+
+// FillInvokeInfo is an InvokeOption that writes information on the types
+// accepted by the Invoke function into the specified InvokeInfo.
+// For example:
+//
+//			var info dig.InvokeInfo
+//			err := c.Invoke(func(string, int){}, dig.FillInvokeInfo(&info))
+//
+//	  info.Inputs[0].String() will be string.
+//	  info.Inputs[1].String() will be int.
+func FillInvokeInfo(info *InvokeInfo) InvokeOption {
+	return fillInvokeInfoOption{info: info}
+}
+
+type fillInvokeInfoOption struct {
+	info *InvokeInfo
+}
+
+func (o fillInvokeInfoOption) String() string {
+	return fmt.Sprintf("FillInvokeInfo(%p)", o.info)
+}
+
+func (o fillInvokeInfoOption) applyInvokeOption(opts *invokeOptions) {
+	opts.Info = o.info
 }
 
 // Invoke runs the given function after instantiating its dependencies.
@@ -105,6 +137,26 @@ func (s *Scope) Invoke(function interface{}, opts ...InvokeOption) (err error) {
 		}()
 	}
 
+	var options invokeOptions
+	for _, o := range opts {
+		o.applyInvokeOption(&options)
+	}
+
+	// Record info for the invoke if requested
+	if info := options.Info; info != nil {
+		params := pl.DotParam()
+		info.Inputs = make([]*Input, len(params))
+		for i, p := range params {
+			info.Inputs[i] = &Input{
+				t:        p.Type,
+				optional: p.Optional,
+				name:     p.Name,
+				group:    p.Group,
+			}
+		}
+
+	}
+
 	returned := s.invokerFn(reflect.ValueOf(function), args)
 	if len(returned) == 0 {
 		return nil


Test patch:
diff --git a/dig_test.go b/dig_test.go
index 69b10f9..9158a2e 100644
--- a/dig_test.go
+++ b/dig_test.go
@@ -3741,6 +3741,97 @@ func TestProvideInfoOption(t *testing.T) {
 	})
 }
 
+func TestInvokeInfoOption(t *testing.T) {
+	t.Parallel()
+
+	type type1 struct{}
+	type params struct {
+		dig.In
+
+		Field1 string
+		Field2 int
+	}
+	type type2 struct{}
+	c := digtest.New(t)
+	c.RequireProvide(func() map[string]string { return map[string]string{} })
+	c.RequireProvide(func() *type1 { return &type1{} })
+	c.RequireProvide(func() (string, int) {
+		return "hello", 2023
+	})
+	c.RequireProvide(func(s string) *type2 {
+		if s == "hello" {
+			return &type2{}
+		}
+		return nil
+	})
+
+	tests := []struct {
+		desc     string
+		invokeFn any
+		numTyps  int
+		wantStrs []string
+	}{
+		{
+			desc:     "one map type requested",
+			invokeFn: func(m map[string]string) {},
+			numTyps:  1,
+			wantStrs: []string{"map[string]string"},
+		},
+		{
+			desc:     "one map type, one struct type requested",
+			invokeFn: func(m map[string]string, typ1 *type1) {},
+			numTyps:  2,
+			wantStrs: []string{"map[string]string", "*dig_test.type1"},
+		},
+		{
+			desc:     "invoke with param",
+			invokeFn: func(p params) {},
+			numTyps:  2,
+			wantStrs: []string{"string", "int"},
+		},
+		{
+			desc:     "invoke with dependencies",
+			invokeFn: func(typ2 *type2) {},
+			numTyps:  1,
+			wantStrs: []string{"*dig_test.type2"},
+		},
+	}
+
+	for _, tt := range tests {
+		var info dig.InvokeInfo
+		c.RequireInvoke(tt.invokeFn, dig.FillInvokeInfo(&info))
+		require.NotNil(t, info)
+		require.Len(t, info.Inputs, tt.numTyps)
+		for i := 0; i < tt.numTyps; i++ {
+			assert.Equal(t, tt.wantStrs[i], info.Inputs[i].String())
+		}
+	}
+
+	t.Run("no error on nil InvokeInfo", func(t *testing.T) {
+		c := digtest.New(t)
+		c.RequireProvide(func() string { return "" })
+		c.RequireInvoke(func(s string) {}, dig.FillInvokeInfo(nil))
+	})
+}
+
+func TestFillInvokeInfoString(t *testing.T) {
+	t.Parallel()
+
+	t.Run("nil", func(t *testing.T) {
+		t.Parallel()
+
+		assert.Equal(t, "FillInvokeInfo(0x0)", fmt.Sprint(dig.FillInvokeInfo(nil)))
+	})
+
+	t.Run("not nil", func(t *testing.T) {
+		t.Parallel()
+
+		opt := dig.FillInvokeInfo(new(dig.InvokeInfo))
+		assert.NotEqual(t, fmt.Sprint(opt), "FillInvokeInfo(0x0)")
+		assert.Contains(t, fmt.Sprint(opt), "FillInvokeInfo(0x")
+	})
+}
+
 func TestEndToEndSuccessWithAliases(t *testing.T) {
 	t.Run("pointer constructor", func(t *testing.T) {
 		type Buffer = *bytes.Buffer