Skip to content

Commit

Permalink
Call variadic constructors without arguments (#123)
Browse files Browse the repository at this point in the history
Dig constructors may now be variadic functions. These functions will be
called with their dependencies as usual and no arguments will be passed
for the variadic arguments.

So a constructor `New(*Foo, *Bar, ...Option) *Baz` will be treated as
`New(*Foo, *Bar) *Baz`.

See #120 for more discussion.
  • Loading branch information
abhinav authored Jul 11, 2017
1 parent d6bf8ca commit a730197
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

- So that we can introduce new functionality after a 1.0 release, add a
variadic options parameter to all public APIs.
- Added support for functions with variadic arguments. These functions will be
called without supplying their variadic arguments.

## v1.0.0-rc1 (21 Jun 2017)

Expand Down
31 changes: 26 additions & 5 deletions dig.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,9 +352,10 @@ func (c *Container) remove(nodes []*node) {
}

func (c *Container) constructorArgs(ctype reflect.Type) ([]reflect.Value, error) {
args := make([]reflect.Value, 0, ctype.NumIn())
for i := 0; i < ctype.NumIn(); i++ {
arg, err := c.get(edge{key: key{t: ctype.In(i)}})
argTypes := getConstructorArgTypes(ctype)
args := make([]reflect.Value, 0, len(argTypes))
for _, t := range argTypes {
arg, err := c.get(edge{key: key{t: t}})
if err != nil {
return nil, fmt.Errorf("couldn't get arguments for constructor %v: %v", ctype, err)
}
Expand Down Expand Up @@ -390,8 +391,8 @@ func newNode(k key, ctor interface{}, ctype reflect.Type) (*node, error) {
// Retrieves the dependencies for a constructor
func getConstructorDependencies(ctype reflect.Type) ([]edge, error) {
var deps []edge
for i := 0; i < ctype.NumIn(); i++ {
err := traverseInTypes(ctype.In(i), func(e edge) {
for _, t := range getConstructorArgTypes(ctype) {
err := traverseInTypes(t, func(e edge) {
deps = append(deps, e)
})
if err != nil {
Expand All @@ -401,6 +402,26 @@ func getConstructorDependencies(ctype reflect.Type) ([]edge, error) {
return deps, nil
}

// Retrieves the types of the arguments of a constructor in-order.
//
// If the constructor is a variadic function, the returned list does NOT
// include the implicit slice argument because dig does not support passing
// those values in yet.
func getConstructorArgTypes(ctype reflect.Type) []reflect.Type {
numArgs := ctype.NumIn()
if ctype.IsVariadic() {
// NOTE: If the function is variadic, we skip the last argument
// because we're not filling variadic arguments yet. See #120.
numArgs--
}

args := make([]reflect.Type, numArgs)
for i := 0; i < numArgs; i++ {
args[i] = ctype.In(i)
}
return args
}

func cycleError(cycle []key, last key) error {
b := &bytes.Buffer{}
for _, k := range cycle {
Expand Down
53 changes: 53 additions & 0 deletions dig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,59 @@ func TestEndToEndSuccess(t *testing.T) {
assert.Nil(t, p.Baz, "Baz must be unset")
}), "invoke failed")
})

t.Run("variadic arguments invoke", func(t *testing.T) {
c := New()

type A struct{}

var gaveA *A
require.NoError(t, c.Provide(func() *A {
gaveA = &A{}
return gaveA
}), "failed to provide A")

require.NoError(t, c.Provide(func() []*A {
panic("[]*A constructor must not be called.")
}), "failed to provide A slice")

require.NoError(t, c.Invoke(func(a *A, as ...*A) {
require.NotNil(t, a, "A must not be nil")
require.True(t, a == gaveA, "A must match")
require.Empty(t, as, "varargs must be empty")
}), "failed to invoke")
})

t.Run("variadic arguments dependency", func(t *testing.T) {
c := New()

type A struct{}
type B struct{}

var gaveA *A
require.NoError(t, c.Provide(func() *A {
gaveA = &A{}
return gaveA
}), "failed to provide A")

require.NoError(t, c.Provide(func() []*A {
panic("[]*A constructor must not be called.")
}), "failed to provide A slice")

var gaveB *B
require.NoError(t, c.Provide(func(a *A, as ...*A) *B {
require.NotNil(t, a, "A must not be nil")
require.True(t, a == gaveA, "A must match")
require.Empty(t, as, "varargs must be empty")
gaveB = &B{}
return gaveB
}), "failed to provide B")

require.NoError(t, c.Invoke(func(b *B) {
require.NotNil(t, b, "B must not be nil")
require.True(t, b == gaveB, "B must match")
}), "failed to invoke")
})
}

func TestProvideConstructorErrors(t *testing.T) {
Expand Down

0 comments on commit a730197

Please sign in to comment.