From 613387ae5f71823c726117b35b30983f1b55e470 Mon Sep 17 00:00:00 2001 From: Michelle Laurenti Date: Mon, 4 Mar 2024 15:42:05 +0100 Subject: [PATCH 1/8] add provided callback --- constructor.go | 21 ++++++++ container.go | 44 ++++++++++++++++ container_test.go | 6 +++ dig_test.go | 131 ++++++++++++++++++++++++++++++++++++++++++++++ scope.go | 4 ++ 5 files changed, 206 insertions(+) diff --git a/constructor.go b/constructor.go index 034c41c2..fd6b4604 100644 --- a/constructor.go +++ b/constructor.go @@ -192,6 +192,27 @@ func (n *constructorNode) Call(c containerStore) (err error) { // the rest of the graph to instantiate the dependencies of this // container. receiver.Commit(n.s) + + if n.s.callback != nil { + for k, v := range receiver.values { + n.s.callback(ProvidedInfo{ + Name: k.name, + Type: k.t, + Value: v, + }) + } + + for k, vs := range receiver.groups { + for _, v := range vs { + n.s.callback(ProvidedInfo{ + Name: k.group, + Type: k.t, + Value: v, + }) + } + } + } + n.called = true return nil } diff --git a/container.go b/container.go index 983fd3f9..21014b5f 100644 --- a/container.go +++ b/container.go @@ -231,6 +231,50 @@ func (o dryRunOption) applyOption(c *Container) { } } +// ProvidedInfo contains information on a constructed value +// and is passed to a [ProvidedCallback] registered with +// [WithProvidedCallback]. +type ProvidedInfo struct { + // Name is the name of the constructed value set with [Name] + // or the name of the group set with [Group]. + Name string + + // Type is the type of the constructed value. + Type reflect.Type + + // Value is the concrete value that was constructed. + Value reflect.Value +} + +// ProvidedCallback is a function that can be registered +// on the container with [WithProvidedCallback], +// and will be called every time a new value is constructed. +type ProvidedCallback func(ProvidedInfo) + +// WithProvidedCallback returns an [Option] which has Dig call +// the passed in [ProvidedCallback] each time a new value +// is constructed. +// +// See [ProvidedInfo] for more info on the information passed +// to the [ProvidedCallback]. +func WithProvidedCallback(callback ProvidedCallback) Option { + return withProvidedCallback{ + callback: callback, + } +} + +type withProvidedCallback struct { + callback ProvidedCallback +} + +func (withProvidedCallback) String() string { + return "WithProvidedCallback()" +} + +func (o withProvidedCallback) applyOption(c *Container) { + c.scope.callback = o.callback +} + // invokerFn specifies how the container calls user-supplied functions. type invokerFn func(fn reflect.Value, args []reflect.Value) (results []reflect.Value) diff --git a/container_test.go b/container_test.go index e75a41cc..078a55c5 100644 --- a/container_test.go +++ b/container_test.go @@ -67,4 +67,10 @@ func TestOptionStrings(t *testing.T) { assert.Equal(t, "RecoverFromPanics()", fmt.Sprint(RecoverFromPanics())) }) + + t.Run("WithProvidedCallback()", func(t *testing.T) { + t.Parallel() + + assert.Equal(t, "WithProvidedCallback()", fmt.Sprint(WithProvidedCallback(func(_ ProvidedInfo) {}))) + }) } diff --git a/dig_test.go b/dig_test.go index 5cbf4ee8..da05f1b2 100644 --- a/dig_test.go +++ b/dig_test.go @@ -1685,6 +1685,137 @@ func TestRecoverFromPanic(t *testing.T) { func giveInt() int { return 5 } +type providedInterface interface { + Start() +} + +type providedStruct struct{} + +func (providedStruct) Start() {} + +func TestProvidedCallback(t *testing.T) { + t.Run("works with primitives", func(t *testing.T) { + var providedCallbackCalled bool + + c := digtest.New(t, dig.WithProvidedCallback(func(pi dig.ProvidedInfo) { + assert.Equal(t, "", pi.Name) + assert.Equal(t, reflect.TypeOf(5), pi.Type) + assert.True(t, pi.Value.CanInt()) + assert.EqualValues(t, 5, pi.Value.Int()) + providedCallbackCalled = true + })) + + c.RequireProvide(giveInt) + + c.RequireInvoke(func(a int) { + assert.Equal(t, 5, a) + }) + + assert.True(t, providedCallbackCalled) + }) + + t.Run("works with named values", func(t *testing.T) { + var providedCallbackCalled bool + + c := digtest.New(t, dig.WithProvidedCallback(func(pi dig.ProvidedInfo) { + assert.Equal(t, "test", pi.Name) + assert.Equal(t, reflect.TypeOf(5), pi.Type) + assert.True(t, pi.Value.CanInt()) + assert.EqualValues(t, 5, pi.Value.Int()) + providedCallbackCalled = true + })) + + c.RequireProvide(giveInt, dig.Name("test")) + + type params struct { + dig.In + + Value int `name:"test"` + } + + c.RequireInvoke(func(a params) { + assert.Equal(t, 5, a.Value) + }) + + assert.True(t, providedCallbackCalled) + }) + + t.Run("works with scopes", func(t *testing.T) { + var providedCallbackCalled bool + + c := digtest.New(t, dig.WithProvidedCallback(func(pi dig.ProvidedInfo) { + assert.Equal(t, "", pi.Name) + assert.Equal(t, reflect.TypeOf(5), pi.Type) + assert.True(t, pi.Value.CanInt()) + assert.EqualValues(t, 5, pi.Value.Int()) + providedCallbackCalled = true + })) + + s := c.Scope("test") + + s.RequireProvide(giveInt) + + s.RequireInvoke(func(a int) { + assert.Equal(t, 5, a) + }) + + assert.True(t, providedCallbackCalled) + }) + + t.Run("works with value groups", func(t *testing.T) { + var providedCallbackCalledTimes int + + c := digtest.New(t, dig.WithProvidedCallback(func(pi dig.ProvidedInfo) { + assert.Equal(t, "test", pi.Name) + assert.Equal(t, reflect.TypeOf(5), pi.Type) + assert.True(t, pi.Value.CanInt()) + assert.True(t, pi.Value.Int() == 5 || pi.Value.Int() == 6) + providedCallbackCalledTimes++ + })) + + c.RequireProvide(giveInt, dig.Group("test")) + c.RequireProvide(func() int { return 6 }, dig.Group("test")) + + type params struct { + dig.In + + Value []int `group:"test"` + } + + c.RequireInvoke(func(a params) { + assert.ElementsMatch(t, []int{5, 6}, a.Value) + }) + + assert.Equal(t, 2, providedCallbackCalledTimes) + }) + + t.Run("works with interfaces", func(t *testing.T) { + var providedCallbackCalled bool + + var gave providedInterface + + c := digtest.New(t, dig.WithProvidedCallback(func(pi dig.ProvidedInfo) { + assert.Equal(t, "", pi.Name) + assert.Equal(t, reflect.TypeOf(&gave).Elem(), pi.Type) + assert.True(t, pi.Value.CanInterface()) + _, ok := pi.Value.Interface().(providedInterface) + assert.True(t, ok) + providedCallbackCalled = true + })) + + c.RequireProvide(func() providedInterface { + gave = &providedStruct{} + return gave + }) + + c.RequireInvoke(func(got providedInterface) { + assert.Equal(t, gave, got) + }) + + assert.True(t, providedCallbackCalled) + }) +} + func TestCallback(t *testing.T) { t.Run("no errors", func(t *testing.T) { var ( diff --git a/scope.go b/scope.go index d5478aca..cc454509 100644 --- a/scope.go +++ b/scope.go @@ -90,6 +90,9 @@ type Scope struct { // All the child scopes of this Scope. childScopes []*Scope + + // Callback to execute for each value that is constructed + callback ProvidedCallback } func newScope() *Scope { @@ -119,6 +122,7 @@ func (s *Scope) Scope(name string, opts ...ScopeOption) *Scope { child.invokerFn = s.invokerFn child.deferAcyclicVerification = s.deferAcyclicVerification child.recoverFromPanics = s.recoverFromPanics + child.callback = s.callback // child copies the parent's graph nodes. for _, node := range s.gh.nodes { From fd1d5947a5deb67b75d1758218c067b0e94375fe Mon Sep 17 00:00:00 2001 From: Michelle Laurenti Date: Wed, 6 Mar 2024 14:44:49 +0100 Subject: [PATCH 2/8] update value callback implementation --- callback.go | 22 +++++ constructor.go | 31 +++---- container.go | 44 ---------- container_test.go | 6 -- decorate.go | 17 +++- dig_test.go | 211 ++++++++++++++++++++++++++++++++-------------- result.go | 10 +++ scope.go | 2 +- 8 files changed, 206 insertions(+), 137 deletions(-) diff --git a/callback.go b/callback.go index dfe47ea6..3e547509 100644 --- a/callback.go +++ b/callback.go @@ -20,6 +20,8 @@ package dig +import "reflect" + // CallbackInfo contains information about a provided function or decorator // called by Dig, and is passed to a [Callback] registered with // [WithProviderCallback] or [WithDecoratorCallback]. @@ -32,6 +34,9 @@ type CallbackInfo struct { // function, if any. When used in conjunction with [RecoverFromPanics], // this will be set to a [PanicError] when the function panics. Error error + + // TODO + Values []reflect.Value } // Callback is a function that can be registered with a provided function @@ -89,6 +94,18 @@ func WithDecoratorCallback(callback Callback) DecorateOption { } } +// WithContainerCallback returns an [Option] which has Dig call +// the passed in [Callback] each time a new value +// is constructed. +// +// See [CallbackInfo] for more info on the information passed +// to the [Callback]. +func WithContainerCallback(callback Callback) Option { + return withCallbackOption{ + callback: callback, + } +} + type withCallbackOption struct { callback Callback } @@ -96,6 +113,7 @@ type withCallbackOption struct { var ( _ ProvideOption = withCallbackOption{} _ DecorateOption = withCallbackOption{} + _ Option = withCallbackOption{} ) func (o withCallbackOption) applyProvideOption(po *provideOptions) { @@ -105,3 +123,7 @@ func (o withCallbackOption) applyProvideOption(po *provideOptions) { func (o withCallbackOption) apply(do *decorateOptions) { do.Callback = o.callback } + +func (o withCallbackOption) applyOption(c *Container) { + c.scope.callback = o.callback +} diff --git a/constructor.go b/constructor.go index fd6b4604..2043bc6d 100644 --- a/constructor.go +++ b/constructor.go @@ -160,12 +160,15 @@ func (n *constructorNode) Call(c containerStore) (err error) { } } + var values []reflect.Value + if n.callback != nil { // Wrap in separate func to include PanicErrors defer func() { n.callback(CallbackInfo{ - Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name), - Error: err, + Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name), + Error: err, + Values: values, }) }() } @@ -193,24 +196,14 @@ func (n *constructorNode) Call(c containerStore) (err error) { // container. receiver.Commit(n.s) - if n.s.callback != nil { - for k, v := range receiver.values { - n.s.callback(ProvidedInfo{ - Name: k.name, - Type: k.t, - Value: v, - }) - } + values = n.resultList.GetValues(results) - for k, vs := range receiver.groups { - for _, v := range vs { - n.s.callback(ProvidedInfo{ - Name: k.group, - Type: k.t, - Value: v, - }) - } - } + if n.s.callback != nil { + n.s.callback(CallbackInfo{ + Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name), + Error: err, + Values: values, + }) } n.called = true diff --git a/container.go b/container.go index 21014b5f..983fd3f9 100644 --- a/container.go +++ b/container.go @@ -231,50 +231,6 @@ func (o dryRunOption) applyOption(c *Container) { } } -// ProvidedInfo contains information on a constructed value -// and is passed to a [ProvidedCallback] registered with -// [WithProvidedCallback]. -type ProvidedInfo struct { - // Name is the name of the constructed value set with [Name] - // or the name of the group set with [Group]. - Name string - - // Type is the type of the constructed value. - Type reflect.Type - - // Value is the concrete value that was constructed. - Value reflect.Value -} - -// ProvidedCallback is a function that can be registered -// on the container with [WithProvidedCallback], -// and will be called every time a new value is constructed. -type ProvidedCallback func(ProvidedInfo) - -// WithProvidedCallback returns an [Option] which has Dig call -// the passed in [ProvidedCallback] each time a new value -// is constructed. -// -// See [ProvidedInfo] for more info on the information passed -// to the [ProvidedCallback]. -func WithProvidedCallback(callback ProvidedCallback) Option { - return withProvidedCallback{ - callback: callback, - } -} - -type withProvidedCallback struct { - callback ProvidedCallback -} - -func (withProvidedCallback) String() string { - return "WithProvidedCallback()" -} - -func (o withProvidedCallback) applyOption(c *Container) { - c.scope.callback = o.callback -} - // invokerFn specifies how the container calls user-supplied functions. type invokerFn func(fn reflect.Value, args []reflect.Value) (results []reflect.Value) diff --git a/container_test.go b/container_test.go index 078a55c5..e75a41cc 100644 --- a/container_test.go +++ b/container_test.go @@ -67,10 +67,4 @@ func TestOptionStrings(t *testing.T) { assert.Equal(t, "RecoverFromPanics()", fmt.Sprint(RecoverFromPanics())) }) - - t.Run("WithProvidedCallback()", func(t *testing.T) { - t.Parallel() - - assert.Equal(t, "WithProvidedCallback()", fmt.Sprint(WithProvidedCallback(func(_ ProvidedInfo) {}))) - }) } diff --git a/decorate.go b/decorate.go index df362e98..f935ae2e 100644 --- a/decorate.go +++ b/decorate.go @@ -121,12 +121,15 @@ func (n *decoratorNode) Call(s containerStore) (err error) { } } + var values []reflect.Value + if n.callback != nil { // Wrap in separate func to include PanicErrors defer func() { n.callback(CallbackInfo{ - Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name), - Error: err, + Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name), + Error: err, + Values: values, }) }() } @@ -146,6 +149,16 @@ func (n *decoratorNode) Call(s containerStore) (err error) { if err = n.results.ExtractList(n.s, true /* decorated */, results); err != nil { return err } + values = n.results.GetValues(results) + + if n.s.callback != nil { + n.s.callback(CallbackInfo{ + Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name), + Error: err, + Values: values, + }) + } + n.state = decoratorCalled return nil } diff --git a/dig_test.go b/dig_test.go index da05f1b2..60d01539 100644 --- a/dig_test.go +++ b/dig_test.go @@ -1695,14 +1695,15 @@ func (providedStruct) Start() {} func TestProvidedCallback(t *testing.T) { t.Run("works with primitives", func(t *testing.T) { - var providedCallbackCalled bool - - c := digtest.New(t, dig.WithProvidedCallback(func(pi dig.ProvidedInfo) { - assert.Equal(t, "", pi.Name) - assert.Equal(t, reflect.TypeOf(5), pi.Type) - assert.True(t, pi.Value.CanInt()) - assert.EqualValues(t, 5, pi.Value.Int()) - providedCallbackCalled = true + var containerCallbackCalled bool + + c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { + assert.Equal(t, "go.uber.org/dig_test.giveInt", ci.Name) + assert.Nil(t, ci.Error) + assert.Len(t, ci.Values, 1) + assert.True(t, ci.Values[0].CanInt()) + assert.EqualValues(t, 5, ci.Values[0].Int()) + containerCallbackCalled = true })) c.RequireProvide(giveInt) @@ -1711,44 +1712,19 @@ func TestProvidedCallback(t *testing.T) { assert.Equal(t, 5, a) }) - assert.True(t, providedCallbackCalled) - }) - - t.Run("works with named values", func(t *testing.T) { - var providedCallbackCalled bool - - c := digtest.New(t, dig.WithProvidedCallback(func(pi dig.ProvidedInfo) { - assert.Equal(t, "test", pi.Name) - assert.Equal(t, reflect.TypeOf(5), pi.Type) - assert.True(t, pi.Value.CanInt()) - assert.EqualValues(t, 5, pi.Value.Int()) - providedCallbackCalled = true - })) - - c.RequireProvide(giveInt, dig.Name("test")) - - type params struct { - dig.In - - Value int `name:"test"` - } - - c.RequireInvoke(func(a params) { - assert.Equal(t, 5, a.Value) - }) - - assert.True(t, providedCallbackCalled) + assert.True(t, containerCallbackCalled) }) t.Run("works with scopes", func(t *testing.T) { - var providedCallbackCalled bool - - c := digtest.New(t, dig.WithProvidedCallback(func(pi dig.ProvidedInfo) { - assert.Equal(t, "", pi.Name) - assert.Equal(t, reflect.TypeOf(5), pi.Type) - assert.True(t, pi.Value.CanInt()) - assert.EqualValues(t, 5, pi.Value.Int()) - providedCallbackCalled = true + var containerCallbackCalled bool + + c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { + assert.Equal(t, "go.uber.org/dig_test.giveInt", ci.Name) + assert.Nil(t, ci.Error) + assert.Len(t, ci.Values, 1) + assert.True(t, ci.Values[0].CanInt()) + assert.EqualValues(t, 5, ci.Values[0].Int()) + containerCallbackCalled = true })) s := c.Scope("test") @@ -1759,18 +1735,19 @@ func TestProvidedCallback(t *testing.T) { assert.Equal(t, 5, a) }) - assert.True(t, providedCallbackCalled) + assert.True(t, containerCallbackCalled) }) t.Run("works with value groups", func(t *testing.T) { - var providedCallbackCalledTimes int - - c := digtest.New(t, dig.WithProvidedCallback(func(pi dig.ProvidedInfo) { - assert.Equal(t, "test", pi.Name) - assert.Equal(t, reflect.TypeOf(5), pi.Type) - assert.True(t, pi.Value.CanInt()) - assert.True(t, pi.Value.Int() == 5 || pi.Value.Int() == 6) - providedCallbackCalledTimes++ + var containerCallbackCalledTimes int + + c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { + assert.NotEmpty(t, ci.Name) + assert.Nil(t, ci.Error) + assert.Len(t, ci.Values, 1) + assert.True(t, ci.Values[0].CanInt()) + assert.True(t, ci.Values[0].Int() == 5 || ci.Values[0].Int() == 6) + containerCallbackCalledTimes++ })) c.RequireProvide(giveInt, dig.Group("test")) @@ -1786,21 +1763,22 @@ func TestProvidedCallback(t *testing.T) { assert.ElementsMatch(t, []int{5, 6}, a.Value) }) - assert.Equal(t, 2, providedCallbackCalledTimes) + assert.Equal(t, 2, containerCallbackCalledTimes) }) t.Run("works with interfaces", func(t *testing.T) { - var providedCallbackCalled bool + var containerCallbackCalled bool var gave providedInterface - c := digtest.New(t, dig.WithProvidedCallback(func(pi dig.ProvidedInfo) { - assert.Equal(t, "", pi.Name) - assert.Equal(t, reflect.TypeOf(&gave).Elem(), pi.Type) - assert.True(t, pi.Value.CanInterface()) - _, ok := pi.Value.Interface().(providedInterface) + c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { + assert.NotEmpty(t, ci.Name) + assert.Nil(t, ci.Error) + assert.Len(t, ci.Values, 1) + assert.True(t, ci.Values[0].CanInterface()) + _, ok := ci.Values[0].Interface().(providedInterface) assert.True(t, ok) - providedCallbackCalled = true + containerCallbackCalled = true })) c.RequireProvide(func() providedInterface { @@ -1812,31 +1790,129 @@ func TestProvidedCallback(t *testing.T) { assert.Equal(t, gave, got) }) - assert.True(t, providedCallbackCalled) + assert.True(t, containerCallbackCalled) + }) + + t.Run("works with provider returning multiple values", func(t *testing.T) { + var containerCallbackCalled bool + + c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { + assert.NotEmpty(t, ci.Name) + assert.Nil(t, ci.Error) + assert.Len(t, ci.Values, 2) + assert.EqualValues(t, "five", ci.Values[0].String()) + assert.EqualValues(t, 5, ci.Values[1].Int()) + containerCallbackCalled = true + })) + + c.RequireProvide(func() (string, int) { + return "five", 5 + }) + + c.RequireInvoke(func(s string, i int) { + assert.Equal(t, "five", s) + assert.Equal(t, 5, i) + }) + + assert.True(t, containerCallbackCalled) + }) + + t.Run("does not receive error with providers that can fail", func(t *testing.T) { + var containerCallbackCalled bool + + c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { + assert.NotEmpty(t, ci.Name) + assert.Nil(t, ci.Error) + assert.Len(t, ci.Values, 2) + assert.EqualValues(t, "five", ci.Values[0].String()) + assert.EqualValues(t, 5, ci.Values[1].Int()) + containerCallbackCalled = true + })) + + c.RequireProvide(func() (string, int, error) { + return "five", 5, nil + }) + + c.RequireInvoke(func(s string, i int) { + assert.Equal(t, "five", s) + assert.Equal(t, 5, i) + }) + + assert.True(t, containerCallbackCalled) + }) + + t.Run("callback not invoked on provider error", func(t *testing.T) { + var containerCallbackCalled bool + + c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { + containerCallbackCalled = true + })) + + c.RequireProvide(func() (string, int, error) { + return "", 0, fmt.Errorf("failed") + }) + + err := c.Invoke(func(_ string, _ int) {}) + + assert.NotNil(t, err) + + assert.False(t, containerCallbackCalled) + }) + + t.Run("values are passed in the correct order", func(t *testing.T) { + }) + + t.Run("values with the As option are passed as the original type", func(t *testing.T) { + }) + + t.Run("object values are passed as objects", func(t *testing.T) { }) } func TestCallback(t *testing.T) { t.Run("no errors", func(t *testing.T) { var ( - provideCallbackCalled bool - decorateCallbackCalled bool + provideCallbackCalled bool + decorateCallbackCalled bool + containerCallbackCalledTimes int ) - c := digtest.New(t) + c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { + containerCallbackCalledTimes++ + switch containerCallbackCalledTimes { + case 1: + assert.Equal(t, "go.uber.org/dig_test.giveInt", ci.Name) + assert.NoError(t, ci.Error) + assert.Len(t, ci.Values, 1) + assert.True(t, ci.Values[0].CanInt()) + assert.EqualValues(t, 5, ci.Values[0].Int()) + case 2: + assert.Equal(t, "go.uber.org/dig_test.TestCallback.func1.3", ci.Name) + assert.NoError(t, ci.Error) + assert.Len(t, ci.Values, 1) + assert.True(t, ci.Values[0].CanInt()) + assert.EqualValues(t, 10, ci.Values[0].Int()) + } + })) c.RequireProvide( giveInt, dig.WithProviderCallback(func(ci dig.CallbackInfo) { assert.Equal(t, "go.uber.org/dig_test.giveInt", ci.Name) assert.NoError(t, ci.Error) + assert.Len(t, ci.Values, 1) + assert.True(t, ci.Values[0].CanInt()) + assert.EqualValues(t, 5, ci.Values[0].Int()) provideCallbackCalled = true }), ) c.RequireDecorate( func(a int) int { return a + 5 }, dig.WithDecoratorCallback(func(ci dig.CallbackInfo) { - assert.Equal(t, "go.uber.org/dig_test.TestCallback.func1.2", ci.Name) + assert.Equal(t, "go.uber.org/dig_test.TestCallback.func1.3", ci.Name) assert.NoError(t, ci.Error) + assert.Len(t, ci.Values, 1) + assert.True(t, ci.Values[0].CanInt()) + assert.EqualValues(t, 10, ci.Values[0].Int()) decorateCallbackCalled = true }), ) @@ -1845,6 +1921,7 @@ func TestCallback(t *testing.T) { assert.True(t, provideCallbackCalled) assert.True(t, decorateCallbackCalled) + assert.Equal(t, 2, containerCallbackCalledTimes) }) t.Run("provide error", func(t *testing.T) { @@ -1858,6 +1935,7 @@ func TestCallback(t *testing.T) { dig.WithProviderCallback(func(ci dig.CallbackInfo) { assert.Equal(t, "go.uber.org/dig_test.TestCallback.func2.1", ci.Name) assert.ErrorContains(t, ci.Error, "terrible callback sadness") + assert.Nil(t, ci.Values) called = true }), ) @@ -1878,6 +1956,7 @@ func TestCallback(t *testing.T) { dig.WithDecoratorCallback(func(ci dig.CallbackInfo) { assert.Equal(t, "go.uber.org/dig_test.TestCallback.func3.1", ci.Name) assert.ErrorContains(t, ci.Error, "terrible callback sadness") + assert.Nil(t, ci.Values) called = true }), ) @@ -1897,6 +1976,7 @@ func TestCallback(t *testing.T) { var pe dig.PanicError assert.True(t, errors.As(ci.Error, &pe)) assert.ErrorContains(t, ci.Error, "panic: \"unreal misfortune\"") + assert.Nil(t, ci.Values) called = true }), ) @@ -1917,6 +1997,7 @@ func TestCallback(t *testing.T) { var pe dig.PanicError assert.True(t, errors.As(ci.Error, &pe)) assert.ErrorContains(t, ci.Error, "panic: \"unreal misfortune\"") + assert.Nil(t, ci.Values) called = true }), diff --git a/result.go b/result.go index 369cd218..192f47ae 100644 --- a/result.go +++ b/result.go @@ -259,6 +259,16 @@ func (rl resultList) ExtractList(cw containerWriter, decorated bool, values []re return nil } +func (rl resultList) GetValues(values []reflect.Value) []reflect.Value { + result := make([]reflect.Value, 0) + for i, v := range values { + if resultIdx := rl.resultIndexes[i]; resultIdx >= 0 { + result = append(result, v) + } + } + return result +} + // resultSingle is an explicit value produced by a constructor, optionally // with a name. // diff --git a/scope.go b/scope.go index cc454509..844d0aaa 100644 --- a/scope.go +++ b/scope.go @@ -92,7 +92,7 @@ type Scope struct { childScopes []*Scope // Callback to execute for each value that is constructed - callback ProvidedCallback + callback Callback } func newScope() *Scope { From 4993f6bab12229cb839a308af889a67f13001aee Mon Sep 17 00:00:00 2001 From: Michelle Laurenti Date: Wed, 6 Mar 2024 14:56:55 +0100 Subject: [PATCH 3/8] handle dig.Out in value callback --- constructor.go | 2 +- decorate.go | 2 +- dig_test.go | 32 +++++++++++++++++++++++++++----- result.go | 27 +++++++++++++++++++++++++-- 4 files changed, 54 insertions(+), 9 deletions(-) diff --git a/constructor.go b/constructor.go index 2043bc6d..e3274464 100644 --- a/constructor.go +++ b/constructor.go @@ -196,7 +196,7 @@ func (n *constructorNode) Call(c containerStore) (err error) { // container. receiver.Commit(n.s) - values = n.resultList.GetValues(results) + values = n.resultList.Values(results) if n.s.callback != nil { n.s.callback(CallbackInfo{ diff --git a/decorate.go b/decorate.go index f935ae2e..ff7af7e6 100644 --- a/decorate.go +++ b/decorate.go @@ -149,7 +149,7 @@ func (n *decoratorNode) Call(s containerStore) (err error) { if err = n.results.ExtractList(n.s, true /* decorated */, results); err != nil { return err } - values = n.results.GetValues(results) + values = n.results.Values(results) if n.s.callback != nil { n.s.callback(CallbackInfo{ diff --git a/dig_test.go b/dig_test.go index 60d01539..e0b32780 100644 --- a/dig_test.go +++ b/dig_test.go @@ -1859,13 +1859,35 @@ func TestProvidedCallback(t *testing.T) { assert.False(t, containerCallbackCalled) }) - t.Run("values are passed in the correct order", func(t *testing.T) { - }) + t.Run("object values are exploded into single values", func(t *testing.T) { + var containerCallbackCalled bool - t.Run("values with the As option are passed as the original type", func(t *testing.T) { - }) + c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { + assert.NotEmpty(t, ci.Name) + assert.Nil(t, ci.Error) + assert.Len(t, ci.Values, 2) + assert.EqualValues(t, "five", ci.Values[0].String()) + assert.EqualValues(t, 5, ci.Values[1].Int()) + containerCallbackCalled = true + })) + + type out struct { + dig.Out + + String string + Int int + } + + c.RequireProvide(func() (out, error) { + return out{String: "five", Int: 5}, nil + }) - t.Run("object values are passed as objects", func(t *testing.T) { + c.RequireInvoke(func(s string, i int) { + assert.Equal(t, "five", s) + assert.Equal(t, 5, i) + }) + + assert.True(t, containerCallbackCalled) }) } diff --git a/result.go b/result.go index 192f47ae..c6a80e56 100644 --- a/result.go +++ b/result.go @@ -45,6 +45,8 @@ type result interface { // This MAY panic if the result does not consume a single value. Extract(containerWriter, bool, reflect.Value) + GetValues(reflect.Value) []reflect.Value + // DotResult returns a slice of dot.Result(s). DotResult() []*dot.Result } @@ -259,11 +261,16 @@ func (rl resultList) ExtractList(cw containerWriter, decorated bool, values []re return nil } -func (rl resultList) GetValues(values []reflect.Value) []reflect.Value { +func (rl resultList) GetValues(values reflect.Value) []reflect.Value { + digerror.BugPanicf("resultList.GetValues() must never be called") + panic("") // Unreachable, as BugPanicf above will panic. +} + +func (rl resultList) Values(values []reflect.Value) []reflect.Value { result := make([]reflect.Value, 0) for i, v := range values { if resultIdx := rl.resultIndexes[i]; resultIdx >= 0 { - result = append(result, v) + result = append(result, rl.Results[resultIdx].GetValues(v)...) } } return result @@ -346,6 +353,10 @@ func (rs resultSingle) Extract(cw containerWriter, decorated bool, v reflect.Val } } +func (rs resultSingle) GetValues(v reflect.Value) []reflect.Value { + return []reflect.Value{v} +} + // resultObject is a dig.Out struct where each field is another result. // // This object is not added to the graph. Its fields are interpreted as @@ -398,6 +409,14 @@ func (ro resultObject) Extract(cw containerWriter, decorated bool, v reflect.Val } } +func (ro resultObject) GetValues(v reflect.Value) []reflect.Value { + res := make([]reflect.Value, 0) + for _, f := range ro.Fields { + res = append(res, v.Field(f.FieldIndex)) + } + return res +} + // resultObjectField is a single field inside a dig.Out struct. type resultObjectField struct { // Name of the field in the struct. @@ -543,3 +562,7 @@ func (rt resultGrouped) Extract(cw containerWriter, decorated bool, v reflect.Va cw.submitGroupedValue(rt.Group, rt.Type, v.Index(i)) } } + +func (rt resultGrouped) GetValues(v reflect.Value) []reflect.Value { + return []reflect.Value{v} +} From a21295de568e9c8d329eaf225ef8dbe5aa4d0ced Mon Sep 17 00:00:00 2001 From: Michelle Laurenti Date: Wed, 6 Mar 2024 14:59:07 +0100 Subject: [PATCH 4/8] update comments --- callback.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/callback.go b/callback.go index 3e547509..6067ff5d 100644 --- a/callback.go +++ b/callback.go @@ -35,7 +35,8 @@ type CallbackInfo struct { // this will be set to a [PanicError] when the function panics. Error error - // TODO + // Values contains all values constructed by the [Callback]'s + // associated function. Values []reflect.Value } From 6b4ddf63ba78b8faa2db2ed53cdf5bbf49b0ddd7 Mon Sep 17 00:00:00 2001 From: Michelle Laurenti Date: Wed, 6 Mar 2024 15:18:47 +0100 Subject: [PATCH 5/8] comment new result method --- result.go | 1 + 1 file changed, 1 insertion(+) diff --git a/result.go b/result.go index c6a80e56..22525113 100644 --- a/result.go +++ b/result.go @@ -45,6 +45,7 @@ type result interface { // This MAY panic if the result does not consume a single value. Extract(containerWriter, bool, reflect.Value) + // GetValues returns all values contained in a result. GetValues(reflect.Value) []reflect.Value // DotResult returns a slice of dot.Result(s). From cd7d216f7b4d46a83d55def2f885eaa84e604c2b Mon Sep 17 00:00:00 2001 From: Michelle Laurenti Date: Thu, 7 Mar 2024 08:05:32 +0100 Subject: [PATCH 6/8] remove container option --- callback.go | 17 --- constructor.go | 8 - decorate.go | 8 - dig_test.go | 397 ++++++++++++++++++++++--------------------------- scope.go | 4 - 5 files changed, 177 insertions(+), 257 deletions(-) diff --git a/callback.go b/callback.go index 6067ff5d..451411c0 100644 --- a/callback.go +++ b/callback.go @@ -95,18 +95,6 @@ func WithDecoratorCallback(callback Callback) DecorateOption { } } -// WithContainerCallback returns an [Option] which has Dig call -// the passed in [Callback] each time a new value -// is constructed. -// -// See [CallbackInfo] for more info on the information passed -// to the [Callback]. -func WithContainerCallback(callback Callback) Option { - return withCallbackOption{ - callback: callback, - } -} - type withCallbackOption struct { callback Callback } @@ -114,7 +102,6 @@ type withCallbackOption struct { var ( _ ProvideOption = withCallbackOption{} _ DecorateOption = withCallbackOption{} - _ Option = withCallbackOption{} ) func (o withCallbackOption) applyProvideOption(po *provideOptions) { @@ -124,7 +111,3 @@ func (o withCallbackOption) applyProvideOption(po *provideOptions) { func (o withCallbackOption) apply(do *decorateOptions) { do.Callback = o.callback } - -func (o withCallbackOption) applyOption(c *Container) { - c.scope.callback = o.callback -} diff --git a/constructor.go b/constructor.go index e3274464..7e75f8bf 100644 --- a/constructor.go +++ b/constructor.go @@ -198,14 +198,6 @@ func (n *constructorNode) Call(c containerStore) (err error) { values = n.resultList.Values(results) - if n.s.callback != nil { - n.s.callback(CallbackInfo{ - Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name), - Error: err, - Values: values, - }) - } - n.called = true return nil } diff --git a/decorate.go b/decorate.go index ff7af7e6..80d37421 100644 --- a/decorate.go +++ b/decorate.go @@ -151,14 +151,6 @@ func (n *decoratorNode) Call(s containerStore) (err error) { } values = n.results.Values(results) - if n.s.callback != nil { - n.s.callback(CallbackInfo{ - Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name), - Error: err, - Values: values, - }) - } - n.state = decoratorCalled return nil } diff --git a/dig_test.go b/dig_test.go index e0b32780..f74a7cf4 100644 --- a/dig_test.go +++ b/dig_test.go @@ -1693,229 +1693,14 @@ type providedStruct struct{} func (providedStruct) Start() {} -func TestProvidedCallback(t *testing.T) { - t.Run("works with primitives", func(t *testing.T) { - var containerCallbackCalled bool - - c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { - assert.Equal(t, "go.uber.org/dig_test.giveInt", ci.Name) - assert.Nil(t, ci.Error) - assert.Len(t, ci.Values, 1) - assert.True(t, ci.Values[0].CanInt()) - assert.EqualValues(t, 5, ci.Values[0].Int()) - containerCallbackCalled = true - })) - - c.RequireProvide(giveInt) - - c.RequireInvoke(func(a int) { - assert.Equal(t, 5, a) - }) - - assert.True(t, containerCallbackCalled) - }) - - t.Run("works with scopes", func(t *testing.T) { - var containerCallbackCalled bool - - c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { - assert.Equal(t, "go.uber.org/dig_test.giveInt", ci.Name) - assert.Nil(t, ci.Error) - assert.Len(t, ci.Values, 1) - assert.True(t, ci.Values[0].CanInt()) - assert.EqualValues(t, 5, ci.Values[0].Int()) - containerCallbackCalled = true - })) - - s := c.Scope("test") - - s.RequireProvide(giveInt) - - s.RequireInvoke(func(a int) { - assert.Equal(t, 5, a) - }) - - assert.True(t, containerCallbackCalled) - }) - - t.Run("works with value groups", func(t *testing.T) { - var containerCallbackCalledTimes int - - c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { - assert.NotEmpty(t, ci.Name) - assert.Nil(t, ci.Error) - assert.Len(t, ci.Values, 1) - assert.True(t, ci.Values[0].CanInt()) - assert.True(t, ci.Values[0].Int() == 5 || ci.Values[0].Int() == 6) - containerCallbackCalledTimes++ - })) - - c.RequireProvide(giveInt, dig.Group("test")) - c.RequireProvide(func() int { return 6 }, dig.Group("test")) - - type params struct { - dig.In - - Value []int `group:"test"` - } - - c.RequireInvoke(func(a params) { - assert.ElementsMatch(t, []int{5, 6}, a.Value) - }) - - assert.Equal(t, 2, containerCallbackCalledTimes) - }) - - t.Run("works with interfaces", func(t *testing.T) { - var containerCallbackCalled bool - - var gave providedInterface - - c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { - assert.NotEmpty(t, ci.Name) - assert.Nil(t, ci.Error) - assert.Len(t, ci.Values, 1) - assert.True(t, ci.Values[0].CanInterface()) - _, ok := ci.Values[0].Interface().(providedInterface) - assert.True(t, ok) - containerCallbackCalled = true - })) - - c.RequireProvide(func() providedInterface { - gave = &providedStruct{} - return gave - }) - - c.RequireInvoke(func(got providedInterface) { - assert.Equal(t, gave, got) - }) - - assert.True(t, containerCallbackCalled) - }) - - t.Run("works with provider returning multiple values", func(t *testing.T) { - var containerCallbackCalled bool - - c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { - assert.NotEmpty(t, ci.Name) - assert.Nil(t, ci.Error) - assert.Len(t, ci.Values, 2) - assert.EqualValues(t, "five", ci.Values[0].String()) - assert.EqualValues(t, 5, ci.Values[1].Int()) - containerCallbackCalled = true - })) - - c.RequireProvide(func() (string, int) { - return "five", 5 - }) - - c.RequireInvoke(func(s string, i int) { - assert.Equal(t, "five", s) - assert.Equal(t, 5, i) - }) - - assert.True(t, containerCallbackCalled) - }) - - t.Run("does not receive error with providers that can fail", func(t *testing.T) { - var containerCallbackCalled bool - - c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { - assert.NotEmpty(t, ci.Name) - assert.Nil(t, ci.Error) - assert.Len(t, ci.Values, 2) - assert.EqualValues(t, "five", ci.Values[0].String()) - assert.EqualValues(t, 5, ci.Values[1].Int()) - containerCallbackCalled = true - })) - - c.RequireProvide(func() (string, int, error) { - return "five", 5, nil - }) - - c.RequireInvoke(func(s string, i int) { - assert.Equal(t, "five", s) - assert.Equal(t, 5, i) - }) - - assert.True(t, containerCallbackCalled) - }) - - t.Run("callback not invoked on provider error", func(t *testing.T) { - var containerCallbackCalled bool - - c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { - containerCallbackCalled = true - })) - - c.RequireProvide(func() (string, int, error) { - return "", 0, fmt.Errorf("failed") - }) - - err := c.Invoke(func(_ string, _ int) {}) - - assert.NotNil(t, err) - - assert.False(t, containerCallbackCalled) - }) - - t.Run("object values are exploded into single values", func(t *testing.T) { - var containerCallbackCalled bool - - c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { - assert.NotEmpty(t, ci.Name) - assert.Nil(t, ci.Error) - assert.Len(t, ci.Values, 2) - assert.EqualValues(t, "five", ci.Values[0].String()) - assert.EqualValues(t, 5, ci.Values[1].Int()) - containerCallbackCalled = true - })) - - type out struct { - dig.Out - - String string - Int int - } - - c.RequireProvide(func() (out, error) { - return out{String: "five", Int: 5}, nil - }) - - c.RequireInvoke(func(s string, i int) { - assert.Equal(t, "five", s) - assert.Equal(t, 5, i) - }) - - assert.True(t, containerCallbackCalled) - }) -} - func TestCallback(t *testing.T) { t.Run("no errors", func(t *testing.T) { var ( - provideCallbackCalled bool - decorateCallbackCalled bool - containerCallbackCalledTimes int + provideCallbackCalled bool + decorateCallbackCalled bool ) - c := digtest.New(t, dig.WithContainerCallback(func(ci dig.CallbackInfo) { - containerCallbackCalledTimes++ - switch containerCallbackCalledTimes { - case 1: - assert.Equal(t, "go.uber.org/dig_test.giveInt", ci.Name) - assert.NoError(t, ci.Error) - assert.Len(t, ci.Values, 1) - assert.True(t, ci.Values[0].CanInt()) - assert.EqualValues(t, 5, ci.Values[0].Int()) - case 2: - assert.Equal(t, "go.uber.org/dig_test.TestCallback.func1.3", ci.Name) - assert.NoError(t, ci.Error) - assert.Len(t, ci.Values, 1) - assert.True(t, ci.Values[0].CanInt()) - assert.EqualValues(t, 10, ci.Values[0].Int()) - } - })) + c := digtest.New(t) c.RequireProvide( giveInt, dig.WithProviderCallback(func(ci dig.CallbackInfo) { @@ -1930,7 +1715,7 @@ func TestCallback(t *testing.T) { c.RequireDecorate( func(a int) int { return a + 5 }, dig.WithDecoratorCallback(func(ci dig.CallbackInfo) { - assert.Equal(t, "go.uber.org/dig_test.TestCallback.func1.3", ci.Name) + assert.Equal(t, "go.uber.org/dig_test.TestCallback.func1.2", ci.Name) assert.NoError(t, ci.Error) assert.Len(t, ci.Values, 1) assert.True(t, ci.Values[0].CanInt()) @@ -1943,7 +1728,6 @@ func TestCallback(t *testing.T) { assert.True(t, provideCallbackCalled) assert.True(t, decorateCallbackCalled) - assert.Equal(t, 2, containerCallbackCalledTimes) }) t.Run("provide error", func(t *testing.T) { @@ -2028,6 +1812,179 @@ func TestCallback(t *testing.T) { c.Invoke(func(int) {}) assert.True(t, called) }) + t.Run("callback receives primitives", func(t *testing.T) { + var providerCallbackCalled bool + + c := digtest.New(t) + + c.RequireProvide(giveInt, dig.WithProviderCallback(func(ci dig.CallbackInfo) { + assert.Equal(t, "go.uber.org/dig_test.giveInt", ci.Name) + assert.Nil(t, ci.Error) + assert.Len(t, ci.Values, 1) + assert.True(t, ci.Values[0].CanInt()) + assert.EqualValues(t, 5, ci.Values[0].Int()) + providerCallbackCalled = true + })) + + c.RequireInvoke(func(a int) { + assert.Equal(t, 5, a) + }) + + assert.True(t, providerCallbackCalled) + }) + + t.Run("callback works with value groups", func(t *testing.T) { + var providerCallbackCalledTimes int + + c := digtest.New(t) + + c.RequireProvide(giveInt, dig.Group("test"), dig.WithProviderCallback(func(ci dig.CallbackInfo) { + assert.NotEmpty(t, ci.Name) + assert.Nil(t, ci.Error) + assert.Len(t, ci.Values, 1) + assert.True(t, ci.Values[0].CanInt()) + assert.EqualValues(t, 5, ci.Values[0].Int()) + providerCallbackCalledTimes++ + })) + c.RequireProvide(func() int { return 6 }, dig.Group("test"), dig.WithProviderCallback(func(ci dig.CallbackInfo) { + assert.NotEmpty(t, ci.Name) + assert.Nil(t, ci.Error) + assert.Len(t, ci.Values, 1) + assert.True(t, ci.Values[0].CanInt()) + assert.EqualValues(t, 6, ci.Values[0].Int()) + providerCallbackCalledTimes++ + })) + + type params struct { + dig.In + + Value []int `group:"test"` + } + + c.RequireInvoke(func(a params) { + assert.ElementsMatch(t, []int{5, 6}, a.Value) + }) + + assert.Equal(t, 2, providerCallbackCalledTimes) + }) + + t.Run("callback works with interfaces", func(t *testing.T) { + var providerCallbackCalled bool + + var gave providedInterface + + c := digtest.New(t) + + c.RequireProvide( + func() providedInterface { + gave = &providedStruct{} + return gave + }, + dig.WithProviderCallback(func(ci dig.CallbackInfo) { + assert.NotEmpty(t, ci.Name) + assert.Nil(t, ci.Error) + assert.Len(t, ci.Values, 1) + assert.True(t, ci.Values[0].CanInterface()) + _, ok := ci.Values[0].Interface().(providedInterface) + assert.True(t, ok) + providerCallbackCalled = true + }), + ) + + c.RequireInvoke(func(got providedInterface) { + assert.Equal(t, gave, got) + }) + + assert.True(t, providerCallbackCalled) + }) + + t.Run("callback works with provider returning multiple values", func(t *testing.T) { + var providerCallbackCalled bool + + c := digtest.New(t) + + c.RequireProvide( + func() (string, int) { + return "five", 5 + }, + dig.WithProviderCallback(func(ci dig.CallbackInfo) { + assert.NotEmpty(t, ci.Name) + assert.Nil(t, ci.Error) + assert.Len(t, ci.Values, 2) + assert.EqualValues(t, "five", ci.Values[0].String()) + assert.EqualValues(t, 5, ci.Values[1].Int()) + providerCallbackCalled = true + }), + ) + + c.RequireInvoke(func(s string, i int) { + assert.Equal(t, "five", s) + assert.Equal(t, 5, i) + }) + + assert.True(t, providerCallbackCalled) + }) + + t.Run("callback does not receive nil error value with providers that can fail", func(t *testing.T) { + var providerCallbackCalled bool + + c := digtest.New(t) + + c.RequireProvide( + func() (string, int, error) { + return "five", 5, nil + }, + dig.WithProviderCallback(func(ci dig.CallbackInfo) { + assert.NotEmpty(t, ci.Name) + assert.Nil(t, ci.Error) + assert.Len(t, ci.Values, 2) + assert.EqualValues(t, "five", ci.Values[0].String()) + assert.EqualValues(t, 5, ci.Values[1].Int()) + providerCallbackCalled = true + }), + ) + + c.RequireInvoke(func(s string, i int) { + assert.Equal(t, "five", s) + assert.Equal(t, 5, i) + }) + + assert.True(t, providerCallbackCalled) + }) + + t.Run("object values are exploded into single values", func(t *testing.T) { + var containerCallbackCalled bool + + c := digtest.New(t) + + type out struct { + dig.Out + + String string + Int int + } + + c.RequireProvide( + func() (out, error) { + return out{String: "five", Int: 5}, nil + }, + dig.WithProviderCallback(func(ci dig.CallbackInfo) { + assert.NotEmpty(t, ci.Name) + assert.Nil(t, ci.Error) + assert.Len(t, ci.Values, 2) + assert.EqualValues(t, "five", ci.Values[0].String()) + assert.EqualValues(t, 5, ci.Values[1].Int()) + containerCallbackCalled = true + }), + ) + + c.RequireInvoke(func(s string, i int) { + assert.Equal(t, "five", s) + assert.Equal(t, 5, i) + }) + + assert.True(t, containerCallbackCalled) + }) } func TestProvideConstructorErrors(t *testing.T) { diff --git a/scope.go b/scope.go index 844d0aaa..d5478aca 100644 --- a/scope.go +++ b/scope.go @@ -90,9 +90,6 @@ type Scope struct { // All the child scopes of this Scope. childScopes []*Scope - - // Callback to execute for each value that is constructed - callback Callback } func newScope() *Scope { @@ -122,7 +119,6 @@ func (s *Scope) Scope(name string, opts ...ScopeOption) *Scope { child.invokerFn = s.invokerFn child.deferAcyclicVerification = s.deferAcyclicVerification child.recoverFromPanics = s.recoverFromPanics - child.callback = s.callback // child copies the parent's graph nodes. for _, node := range s.gh.nodes { From d0260788e132526aabb6ba05acc8b3a71808cef8 Mon Sep 17 00:00:00 2001 From: Michelle Laurenti Date: Mon, 11 Mar 2024 10:13:57 +0100 Subject: [PATCH 7/8] expand callback values docs --- callback.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/callback.go b/callback.go index 451411c0..106a4591 100644 --- a/callback.go +++ b/callback.go @@ -36,7 +36,8 @@ type CallbackInfo struct { Error error // Values contains all values constructed by the [Callback]'s - // associated function. + // associated function. These are the actual values inside the container: + // modifying them may result in undefined behaviour. Values []reflect.Value } From 16c63d68f0cff142da3c6d70f4a0fcb287649450 Mon Sep 17 00:00:00 2001 From: Michelle Date: Mon, 11 Mar 2024 10:16:03 +0100 Subject: [PATCH 8/8] specify result values array size on make Co-authored-by: Jacob Oaks --- result.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/result.go b/result.go index 22525113..6be24e26 100644 --- a/result.go +++ b/result.go @@ -411,9 +411,9 @@ func (ro resultObject) Extract(cw containerWriter, decorated bool, v reflect.Val } func (ro resultObject) GetValues(v reflect.Value) []reflect.Value { - res := make([]reflect.Value, 0) - for _, f := range ro.Fields { - res = append(res, v.Field(f.FieldIndex)) + res := make([]reflect.Value, len(ro.Fields)) + for i, f := range ro.Fields { + res[i] = v.Field(f.FieldIndex) } return res }