Repository: uber-go/dig (go)

Issue:
ProvideOption to override digreflect.Func
## Problem

It is really hard to debug errors with constructors created by reflection because the error will just say `"reflect".makeFuncStub (/Users/arc/sdk/go1.16.3/src/reflect/asm_amd64.s:14)`.

## Proposed Solution

I propose creating a `ProvideOption` that allows setting `digreflect.Func` manually so that frameworks that are building functions with reflection can override this information to provide better error messages to users. I imagine this would improve Fx as well because `Supply` builds a constructor by reflection.

Pull request:
Add LocationForPC ProvideOption
Closes #281 

Patch:
diff --git a/dig.go b/dig.go
index dc3c236..ae33096 100644
--- a/dig.go
+++ b/dig.go
@@ -60,9 +60,10 @@ type optionFunc func(*Container)
 func (f optionFunc) applyOption(c *Container) { f(c) }
 
 type provideOptions struct {
-	Name  string
-	Group string
-	Info  *ProvideInfo
+	Name     string
+	Group    string
+	Info     *ProvideInfo
+	Location *digreflect.Func
 }
 
 func (o *provideOptions) Validate() error {
@@ -198,6 +199,18 @@ func FillProvideInfo(info *ProvideInfo) ProvideOption {
 	})
 }
 
+// LocationForPC is a ProvideOption which specifies an alternate function program
+// counter address to be used for debug information. The package, name, file and
+// line number of this alternate function address will be used in error messages
+// and DOT graphs. This option is intended to be used with functions created
+// with the reflect.MakeFunc method whose error messages are otherwise hard to
+// understand
+func LocationForPC(pc uintptr) ProvideOption {
+	return provideOptionFunc(func(opts *provideOptions) {
+		opts.Location = digreflect.InspectFuncPC(pc)
+	})
+}
+
 // An InvokeOption modifies the default behavior of Invoke. It's included for
 // future functionality; currently, there are no concrete implementations.
 type InvokeOption interface {
@@ -538,6 +551,7 @@ func (c *Container) provide(ctor interface{}, opts provideOptions) error {
 		nodeOptions{
 			ResultName:  opts.Name,
 			ResultGroup: opts.Group,
+			Location:    opts.Location,
 		},
 	)
 	if err != nil {
@@ -742,6 +756,7 @@ type nodeOptions struct {
 	// or belong to the specified value group
 	ResultName  string
 	ResultGroup string
+	Location    *digreflect.Func
 }
 
 func newNode(ctor interface{}, opts nodeOptions) (*node, error) {
@@ -765,10 +780,15 @@ func newNode(ctor interface{}, opts nodeOptions) (*node, error) {
 		return nil, err
 	}
 
+	location := opts.Location
+	if location == nil {
+		location = digreflect.InspectFunc(ctor)
+	}
+
 	return &node{
 		ctor:       ctor,
 		ctype:      ctype,
-		location:   digreflect.InspectFunc(ctor),
+		location:   location,
 		id:         dot.CtorID(cptr),
 		paramList:  params,
 		resultList: results,
diff --git a/internal/digreflect/func.go b/internal/digreflect/func.go
index a170b07..8554ed8 100644
--- a/internal/digreflect/func.go
+++ b/internal/digreflect/func.go
@@ -66,9 +66,18 @@ func (f *Func) Format(w fmt.State, c rune) {
 // function.
 func InspectFunc(function interface{}) *Func {
 	fptr := reflect.ValueOf(function).Pointer()
-	f := runtime.FuncForPC(fptr)
+	return InspectFuncPC(fptr)
+}
+
+// InspectFuncPC inspects and returns runtime information about the function
+// at the given program counter address.
+func InspectFuncPC(pc uintptr) *Func {
+	f := runtime.FuncForPC(pc)
+	if f == nil {
+		return nil
+	}
 	pkgName, funcName := splitFuncName(f.Name())
-	fileName, lineNum := f.FileLine(fptr)
+	fileName, lineNum := f.FileLine(pc)
 	return &Func{
 		Name:    funcName,
 		Package: pkgName,


Test patch:
diff --git a/dig_test.go b/dig_test.go
index a907f61..7c0db2a 100644
--- a/dig_test.go
+++ b/dig_test.go
@@ -1549,6 +1549,24 @@ func TestProvideGroupAndName(t *testing.T) {
 		"name:\"bar\" provided with group:\"foo\"")
 }
 
+type testStruct struct{}
+
+func (testStruct) TestMethod(x int) float64 { return float64(x) }
+
+func TestProvideLocation(t *testing.T) {
+	t.Parallel()
+
+	c := New()
+	err := c.Provide(func(x int) float64 {
+		return testStruct{}.TestMethod(x)
+	}, LocationForPC(reflect.TypeOf(testStruct{}).Method(0).Func.Pointer()))
+	require.NoError(t, err)
+	err = c.Invoke(func(y float64) {})
+	require.Error(t, err)
+	require.Contains(t, err.Error(), `"go.uber.org/dig".testStruct.TestMethod`)
+	require.Contains(t, err.Error(), `dig/dig_test.go`)
+}
+
 func TestCantProvideUntypedNil(t *testing.T) {
 	t.Parallel()
 	c := New()