Skip to content

Commit

Permalink
doc: Clarify Supply/Replace behavior with interface values (#857)
Browse files Browse the repository at this point in the history
Use of `fx.Replace` or `fx.Supply` with interface values can lead to
unexpected behavior. `fx.Supply((io.Reader)(r))` will use the type of
the implementation `r`, not `io.Reader`.

This clarifies the documentation for both APIs to make this explicit.

Refs GO-1253
  • Loading branch information
abhinav committed Mar 9, 2022
1 parent 5ebb242 commit 6bc0d21
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 3 deletions.
44 changes: 41 additions & 3 deletions replace.go
Expand Up @@ -28,14 +28,52 @@ import (
"go.uber.org/fx/internal/fxreflect"
)

// Replace provides instantiated values for graph modification. Similar to
// what fx.Supply is to fx.Provide, values provided by fx.Replace behaves
// similarly to values produced by decorators specified with fx.Decorate.
// Replace provides instantiated values for graph modification as if
// they had been provided using a decorator with fx.Decorate.
// The most specific type of each value (as determined by reflection) is used.
//
// Refer to the documentation on fx.Decorate to see how graph modifications
// work with fx.Module.
//
// This serves a purpose similar to what fx.Supply does for fx.Provide.
//
// For example, given,
//
// var log *zap.Logger = ...
//
// The following two forms are equivalent.
//
// fx.Replace(log)
//
// fx.Decorate(
// func() *zap.Logger {
// return log
// },
// )
//
// Replace panics if a value (or annotation target) is an untyped nil or an error.
//
// Replace Caveats
//
// As mentioned above, Replace uses the most specific type of the provided
// value. For interface values, this refers to the type of the implementation,
// not the interface. So if you try to replace an io.Writer, fx.Replace will
// use the type of the implementation.
//
// var stderr io.Writer = os.Stderr
// fx.Replace(stderr)
//
// Is equivalent to,
//
// fx.Decorate(func() *os.File { return os.Stderr })
//
// This is typically NOT what you intended. To replace the io.Writer in the
// container with the value above, we need to use the fx.Annotate function with
// the fx.As annotation.
//
// fx.Replace(
// fx.Annotate(os.Stderr, fx.As(new(io.Writer)))
// )
func Replace(values ...interface{}) Option {
decorators := make([]interface{}, len(values)) // one function per value
types := make([]reflect.Type, len(values))
Expand Down
24 changes: 24 additions & 0 deletions supply.go
Expand Up @@ -32,6 +32,8 @@ import (
// they had been provided using a constructor that simply returns them.
// The most specific type of each value (as determined by reflection) is used.
//
// This serves a purpose similar to what fx.Replace does for fx.Decorate.
//
// For example, given:
//
// type (
Expand All @@ -53,6 +55,28 @@ import (
// )
//
// Supply panics if a value (or annotation target) is an untyped nil or an error.
//
// Supply Caveats
//
// As mentioned above, Supply uses the most specific type of the provided
// value. For interface values, this refers to the type of the implementation,
// not the interface. So if you supply an http.Handler, fx.Supply will use the
// type of the implementation.
//
// var handler http.Handler = http.HandlerFunc(f)
// fx.Supply(handler)
//
// Is equivalent to,
//
// fx.Provide(func() http.HandlerFunc { return f })
//
// This is typically NOT what you intended. To supply the handler above as an
// http.Handler, we need to use the fx.Annotate function with the fx.As
// annotation.
//
// fx.Supply(
// fx.Annotate(handler, fx.As(new(http.Handler))),
// )
func Supply(values ...interface{}) Option {
constructors := make([]interface{}, len(values)) // one function per value
types := make([]reflect.Type, len(values))
Expand Down

0 comments on commit 6bc0d21

Please sign in to comment.