Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add suggestions for slices and array element missing types #390

Merged
merged 3 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions dig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3255,6 +3255,11 @@ func testInvokeFailures(t *testing.T, dryRun bool) {

A `name:"hello"`
}
type outPointerA struct {
dig.Out

A *A `name:"hello"`
}

cases := []struct {
name string
Expand Down Expand Up @@ -3294,6 +3299,190 @@ func testInvokeFailures(t *testing.T, dryRun bool) {
`\*dig_test.A\[name="hello"\] \(did you mean (to use )?dig_test.A\[name="hello"\]\?\)`,
},
},
{
name: "named value missing, pointer present",
provide: func() outPointerA { return outPointerA{A: &A{}} },
invoke: func(struct {
dig.In

A `name:"hello"`
}) {
},
errContains: []string{
`missing type:`,
`dig_test.A\[name="hello"\] \(did you mean (to use )?\*dig_test.A\[name="hello"\]\?\)`,
},
},
}

for _, tc := range cases {
c := digtest.New(t, dig.DryRun(dryRun))
t.Run(tc.name, func(t *testing.T) {
c.RequireProvide(tc.provide)

err := c.Invoke(tc.invoke)
require.Error(t, err)

lines := append([]string{
`dig_test.go:\d+`, // file:line
}, tc.errContains...)
dig.AssertErrorMatches(t, err,
`missing dependencies for function "go.uber.org/dig_test".testInvokeFailures.\S+`,
lines...)
})
}
})

t.Run("requesting a slice of values or pointers when the other is present", func(t *testing.T) {
type A struct{}
type outA struct {
dig.Out

A []A `name:"hello"`
}
type outPointerA struct {
dig.Out

A []*A `name:"hello"`
}

cases := []struct {
name string
provide interface{}
invoke interface{}
errContains []string
}{
{
name: "value slice missing, pointer slice present",
provide: func() []*A { return []*A{{}} },
invoke: func([]A) {},
errContains: []string{
`missing type:`,
`\[\]dig_test.A \(did you mean (to use )?\[\]\*dig_test.A\?\)`,
},
},
{
name: "pointer slice missing, value slice present",
provide: func() []A { return []A{{}} },
invoke: func([]*A) {},
errContains: []string{
`missing type:`,
`\[\]\*dig_test.A \(did you mean (to use )?\[\]dig_test.A\?\)`,
},
},
{
name: "named pointer slice missing, value slice present",
provide: func() outA { return outA{A: []A{{}}} },
invoke: func(struct {
dig.In

A []*A `name:"hello"`
}) {
},
errContains: []string{
`missing type:`,
`\[\]\*dig_test.A\[name="hello"\] \(did you mean (to use )?\[\]dig_test.A\[name="hello"\]\?\)`,
},
},
{
name: "named value slice missing, pointer slice present",
provide: func() outPointerA { return outPointerA{A: []*A{{}}} },
invoke: func(struct {
dig.In

A []A `name:"hello"`
}) {
},
errContains: []string{
`missing type:`,
`\[\]dig_test.A\[name="hello"\] \(did you mean (to use )?\[\]\*dig_test.A\[name="hello"\]\?\)`,
},
},
}

for _, tc := range cases {
c := digtest.New(t, dig.DryRun(dryRun))
t.Run(tc.name, func(t *testing.T) {
c.RequireProvide(tc.provide)

err := c.Invoke(tc.invoke)
require.Error(t, err)

lines := append([]string{
`dig_test.go:\d+`, // file:line
}, tc.errContains...)
dig.AssertErrorMatches(t, err,
`missing dependencies for function "go.uber.org/dig_test".testInvokeFailures.\S+`,
lines...)
})
}
})

t.Run("requesting an array of values or pointers when the other is present", func(t *testing.T) {
type A struct{}
type outA struct {
dig.Out

A [7]A `name:"hello"`
}
type outPointerA struct {
dig.Out

A [7]*A `name:"hello"`
}

cases := []struct {
name string
provide interface{}
invoke interface{}
errContains []string
}{
{
name: "value slice missing, pointer slice present",
provide: func() [7]*A { return [7]*A{{}} },
invoke: func([7]A) {},
errContains: []string{
`missing type:`,
`\[7\]dig_test.A \(did you mean (to use )?\[7\]\*dig_test.A\?\)`,
},
},
{
name: "pointer slice missing, value slice present",
provide: func() [7]A { return [7]A{{}} },
invoke: func([7]*A) {},
errContains: []string{
`missing type:`,
`\[7\]\*dig_test.A \(did you mean (to use )?\[7\]dig_test.A\?\)`,
},
},
{
name: "named pointer slice missing, value slice present",
provide: func() outA { return outA{A: [7]A{{}}} },
invoke: func(struct {
dig.In

A [7]*A `name:"hello"`
}) {
},
errContains: []string{
`missing type:`,
`\[7\]\*dig_test.A\[name="hello"\] \(did you mean (to use )?\[7\]dig_test.A\[name="hello"\]\?\)`,
},
},
{
name: "named value slice missing, pointer slice present",
provide: func() outPointerA { return outPointerA{A: [7]*A{{}}} },
invoke: func(struct {
dig.In

A [7]A `name:"hello"`
}) {
},
errContains: []string{
`missing type:`,
`\[7\]dig_test.A\[name="hello"\] \(did you mean (to use )?\[7\]\*dig_test.A\[name="hello"\]\?\)`,
},
},
}

for _, tc := range cases {
Expand Down
22 changes: 22 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,28 @@ func newErrMissingTypes(c containerStore, k key) errMissingTypes {
suggestions = append(suggestions, k.t.Elem())
}

if k.t.Kind() == reflect.Slice {
// Maybe the user meant a slice of pointers while we have the slice of elements
suggestions = append(suggestions, reflect.SliceOf(reflect.PtrTo(k.t.Elem())))

// Maybe the user meant a slice of elements while we have the slice of pointers
sliceElement := k.t.Elem()
if sliceElement.Kind() == reflect.Ptr {
suggestions = append(suggestions, reflect.SliceOf(sliceElement.Elem()))
}
}

if k.t.Kind() == reflect.Array {
// Maybe the user meant an array of pointers while we have the array of elements
suggestions = append(suggestions, reflect.ArrayOf(k.t.Len(), reflect.PtrTo(k.t.Elem())))

// Maybe the user meant an array of elements while we have the array of pointers
arrayElement := k.t.Elem()
if arrayElement.Kind() == reflect.Ptr {
suggestions = append(suggestions, reflect.ArrayOf(k.t.Len(), arrayElement.Elem()))
}
}

knownTypes := c.knownTypes()
if k.t.Kind() == reflect.Interface {
// Maybe we have an implementation of the interface.
Expand Down
44 changes: 44 additions & 0 deletions error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,50 @@ func TestMissingTypeFormatting(t *testing.T) {
wantV: "dig.type1 (did you mean *dig.type1, or dig.someInterface?)",
wantPlusV: "dig.type1 (did you mean to use one of *dig.type1, or dig.someInterface?)",
},
{
desc: "one suggestion for a slice of elements",
give: missingType{
Key: key{t: reflect.TypeOf([]type1{})},
suggestions: []key{
{t: reflect.TypeOf([]*type1{})},
},
},
wantV: "[]dig.type1 (did you mean []*dig.type1?)",
wantPlusV: "[]dig.type1 (did you mean to use []*dig.type1?)",
},
{
desc: "one suggestion for an array of elements",
give: missingType{
Key: key{t: reflect.TypeOf([4]type1{})},
suggestions: []key{
{t: reflect.TypeOf([4]*type1{})},
},
},
wantV: "[4]dig.type1 (did you mean [4]*dig.type1?)",
wantPlusV: "[4]dig.type1 (did you mean to use [4]*dig.type1?)",
},
{
desc: "one suggestion for a slice of pointers",
give: missingType{
Key: key{t: reflect.TypeOf([]*type1{})},
suggestions: []key{
{t: reflect.TypeOf([]type1{})},
},
},
wantV: "[]*dig.type1 (did you mean []dig.type1?)",
wantPlusV: "[]*dig.type1 (did you mean to use []dig.type1?)",
},
{
desc: "one suggestion for an array of pointers",
give: missingType{
Key: key{t: reflect.TypeOf([4]*type1{})},
suggestions: []key{
{t: reflect.TypeOf([4]type1{})},
},
},
wantV: "[4]*dig.type1 (did you mean [4]dig.type1?)",
wantPlusV: "[4]*dig.type1 (did you mean to use [4]dig.type1?)",
},
}

for _, tt := range tests {
Expand Down
Loading