Repository: uber-go/fx (go)

Issue:
Annotated structs cannot be used to resolve interfaces
Currently, FX offers some limitations when resolving interfaces to implementations. This brings complications when following the concept of accepting interfaces (and defining them on the client's side) and returning values.
Given two similar interfaces to be bound to the same struct, what I usually do to overcome this is to make a wrapper constructor to resolve structs to the interface.  Such as:
```go
package foo

type interface Performer{
    Perform()
}

type Foo{
    performer Performer
}

func NewFoo(p Performer) *Foo {
    return &Foo{performer: p}
}
```
```go
package bar

type interface Performer{
    Perform()
}

type Bar{
    performer Performer
}

func NewBar(p Performer) *Bar {
    return &Bar{performer: p}
}
```
```go
package performer

type struct Performer{}

func (Performer) Perform() {}

func NewPerformer() *Performer {
    return &Performer{}
}
```
And eventually in a base package:
```go
fx.Provide(
    performer.NewPerformer()
    func(p *performer.Performer) foo.Performer { return p }
    func(p *performer.Performer) bar.Performer { return p }
)
```
By doing this, I can effectively bind several interfaces to the same (or multiple) implementations. It's not that bad but has the drawback of having to redefine these constructors for every interface.
I've been using FX for a while doing this. But lately, I was reworking some of my providers and decided to try `fx.In`, `fx.Out`, and `fx.Annotaed`. 
It would be perfect to bind these interfaces based on their names. NestJS does something [similar](https://docs.nestjs.com/fundamentals/custom-providers#non-class-based-provider-tokens).
I'm not sure if the lack of support for this is a limitation or is by design, especially because of the fx.In docs, it says "Note that both the name AND type of the fields on the parameter struct must match the corresponding result struct.".

Anyway, would you be open to a PR to change this to allow named values to be injected based solely on the name and interface, and not on the actual type?

Pull request description:
Implement fx.As Annotation
This implements `fx.As` Annotation, which lets you annotate a constructor's result types to be provided to the container as a different interface type. 

For example,
```go
fx.Provide(
  fx.Annotate(bytes.NewBuffer(...), fx.As(io.Writer))
)
```
Specifies that the return type of `bytes.NewBuffer` (`bytes.Buffer`) should be provided as `io.Writer` type. 

We leverage the existing reflection code for generating an fx.Out struct type whose field type is the annotated type. This lets us merge the code with `ResultTags` Annotation for cleaner implementation.

Resolves #800, #673

Patch:
diff --git a/annotated.go b/annotated.go
index efa3772..b788dd2 100644
--- a/annotated.go
+++ b/annotated.go
@@ -96,14 +96,18 @@ func (a Annotated) String() string {
 // annotations is used for building out the info needed to generate struct
 // with tags using reflection.
 type annotations struct {
-	fType reflect.Type
+	fType     reflect.Type   // type of the function being annotated
+	asTargets []reflect.Type // list of interfaces the results of target function needs to be annotated as.
+	outTags   []string       // struct tags for the output, if any.
 
-	Ins  []reflect.Type
-	Outs []reflect.Type
+	Ins  []reflect.Type // types of the annotated function's inputs, if any.
+	Outs []reflect.Type // types of the annotated function's outputs, if any.
 
-	annotatedIn  bool
-	annotatedOut bool
+	annotatedIn  bool // whether the function's input is being annotated
+	annotatedOut bool // whether the function's output is being annotated
+	annotatedAs  bool // whether the function's output is being annotated as new types
 
+	returnsError  bool  // whether the function returns an error
 	resultOffsets []int // resultOffsets[N] gives the field offset of Nth result.
 }
 
@@ -126,6 +130,63 @@ func newAnnotations(fType reflect.Type) annotations {
 	}
 }
 
+// field used for embedding fx.Out type in generated struct.
+var _outAnnotationField = reflect.StructField{
+	Name:      "Out",
+	Type:      reflect.TypeOf(Out{}),
+	Anonymous: true,
+}
+
+// This generates the annotated Out struct when function is annotated
+// with fx.As and/or fx.ResultTags options.
+func (ann *annotations) genAnnotatedOutStruct() error {
+	if !ann.annotatedOut && !ann.annotatedAs {
+		return nil
+	}
+	fType := ann.fType
+	// offsets[i] is the index of this field in annotatedResult.
+	// This will always be >0 when valid.
+	offsets := make([]int, fType.NumOut())
+	annotatedResult := []reflect.StructField{_outAnnotationField}
+	for i := 0; i < fType.NumOut(); i++ {
+		if fType.Out(i) == _typeOfError {
+			ann.returnsError = true
+			continue
+		}
+		structFieldType, err := ann.structFieldType(i)
+		if err != nil {
+			return err
+		}
+		structField := reflect.StructField{
+			Name: fmt.Sprintf("Field%d", i),
+			Type: structFieldType,
+		}
+		if i < len(ann.outTags) {
+			structField.Tag = reflect.StructTag(ann.outTags[i])
+		}
+		offsets[i] = len(annotatedResult)
+		annotatedResult = append(annotatedResult, structField)
+	}
+	ann.Outs = []reflect.Type{reflect.StructOf(annotatedResult)}
+	ann.resultOffsets = offsets
+	if ann.returnsError {
+		ann.Outs = append(ann.Outs, _typeOfError)
+	}
+	return nil
+}
+
+// helper for getting type of a fx.Out struct field
+func (ann *annotations) structFieldType(i int) (reflect.Type, error) {
+	if !ann.annotatedAs {
+		return ann.fType.Out(i), nil
+	}
+	asType := ann.asTargets[i]
+	if !ann.fType.Out(i).Implements(asType) {
+		return nil, fmt.Errorf("invalid fx.As: %v does not implement %v", ann.fType, asType)
+	}
+	return asType, nil
+}
+
 // Annotation can be passed to Annotate(f interface{}, anns ...Annotation)
 // for annotating the parameter and result types of a function.
 type Annotation interface {
@@ -230,36 +291,7 @@ func (rt resultTagsAnnotation) apply(ann *annotations) error {
 		return errors.New("cannot apply more than one line of ResultTags")
 	}
 	ann.annotatedOut = true
-	fType := ann.fType
-
-	annotatedResult := []reflect.StructField{{
-		Name:      "Out",
-		Type:      reflect.TypeOf(Out{}),
-		Anonymous: true,
-	}}
-
-	returnsError := false
-	offsets := make([]int, fType.NumOut())
-	for i := 0; i < fType.NumOut(); i++ {
-		if fType.Out(i) == _typeOfError {
-			returnsError = true
-			continue
-		}
-		structField := reflect.StructField{
-			Name: fmt.Sprintf("Field%d", i),
-			Type: fType.Out(i),
-		}
-		if i < len(rt.tags) {
-			structField.Tag = reflect.StructTag(rt.tags[i])
-		}
-		offsets[i] = len(annotatedResult)
-		annotatedResult = append(annotatedResult, structField)
-	}
-	ann.Outs = []reflect.Type{reflect.StructOf(annotatedResult)}
-	ann.resultOffsets = offsets
-	if returnsError {
-		ann.Outs = append(ann.Outs, _typeOfError)
-	}
+	ann.outTags = rt.tags
 	return nil
 }
 
@@ -270,7 +302,68 @@ func ResultTags(tags ...string) Annotation {
 	return resultTagsAnnotation{tags}
 }
 
-// Annotate lets you annotate a function's parameters and returns with tags
+type asAnnotation struct {
+	targets []interface{}
+}
+
+var _ asAnnotation = asAnnotation{}
+
+// As is an Annotation that annotates the result of a function (i.e. a
+// constructor) to be provided as another interface.
+//
+// For example, the following code specifies that the return type of
+// bytes.NewBuffer (bytes.Buffer) should be provided as io.Writer type:
+//
+//   fx.Provide(
+//     fx.Annotate(bytes.NewBuffer(...), fx.As(io.Writer))
+//   )
+//
+// In other words, the code above is equivalent to:
+//
+//  fx.Provide(func() io.Writer {
+//    return bytes.NewBuffer()
+//    // provides io.Writer instead of *bytes.Buffer
+//  })
+//
+// Note that the bytes.Buffer type is provided as an io.Writer type, so this
+// constructor does NOT provide both bytes.Buffer and io.Writer type; it just
+// provides io.Writer type.
+//
+// When multiple values are returned by the annotated function, each type
+// gets mapped to corresponding positional result of the annotated function.
+//
+// For example,
+//  func a() (bytes.Buffer, bytes.Buffer) {
+//    ...
+//  }
+//  fx.Provide(
+//    fx.Annotate(a, fx.As(io.Writer, io.Reader))
+//  )
+//
+// Is equivalent to,
+//
+//  fx.Provide(func() (io.Writer, io.Reader) {
+//    w, r := a()
+//    return w, r
+//  }
+//
+func As(interfaces ...interface{}) Annotation {
+	return asAnnotation{interfaces}
+}
+
+func (at asAnnotation) apply(ann *annotations) error {
+	if ann.annotatedAs {
+		return errors.New("cannot apply more than one line of As")
+	}
+	ann.annotatedAs = true
+	ann.asTargets = make([]reflect.Type, len(at.targets))
+	for i, target := range at.targets {
+		ann.asTargets[i] = reflect.TypeOf(target).Elem()
+	}
+	return nil
+}
+
+// Annotate lets you annotate a function's parameters and returns
 // without you having to declare separate struct definitions for them.
 //
 // For example,
@@ -335,6 +428,13 @@ func Annotate(f interface{}, anns ...Annotation) interface{} {
 		}
 	}
 
+	if err := annotations.genAnnotatedOutStruct(); err != nil {
+		return annotationError{
+			target: f,
+			err:    err,
+		}
+	}
+
 	ins := annotations.Ins
 	outs := annotations.Outs
 	resultOffsets := annotations.resultOffsets
@@ -354,22 +454,20 @@ func Annotate(f interface{}, anns ...Annotation) interface{} {
 		// Call the wrapped function.
 		fResults = fVal.Call(fParams)
 
-		if !annotations.annotatedOut {
+		// If the function's output wasn't annotated and we don't need
+		// to provide it as another type, there's no need to generate
+		// an fx.Out embedded struct so we return early.
+		if !annotations.annotatedOut && !annotations.annotatedAs {
 			return fResults
 		}
 
 		var errValue reflect.Value
-		returnsError := len(outs) > 1
 
 		// wrap the result in an annotated struct
 		results := reflect.New(outs[0]).Elem()
 
 		// aggregate the errors from the annotated function
 		// into one error.
-		if returnsError {
-			errValue = reflect.New(outs[1]).Elem()
-		}
-
 		var errResults error
 		for i := 0; i < numOut; i++ {
 			if fResults[i].Type() == _typeOfError {
@@ -380,7 +478,7 @@ func Annotate(f interface{}, anns ...Annotation) interface{} {
 			}
 			results.Field(resultOffsets[i]).Set(fResults[i])
 		}
-		if returnsError {
+		if annotations.returnsError {
 			if errResults != nil {
 				errValue = reflect.ValueOf(errResults)
 				return []reflect.Value{results, errValue}


Test patch:
diff --git a/annotated_test.go b/annotated_test.go
index 1f60e4a..ca72445 100644
--- a/annotated_test.go
+++ b/annotated_test.go
@@ -23,6 +23,8 @@ package fx_test
 import (
 	"bytes"
 	"errors"
+	"fmt"
+	"io"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -65,6 +67,217 @@ func TestAnnotated(t *testing.T) {
 	})
 }
 
+type asStringer struct {
+	name string
+}
+
+func (as *asStringer) String() string {
+	return as.name
+}
+
+type anotherStringer struct {
+	name string
+}
+
+func (a anotherStringer) String() string {
+	return a.name
+}
+
+func TestAnnotatedAs(t *testing.T) {
+	t.Parallel()
+	type in struct {
+		fx.In
+
+		S fmt.Stringer `name:"goodStringer"`
+	}
+	type myStringer interface {
+		String() string
+	}
+
+	newAsStringer := func() *asStringer {
+		return &asStringer{
+			name: "a good stringer",
+		}
+	}
+
+	tests := []struct {
+		desc    string
+		provide fx.Option
+		invoke  interface{}
+	}{
+		{
+			desc: "provide a good stringer",
+			provide: fx.Provide(
+				fx.Annotate(newAsStringer, fx.As(new(fmt.Stringer))),
+			),
+			invoke: func(s fmt.Stringer) {
+				assert.Equal(t, s.String(), "a good stringer")
+			},
+		},
+		{
+			desc: "value type implementing interface",
+			provide: fx.Provide(
+				fx.Annotate(func() anotherStringer {
+					return anotherStringer{
+						"another stringer",
+					}
+				}, fx.As(new(fmt.Stringer))),
+			),
+			invoke: func(s fmt.Stringer) {
+				assert.Equal(t, s.String(), "another stringer")
+			},
+		},
+		{
+			desc: "provide with multiple types As",
+			provide: fx.Provide(fx.Annotate(func() (*asStringer, *bytes.Buffer) {
+				buf := make([]byte, 1)
+				b := bytes.NewBuffer(buf)
+				return &asStringer{name: "stringer"}, b
+			}, fx.As(new(fmt.Stringer), new(io.Writer)))),
+			invoke: func(s fmt.Stringer, w io.Writer) {
+				w.Write([]byte(s.String()))
+			},
+		},
+		{
+			desc: "provide as with result annotation",
+			provide: fx.Provide(
+				fx.Annotate(func() *asStringer {
+					return &asStringer{name: "stringer"}
+				},
+					fx.ResultTags(`name:"goodStringer"`),
+					fx.As(new(fmt.Stringer))),
+			),
+			invoke: func(i in) {
+				assert.Equal(t, "stringer", i.S.String())
+			},
+		},
+		{
+			// same as the test above, except now we annotate
+			// it in a different order.
+			desc: "provide as with result annotation, in different order",
+			provide: fx.Provide(
+				fx.Annotate(func() *asStringer {
+					return &asStringer{name: "stringer"}
+				},
+					fx.As(new(fmt.Stringer)),
+					fx.ResultTags(`name:"goodStringer"`)),
+			),
+			invoke: func(i in) {
+				assert.Equal(t, "stringer", i.S.String())
+			},
+		},
+		{
+			desc: "provide multiple constructors annotated As",
+			provide: fx.Provide(
+				fx.Annotate(func() *asStringer {
+					return &asStringer{name: "stringer"}
+				}, fx.As(new(fmt.Stringer))),
+				fx.Annotate(func() *bytes.Buffer {
+					buf := make([]byte, 1)
+					return bytes.NewBuffer(buf)
+				}, fx.As(new(io.Writer))),
+			),
+			invoke: func(s fmt.Stringer, w io.Writer) {
+				assert.Equal(t, "stringer", s.String())
+				_, err := w.Write([]byte{1})
+				require.NoError(t, err)
+			},
+		},
+		{
+			desc: "provide the same provider as multiple types",
+			provide: fx.Provide(
+				fx.Annotate(newAsStringer, fx.As(new(fmt.Stringer))),
+				fx.Annotate(newAsStringer, fx.As(new(myStringer))),
+			),
+			invoke: func(s fmt.Stringer, ms myStringer) {
+				assert.Equal(t, "a good stringer", s.String())
+				assert.Equal(t, "a good stringer", ms.String())
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.desc, func(t *testing.T) {
+			t.Parallel()
+
+			app := NewForTest(t,
+				fx.WithLogger(func() fxevent.Logger {
+					return fxtest.NewTestLogger(t)
+				}),
+				tt.provide,
+				fx.Invoke(tt.invoke),
+			)
+			require.NoError(t, app.Err())
+		})
+	}
+}
+
+func TestAnnotatedAsFailures(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		desc          string
+		provide       fx.Option
+		invoke        interface{}
+		errorContains string
+	}{
+		{
+			desc: "provide when an illegal type As",
+			provide: fx.Provide(fx.Annotate(func() *asStringer {
+				return &asStringer{name: "stringer"}
+			}, fx.As(new(io.Writer)))),
+			invoke:        func() {},
+			errorContains: "does not implement",
+		},
+		{
+			desc: "provide two lines of As",
+			provide: fx.Provide(fx.Annotate(func() *asStringer {
+				return &asStringer{name: "stringer"}
+			}, fx.As(new(io.Writer)), fx.As(new(io.Reader)))),
+			invoke:        func() {},
+			errorContains: "cannot apply more than one line of As",
+		},
+		{
+			desc: "don't provide original type using As",
+			provide: fx.Provide(fx.Annotate(func() *asStringer {
+				return &asStringer{name: "stringer"}
+			}, fx.As(new(fmt.Stringer)))),
+			invoke: func(as *asStringer) {
+				fmt.Println(as.String())
+			},
+			errorContains: "missing type: *fx_test.asStringer",
+		},
+		{
+			desc: "fail to provide with name annotation",
+			provide: fx.Provide(fx.Annotate(func(n string) *asStringer {
+				return &asStringer{name: n}
+			}, fx.As(new(fmt.Stringer)), fx.ParamTags(`name:"n"`))),
+			invoke: func(a fmt.Stringer) {
+				fmt.Println(a)
+			},
+			errorContains: `missing type: string[name="n"]`,
+		},
+	}
+
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.desc, func(t *testing.T) {
+			t.Parallel()
+			app := NewForTest(t,
+				fx.WithLogger(func() fxevent.Logger {
+					return fxtest.NewTestLogger(t)
+				}),
+				tt.provide,
+				fx.Invoke(tt.invoke),
+			)
+			err := app.Err()
+			require.Error(t, err)
+			assert.Contains(t, err.Error(), tt.errorContains)
+		})
+	}
+}
+
 func TestAnnotatedWrongUsage(t *testing.T) {
 	t.Parallel()