Skip to content

Commit

Permalink
Add suggestions for slices and array element missing types (#390)
Browse files Browse the repository at this point in the history
* Add suggestions for slices and array element missing types

* Add more test cases

* fix: add test coverage for slice and array of values and pointers suggestions
  • Loading branch information
paullen authored Aug 10, 2023
1 parent a5e352c commit fc364ac
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 0 deletions.
189 changes: 189 additions & 0 deletions dig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3300,6 +3300,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 @@ -3339,6 +3344,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

0 comments on commit fc364ac

Please sign in to comment.