Repository: google/wire (go)

Issue:
wire: wire.FieldsOf() to inject the values from fields of a struct
Pretty new to wire, so forgive me if I am asking the wrong questions.

As far as I understand, wire.Value() allows to specify a value to be injected. If I have a struct, the fields of which must be injected rather than the value of the struct itself, would I have to use wire.Value() for each field of the struct?

Would it make sense to add something like wire.FieldValues(), which injects the fields of the struct that is specified rather than the struct itself?

Pull request description:
add FieldsOf to inject fields of a struct directly
Fixes #32 

Patch:
diff --git a/docs/guide.md b/docs/guide.md
index 35589d0..f493f55 100644
--- a/docs/guide.md
+++ b/docs/guide.md
@@ -198,21 +198,29 @@ type Fooer interface {
     Foo() string
 }
 
-type Bar string
+type MyFooer string
 
-func (b *Bar) Foo() string {
+func (b *MyFooer) Foo() string {
     return string(*b)
 }
 
-func ProvideBar() *Bar {
-    b := new(Bar)
+func provideMyFooer() *MyFooer {
+    b := new(MyFooer)
     *b = "Hello, World!"
     return b
 }
 
-var BarFooer = wire.NewSet(
-    ProvideBar,
-    wire.Bind(new(Fooer), new(Bar)))
+type Bar string
+
+func provideBar(f Fooer) string {
+    // f will be a *MyFooer.
+    return f.Foo()
+}
+
+var Set = wire.NewSet(
+    provideMyFooer,
+    wire.Bind((*Fooer)(nil), (*MyFooer)(nil)),
+    provideBar)
 ```
 
 The first argument to `wire.Bind` is a pointer to a value of the desired
diff --git a/internal/wire/analyze.go b/internal/wire/analyze.go
index 72fd8a6..99ee919 100644
--- a/internal/wire/analyze.go
+++ b/internal/wire/analyze.go
@@ -32,6 +32,7 @@ const (
 	funcProviderCall callKind = iota
 	structProvider
 	valueExpr
+	selectorExpr
 )
 
 // A call represents a step of an injector function.  It may be either a
@@ -44,17 +45,21 @@ type call struct {
 	// out is the type this step produces.
 	out types.Type
 
-	// pkg and name identify the provider to call for kind ==
-	// funcProviderCall or the type to construct for kind ==
-	// structProvider.
+	// pkg and name identify one of the following:
+	// 1) the provider to call for kind == funcProviderCall;
+	// 2) the type to construct for kind == structProvider;
+	// 3) the name to select for kind == selectorExpr.
 	pkg  *types.Package
 	name string
 
-	// args is a list of arguments to call the provider with.  Each element is:
-	// a) one of the givens (args[i] < len(given)), or
+	// args is a list of arguments to call the provider with. Each element is:
+	// a) one of the givens (args[i] < len(given)),
 	// b) the result of a previous provider call (args[i] >= len(given))
 	//
 	// This will be nil for kind == valueExpr.
+	//
+	// If kind == selectorExpr, then the length of this slice will be 1 and the
+	// "argument" will be the value to access fields from.
 	args []int
 
 	// varargs is true if the provider function is variadic.
@@ -207,6 +212,29 @@ dfs:
 				valueExpr:     v.expr,
 				valueTypeInfo: v.info,
 			})
+		case pv.IsField():
+			f := pv.Field()
+			if index.At(f.Parent) == nil {
+				// Fields have one dependency which is the parent struct. Make
+				// sure to visit it first if it is not already visited.
+				stk = append(stk, curr, frame{t: f.Parent, from: curr.t, up: &curr})
+				continue
+			}
+			index.Set(curr.t, given.Len()+len(calls))
+			v := index.At(f.Parent)
+			if v == errAbort {
+				index.Set(curr.t, errAbort)
+				continue dfs
+			}
+			// Use the args[0] to store the position of the parent struct.
+			args := []int{v.(int)}
+			calls = append(calls, call{
+				kind: selectorExpr,
+				pkg:  f.Pkg,
+				name: f.Name,
+				out:  curr.t,
+				args: args,
+			})
 		default:
 			panic("unknown return value from ProviderSet.For")
 		}
@@ -275,11 +303,24 @@ func verifyArgsUsed(set *ProviderSet, used []*providerSetSrc) []error {
 			errs = append(errs, fmt.Errorf("unused interface binding to type %s", types.TypeString(b.Iface, nil)))
 		}
 	}
+	for _, f := range set.Fields {
+		found := false
+		for _, u := range used {
+			if u.Field == f {
+				found = true
+				break
+			}
+		}
+		if !found {
+			errs = append(errs, fmt.Errorf("unused field %q.%s", f.Parent, f.Name))
+		}
+	}
 	return errs
 }
 
-// buildProviderMap creates the providerMap and srcMap fields for a given provider set.
-// The given provider set's providerMap and srcMap fields are ignored.
+// buildProviderMap creates the providerMap and srcMap fields for a given
+// provider set. The given provider set's providerMap and srcMap fields are
+// ignored.
 func buildProviderMap(fset *token.FileSet, hasher typeutil.Hasher, set *ProviderSet) (*typeutil.Map, *typeutil.Map, []error) {
 	providerMap := new(typeutil.Map)
 	providerMap.SetHasher(hasher)
@@ -339,6 +380,15 @@ func buildProviderMap(fset *token.FileSet, hasher typeutil.Hasher, set *Provider
 		providerMap.Set(v.Out, &ProvidedType{t: v.Out, v: v})
 		srcMap.Set(v.Out, src)
 	}
+	for _, f := range set.Fields {
+		src := &providerSetSrc{Field: f}
+		if prevSrc := srcMap.At(f.Out); prevSrc != nil {
+			ec.add(bindingConflictError(fset, f.Out, set, src, prevSrc.(*providerSetSrc)))
+			continue
+		}
+		providerMap.Set(f.Out, &ProvidedType{t: f.Out, f: f})
+		srcMap.Set(f.Out, src)
+	}
 	if len(ec.errors) > 0 {
 		return nil, nil, ec.errors
 	}
@@ -398,38 +448,49 @@ func verifyAcyclic(providerMap *typeutil.Map, hasher typeutil.Hasher) []error {
 				continue
 			}
 			pt := x.(*ProvidedType)
-			if pt.IsValue() {
+			switch {
+			case pt.IsValue():
 				// Leaf: values do not have dependencies.
-				continue
-			}
-			if pt.IsArg() {
+			case pt.IsArg():
 				// Injector arguments do not have dependencies.
-				continue
-			}
-			if !pt.IsProvider() {
-				panic("invalid provider map value")
-			}
-			for _, arg := range pt.Provider().Args {
-				a := arg.Type
-				hasCycle := false
-				for i, b := range curr {
-					if types.Identical(a, b) {
-						sb := new(strings.Builder)
-						fmt.Fprintf(sb, "cycle for %s:\n", types.TypeString(a, nil))
-						for j := i; j < len(curr); j++ {
-							p := providerMap.At(curr[j]).(*ProvidedType).Provider()
-							fmt.Fprintf(sb, "%s (%s.%s) ->\n", types.TypeString(curr[j], nil), p.Pkg.Path(), p.Name)
-						}
-						fmt.Fprintf(sb, "%s", types.TypeString(a, nil))
-						ec.add(errors.New(sb.String()))
-						hasCycle = true
-						break
+			case pt.IsProvider() || pt.IsField():
+				var args []types.Type
+				if pt.IsProvider() {
+					for _, arg := range pt.Provider().Args {
+						args = append(args, arg.Type)
 					}
+				} else {
+					args = append(args, pt.Field().Parent)
 				}
-				if !hasCycle {
-					next := append(append([]types.Type(nil), curr...), a)
-					stk = append(stk, next)
+				for _, a := range args {
+					hasCycle := false
+					for i, b := range curr {
+						if types.Identical(a, b) {
+							sb := new(strings.Builder)
+							fmt.Fprintf(sb, "cycle for %s:\n", types.TypeString(a, nil))
+							for j := i; j < len(curr); j++ {
+								t := providerMap.At(curr[j]).(*ProvidedType)
+								if t.IsProvider() {
+									p := t.Provider()
+									fmt.Fprintf(sb, "%s (%s.%s) ->\n", types.TypeString(curr[j], nil), p.Pkg.Path(), p.Name)
+								} else {
+									p := t.Field()
+									fmt.Fprintf(sb, "%s (%s.%s) ->\n", types.TypeString(curr[j], nil), p.Parent, p.Name)
+								}
+							}
+							fmt.Fprintf(sb, "%s", types.TypeString(a, nil))
+							ec.add(errors.New(sb.String()))
+							hasCycle = true
+							break
+						}
+					}
+					if !hasCycle {
+						next := append(append([]types.Type(nil), curr...), a)
+						stk = append(stk, next)
+					}
 				}
+			default:
+				panic("invalid provider map value")
 			}
 		}
 	}
diff --git a/internal/wire/parse.go b/internal/wire/parse.go
index 812c8b5..aa3d48a 100644
--- a/internal/wire/parse.go
+++ b/internal/wire/parse.go
@@ -37,6 +37,7 @@ type providerSetSrc struct {
 	Value       *Value
 	Import      *ProviderSet
 	InjectorArg *InjectorArg
+	Field       *Field
 }
 
 // description returns a string describing the source of p, including line numbers.
@@ -63,6 +64,8 @@ func (p *providerSetSrc) description(fset *token.FileSet, typ types.Type) string
 	case p.InjectorArg != nil:
 		args := p.InjectorArg.Args
 		return fmt.Sprintf("argument %s to injector function %s (%s)", args.Tuple.At(p.InjectorArg.Index).Name(), args.Name, fset.Position(args.Pos))
+	case p.Field != nil:
+		return fmt.Sprintf("wire.FieldsOf (%s)", fset.Position(p.Field.Pos))
 	}
 	panic("providerSetSrc with no fields set")
 }
@@ -96,6 +99,7 @@ type ProviderSet struct {
 	Providers []*Provider
 	Bindings  []*IfaceBinding
 	Values    []*Value
+	Fields    []*Field
 	Imports   []*ProviderSet
 	// InjectorArgs is only filled in for wire.Build.
 	InjectorArgs *InjectorArgs
@@ -214,6 +218,21 @@ type InjectorArgs struct {
 	Pos token.Pos
 }
 
+// Field describes a list of fields from a struct.
+type Field struct {
+	// Parent is the struct or pointer to the struct that the field belongs to.
+	Parent types.Type
+	// Name is the field name.
+	Name string
+	// Pkg is the package that the struct resides in.
+	Pkg *types.Package
+	// Pos is the source position of the field declaration.
+	// defining these fields.
+	Pos token.Pos
+	// Out is the field's type.
+	Out types.Type
+}
+
 // Load finds all the provider sets in the packages that match the given
 // patterns, as well as the provider sets' transitive dependencies. It
 // may return both errors and Info. The patterns are defined by the
@@ -436,8 +455,8 @@ func newObjectCache(pkgs []*packages.Package) *objectCache {
 	return oc
 }
 
-// get converts a Go object into a Wire structure. It may return a
-// *Provider, an *IfaceBinding, a *ProviderSet, or a *Value.
+// get converts a Go object into a Wire structure. It may return a *Provider, an
+// *IfaceBinding, a *ProviderSet, a *Value, or a *Fields.
 func (oc *objectCache) get(obj types.Object) (val interface{}, errs []error) {
 	ref := objRef{
 		importPath: obj.Pkg().Path(),
@@ -493,8 +512,8 @@ func (oc *objectCache) varDecl(obj *types.Var) *ast.ValueSpec {
 	return nil
 }
 
-// processExpr converts an expression into a Wire structure. It may
-// return a *Provider, an *IfaceBinding, a *ProviderSet, or a *Value.
+// processExpr converts an expression into a Wire structure. It may return a
+// *Provider, an *IfaceBinding, a *ProviderSet, a *Value or a *Field.
 func (oc *objectCache) processExpr(info *types.Info, pkgPath string, expr ast.Expr, varName string) (interface{}, []error) {
 	exprPos := oc.fset.Position(expr.Pos())
 	expr = astutil.Unparen(expr)
@@ -531,6 +550,12 @@ func (oc *objectCache) processExpr(info *types.Info, pkgPath string, expr ast.Ex
 				return nil, []error{notePosition(exprPos, err)}
 			}
 			return v, nil
+		case "FieldsOf":
+			v, err := processFieldsOf(oc.fset, info, call)
+			if err != nil {
+				return nil, []error{notePosition(exprPos, err)}
+			}
+			return v, nil
 		default:
 			return nil, []error{notePosition(exprPos, errors.New("unknown pattern"))}
 		}
@@ -570,6 +595,8 @@ func (oc *objectCache) processNewSet(info *types.Info, pkgPath string, call *ast
 			pset.Bindings = append(pset.Bindings, item)
 		case *Value:
 			pset.Values = append(pset.Values, item)
+		case []*Field:
+			pset.Fields = append(pset.Fields, item...)
 		default:
 			panic("unknown item type")
 		}
@@ -845,6 +872,73 @@ func processInterfaceValue(fset *token.FileSet, info *types.Info, call *ast.Call
 	}, nil
 }
 
+// processFieldsOf creates a list of fields from a wire.FieldsOf call.
+func processFieldsOf(fset *token.FileSet, info *types.Info, call *ast.CallExpr) ([]*Field, error) {
+	// Assumes that call.Fun is wire.FieldsOf.
+
+	if len(call.Args) < 2 {
+		return nil, notePosition(fset.Position(call.Pos()),
+			errors.New("call to FieldsOf must specify fields to be extracted"))
+	}
+	const firstArgReqFormat = "first argument to FieldsOf must be a pointer to a struct or a pointer to a pointer to a struct; found %s"
+	structType := info.TypeOf(call.Args[0])
+	structPtr, ok := structType.(*types.Pointer)
+	if !ok {
+		return nil, notePosition(fset.Position(call.Pos()),
+			fmt.Errorf(firstArgReqFormat, types.TypeString(structType, nil)))
+	}
+
+	var struc *types.Struct
+	switch t := structPtr.Elem().Underlying().(type) {
+	case *types.Pointer:
+		struc, ok = t.Elem().Underlying().(*types.Struct)
+		if !ok {
+			return nil, notePosition(fset.Position(call.Pos()),
+				fmt.Errorf(firstArgReqFormat, types.TypeString(struc, nil)))
+		}
+	case *types.Struct:
+		struc = t
+	default:
+		return nil, notePosition(fset.Position(call.Pos()),
+			fmt.Errorf(firstArgReqFormat, types.TypeString(t, nil)))
+	}
+	if struc.NumFields() < len(call.Args)-1 {
+		return nil, notePosition(fset.Position(call.Pos()),
+			fmt.Errorf("fields number exceeds the number available in the struct which has %d fields", struc.NumFields()))
+	}
+
+	fields := make([]*Field, 0, len(call.Args)-1)
+	for i := 1; i < len(call.Args); i++ {
+		v, err := checkField(call.Args[i], struc)
+		if err != nil {
+			return nil, notePosition(fset.Position(call.Pos()), err)
+		}
+		fields = append(fields, &Field{
+			Parent: structPtr.Elem(),
+			Name:   v.Name(),
+			Pkg:    v.Pkg(),
+			Pos:    v.Pos(),
+			Out:    v.Type(),
+		})
+	}
+	return fields, nil
+}
+
+// checkField reports whether f is a field of st. f should be a string with the
+// field name.
+func checkField(f ast.Expr, st *types.Struct) (*types.Var, error) {
+	b, ok := f.(*ast.BasicLit)
+	if !ok {
+		return nil, fmt.Errorf("%v must be a string with the field name", f)
+	}
+	for i := 0; i < st.NumFields(); i++ {
+		if strings.EqualFold(strconv.Quote(st.Field(i).Name()), b.Value) {
+			return st.Field(i), nil
+		}
+	}
+	return nil, fmt.Errorf("%s is not a field of %s", b.Value, st.String())
+}
+
 // findInjectorBuild returns the wire.Build call if fn is an injector template.
 // It returns nil if the function is not an injector template.
 func findInjectorBuild(info *types.Info, fn *ast.FuncDecl) (*ast.CallExpr, error) {
@@ -928,11 +1022,12 @@ type ProvidedType struct {
 	p *Provider
 	v *Value
 	a *InjectorArg
+	f *Field
 }
 
 // IsNil reports whether pt is the zero value.
 func (pt ProvidedType) IsNil() bool {
-	return pt.p == nil && pt.v == nil && pt.a == nil
+	return pt.p == nil && pt.v == nil && pt.a == nil && pt.f == nil
 }
 
 // Type returns the output type.
@@ -961,6 +1056,11 @@ func (pt ProvidedType) IsArg() bool {
 	return pt.a != nil
 }
 
+// IsField reports whether pt points to a Fields.
+func (pt ProvidedType) IsField() bool {
+	return pt.f != nil
+}
+
 // Provider returns pt as a Provider pointer. It panics if pt does not point
 // to a Provider.
 func (pt ProvidedType) Provider() *Provider {
@@ -987,3 +1087,12 @@ func (pt ProvidedType) Arg() *InjectorArg {
 	}
 	return pt.a
 }
+
+// Field returns pt as a Field pointer. It panics if pt does not point to a
+// struct Field.
+func (pt ProvidedType) Field() *Field {
+	if pt.f == nil {
+		panic("ProvidedType does not hold a Field")
+	}
+	return pt.f
+}
diff --git a/internal/wire/wire.go b/internal/wire/wire.go
index 7c1e573..4aabf10 100644
--- a/internal/wire/wire.go
+++ b/internal/wire/wire.go
@@ -623,6 +623,8 @@ func injectPass(name string, sig *types.Signature, calls []call, ig *injectorGen
 			ig.funcProviderCall(lname, c, injectSig)
 		case valueExpr:
 			ig.valueExpr(lname, c)
+		case selectorExpr:
+			ig.fieldExpr(lname, c)
 		default:
 			panic("unknown kind")
 		}
@@ -715,6 +717,15 @@ func (ig *injectorGen) valueExpr(lname string, c *call) {
 	ig.p("\t%s := %s\n", lname, ig.g.values[c.valueExpr])
 }
 
+func (ig *injectorGen) fieldExpr(lname string, c *call) {
+	a := c.args[0]
+	if a < len(ig.paramNames) {
+		ig.p("\t%s := %s.%s\n", lname, ig.paramNames[a], c.name)
+	} else {
+		ig.p("\t%s := %s.%s\n", lname, ig.localNames[a-len(ig.paramNames)], c.name)
+	}
+}
+
 // nameInInjector reports whether name collides with any other identifier
 // in the current injector.
 func (ig *injectorGen) nameInInjector(name string) bool {
diff --git a/wire.go b/wire.go
index 92a4ebe..e7acfa9 100644
--- a/wire.go
+++ b/wire.go
@@ -30,7 +30,8 @@ type ProviderSet struct{}
 
 // NewSet creates a new provider set that includes the providers in its
 // arguments. Each argument is a function value, a struct (zero) value, a
-// provider set, a call to Bind, a call to Value, or a call to InterfaceValue.
+// provider set, a call to Bind, a call to Value, a call to InterfaceValue or a
+// call to FieldsOf.
 //
 // Passing a function value to NewSet declares that the function's first
 // return value type will be provided by calling the function. The arguments
@@ -125,6 +126,9 @@ func Value(interface{}) ProvidedValue {
 }
 
 // InterfaceValue binds an expression to provide a specific interface type.
+// The first argument is a pointer to the interface which user wants to provide.
+// The second argument is the actual variable value whose type implements the
+// interface.
 //
 // Example:
 //
@@ -132,3 +136,28 @@ func Value(interface{}) ProvidedValue {
 func InterfaceValue(typ interface{}, x interface{}) ProvidedValue {
 	return ProvidedValue{}
 }
+
+// StructFields is a collection of the fields from a struct.
+type StructFields struct{}
+
+// FieldsOf declares that the fields named of the given struct type will be used
+// to provide the types of those fields. The structType argument must be a
+// pointer to the struct or a pointer to a pointer to the struct it wishes to reference.
+//
+// The following example would provide *Foo and *Bar using S.MyFoo and S.MyBar respectively:
+//
+//  type S struct {
+//  	MyFoo *Foo
+//  	MyBar *Bar
+//  }
+//
+//  func NewStruct() S { /* ... */ }
+//  var Set = wire.NewSet(wire.FieldsOf(new(S), "MyFoo", "MyBar"))
+//
+//  or
+//
+//  func NewStruct() *S { /* ... */ }
+//  var Set = wire.NewSet(wire.FieldsOf(new(*S), "MyFoo", "MyBar"))
+func FieldsOf(structType interface{}, fieldNames ...string) StructFields {
+	return StructFields{}
+}


Test patch:
diff --git a/internal/wire/testdata/FieldsOfCycle/foo/foo.go b/internal/wire/testdata/FieldsOfCycle/foo/foo.go
new file mode 100644
index 0000000..2f4b61b
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfCycle/foo/foo.go
@@ -0,0 +1,38 @@
+// Copyright 2019 The Wire Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"fmt"
+)
+
+func main() {
+	fmt.Println(injectedBaz())
+}
+
+type Foo int
+type Baz int
+
+type Bar struct {
+	Bz Baz
+}
+
+func provideFoo(_ Baz) Foo {
+	return 0
+}
+
+func provideBar(_ Foo) Bar {
+	return Bar{}
+}
diff --git a/internal/wire/testdata/FieldsOfCycle/foo/wire.go b/internal/wire/testdata/FieldsOfCycle/foo/wire.go
new file mode 100644
index 0000000..6fdbc65
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfCycle/foo/wire.go
@@ -0,0 +1,26 @@
+// Copyright 2019 The Wire Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//+build wireinject
+
+package main
+
+import (
+	"github.com/google/wire"
+)
+
+func injectedBaz() Baz {
+	wire.Build(provideFoo, provideBar, wire.FieldsOf(new(Bar), "Bz"))
+	return 0
+}
diff --git a/internal/wire/testdata/FieldsOfCycle/pkg b/internal/wire/testdata/FieldsOfCycle/pkg
new file mode 100644
index 0000000..f7a5c8c
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfCycle/pkg
@@ -0,0 +1,1 @@
+example.com/foo
diff --git a/internal/wire/testdata/FieldsOfCycle/want/wire_errs.txt b/internal/wire/testdata/FieldsOfCycle/want/wire_errs.txt
new file mode 100644
index 0000000..de95876
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfCycle/want/wire_errs.txt
@@ -0,0 +1,5 @@
+example.com/foo/wire.go:x:y: cycle for example.com/foo.Bar:
+example.com/foo.Bar (example.com/foo.provideBar) ->
+example.com/foo.Foo (example.com/foo.provideFoo) ->
+example.com/foo.Baz (example.com/foo.Bar.Bz) ->
+example.com/foo.Bar
\ No newline at end of file
diff --git a/internal/wire/testdata/FieldsOfImportedStruct/bar/bar.go b/internal/wire/testdata/FieldsOfImportedStruct/bar/bar.go
new file mode 100644
index 0000000..c7cec39
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfImportedStruct/bar/bar.go
@@ -0,0 +1,32 @@
+// Copyright 2019 The Wire Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bar
+
+import (
+	"example.com/foo"
+)
+
+type Config struct {
+	V int
+}
+
+type Service struct {
+	Cfg *Config
+	F   *foo.Service
+}
+
+func New(cfg *Config, f *foo.Service) *Service {
+	return &Service{Cfg: cfg, F: f}
+}
diff --git a/internal/wire/testdata/FieldsOfImportedStruct/baz/baz.go b/internal/wire/testdata/FieldsOfImportedStruct/baz/baz.go
new file mode 100644
index 0000000..c573fe9
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfImportedStruct/baz/baz.go
@@ -0,0 +1,36 @@
+// Copyright 2019 The Wire Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package baz
+
+import (
+	"fmt"
+
+	"example.com/bar"
+	"example.com/foo"
+)
+
+type Config struct {
+	Foo *foo.Config
+	Bar *bar.Config
+}
+
+type Service struct {
+	Foo *foo.Service
+	Bar *bar.Service
+}
+
+func (m *Service) String() string {
+	return fmt.Sprintf("%d %d", m.Foo.Cfg.V, m.Bar.Cfg.V)
+}
diff --git a/internal/wire/testdata/FieldsOfImportedStruct/foo/foo.go b/internal/wire/testdata/FieldsOfImportedStruct/foo/foo.go
new file mode 100644
index 0000000..73afd91
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfImportedStruct/foo/foo.go
@@ -0,0 +1,27 @@
+// Copyright 2019 The Wire Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package foo
+
+type Config struct {
+	V int
+}
+
+type Service struct {
+	Cfg *Config
+}
+
+func New(cfg *Config) *Service {
+	return &Service{Cfg: cfg}
+}
diff --git a/internal/wire/testdata/FieldsOfImportedStruct/main/wire.go b/internal/wire/testdata/FieldsOfImportedStruct/main/wire.go
new file mode 100644
index 0000000..5df1a39
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfImportedStruct/main/wire.go
@@ -0,0 +1,49 @@
+// Copyright 2019 The Wire Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//+build wireinject
+
+package main
+
+import (
+	"fmt"
+
+	"example.com/bar"
+	"example.com/baz"
+	"example.com/foo"
+	"github.com/google/wire"
+)
+
+func newBazService(*baz.Config) *baz.Service {
+	wire.Build(
+		baz.Service{},
+		wire.FieldsOf(
+			new(*baz.Config),
+			"Foo",
+			"Bar",
+		),
+		foo.New,
+		bar.New,
+	)
+	return nil
+}
+
+func main() {
+	cfg := &baz.Config{
+		Foo: &foo.Config{1},
+		Bar: &bar.Config{2},
+	}
+	svc := newBazService(cfg)
+	fmt.Println(svc.String())
+}
diff --git a/internal/wire/testdata/FieldsOfImportedStruct/pkg b/internal/wire/testdata/FieldsOfImportedStruct/pkg
new file mode 100644
index 0000000..b7badd9
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfImportedStruct/pkg
@@ -0,0 +1,1 @@
+example.com/main
diff --git a/internal/wire/testdata/FieldsOfImportedStruct/want/program_out.txt b/internal/wire/testdata/FieldsOfImportedStruct/want/program_out.txt
new file mode 100644
index 0000000..8d04f96
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfImportedStruct/want/program_out.txt
@@ -0,0 +1,1 @@
+1 2
diff --git a/internal/wire/testdata/FieldsOfImportedStruct/want/wire_gen.go b/internal/wire/testdata/FieldsOfImportedStruct/want/wire_gen.go
new file mode 100644
index 0000000..a2fbf95
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfImportedStruct/want/wire_gen.go
@@ -0,0 +1,38 @@
+// Code generated by Wire. DO NOT EDIT.
+
+//go:generate wire
+//+build !wireinject
+
+package main
+
+import (
+	"example.com/bar"
+	"example.com/baz"
+	"example.com/foo"
+	"fmt"
+)
+
+// Injectors from wire.go:
+
+func newBazService(config *baz.Config) *baz.Service {
+	fooConfig := config.Foo
+	service := foo.New(fooConfig)
+	barConfig := config.Bar
+	barService := bar.New(barConfig, service)
+	bazService := &baz.Service{
+		Foo: service,
+		Bar: barService,
+	}
+	return bazService
+}
+
+// wire.go:
+
+func main() {
+	cfg := &baz.Config{
+		Foo: &foo.Config{1},
+		Bar: &bar.Config{2},
+	}
+	svc := newBazService(cfg)
+	fmt.Println(svc.String())
+}
diff --git a/internal/wire/testdata/FieldsOfStruct/foo/foo.go b/internal/wire/testdata/FieldsOfStruct/foo/foo.go
new file mode 100644
index 0000000..09e0eb0
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfStruct/foo/foo.go
@@ -0,0 +1,29 @@
+// Copyright 2018 The Wire Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import "fmt"
+
+type S struct {
+	Foo string
+}
+
+func provideS() S {
+	return S{Foo: "Hello, World!"}
+}
+
+func main() {
+	fmt.Println(injectedMessage())
+}
diff --git a/internal/wire/testdata/FieldsOfStruct/foo/wire.go b/internal/wire/testdata/FieldsOfStruct/foo/wire.go
new file mode 100644
index 0000000..94aa316
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfStruct/foo/wire.go
@@ -0,0 +1,28 @@
+// Copyright 2018 The Wire Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//+build wireinject
+
+package main
+
+import (
+	"github.com/google/wire"
+)
+
+func injectedMessage() string {
+	wire.Build(
+		provideS,
+		wire.FieldsOf(new(S), "Foo"))
+	return ""
+}
diff --git a/internal/wire/testdata/FieldsOfStruct/pkg b/internal/wire/testdata/FieldsOfStruct/pkg
new file mode 100644
index 0000000..f7a5c8c
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfStruct/pkg
@@ -0,0 +1,1 @@
+example.com/foo
diff --git a/internal/wire/testdata/FieldsOfStruct/want/program_out.txt b/internal/wire/testdata/FieldsOfStruct/want/program_out.txt
new file mode 100644
index 0000000..8ab686e
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfStruct/want/program_out.txt
@@ -0,0 +1,1 @@
+Hello, World!
diff --git a/internal/wire/testdata/FieldsOfStruct/want/wire_gen.go b/internal/wire/testdata/FieldsOfStruct/want/wire_gen.go
new file mode 100644
index 0000000..2092a3d
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfStruct/want/wire_gen.go
@@ -0,0 +1,14 @@
+// Code generated by Wire. DO NOT EDIT.
+
+//go:generate wire
+//+build !wireinject
+
+package main
+
+// Injectors from wire.go:
+
+func injectedMessage() string {
+	s := provideS()
+	string2 := s.Foo
+	return string2
+}
diff --git a/internal/wire/testdata/FieldsOfStructPointer/foo/foo.go b/internal/wire/testdata/FieldsOfStructPointer/foo/foo.go
new file mode 100644
index 0000000..a562157
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfStructPointer/foo/foo.go
@@ -0,0 +1,29 @@
+// Copyright 2018 The Wire Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import "fmt"
+
+type S struct {
+	Foo string
+}
+
+func provideS() *S {
+	return &S{Foo: "Hello, World!"}
+}
+
+func main() {
+	fmt.Println(injectedMessage())
+}
diff --git a/internal/wire/testdata/FieldsOfStructPointer/foo/wire.go b/internal/wire/testdata/FieldsOfStructPointer/foo/wire.go
new file mode 100644
index 0000000..ac9a81a
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfStructPointer/foo/wire.go
@@ -0,0 +1,28 @@
+// Copyright 2018 The Wire Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//+build wireinject
+
+package main
+
+import (
+	"github.com/google/wire"
+)
+
+func injectedMessage() string {
+	wire.Build(
+		provideS,
+		wire.FieldsOf(new(*S), "Foo"))
+	return ""
+}
diff --git a/internal/wire/testdata/FieldsOfStructPointer/pkg b/internal/wire/testdata/FieldsOfStructPointer/pkg
new file mode 100644
index 0000000..f7a5c8c
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfStructPointer/pkg
@@ -0,0 +1,1 @@
+example.com/foo
diff --git a/internal/wire/testdata/FieldsOfStructPointer/want/program_out.txt b/internal/wire/testdata/FieldsOfStructPointer/want/program_out.txt
new file mode 100644
index 0000000..8ab686e
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfStructPointer/want/program_out.txt
@@ -0,0 +1,1 @@
+Hello, World!
diff --git a/internal/wire/testdata/FieldsOfStructPointer/want/wire_gen.go b/internal/wire/testdata/FieldsOfStructPointer/want/wire_gen.go
new file mode 100644
index 0000000..2092a3d
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfStructPointer/want/wire_gen.go
@@ -0,0 +1,14 @@
+// Code generated by Wire. DO NOT EDIT.
+
+//go:generate wire
+//+build !wireinject
+
+package main
+
+// Injectors from wire.go:
+
+func injectedMessage() string {
+	s := provideS()
+	string2 := s.Foo
+	return string2
+}
diff --git a/internal/wire/testdata/FieldsOfValueStruct/bar/bar.go b/internal/wire/testdata/FieldsOfValueStruct/bar/bar.go
new file mode 100644
index 0000000..c7cec39
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfValueStruct/bar/bar.go
@@ -0,0 +1,32 @@
+// Copyright 2019 The Wire Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bar
+
+import (
+	"example.com/foo"
+)
+
+type Config struct {
+	V int
+}
+
+type Service struct {
+	Cfg *Config
+	F   *foo.Service
+}
+
+func New(cfg *Config, f *foo.Service) *Service {
+	return &Service{Cfg: cfg, F: f}
+}
diff --git a/internal/wire/testdata/FieldsOfValueStruct/baz/baz.go b/internal/wire/testdata/FieldsOfValueStruct/baz/baz.go
new file mode 100644
index 0000000..c573fe9
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfValueStruct/baz/baz.go
@@ -0,0 +1,36 @@
+// Copyright 2019 The Wire Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package baz
+
+import (
+	"fmt"
+
+	"example.com/bar"
+	"example.com/foo"
+)
+
+type Config struct {
+	Foo *foo.Config
+	Bar *bar.Config
+}
+
+type Service struct {
+	Foo *foo.Service
+	Bar *bar.Service
+}
+
+func (m *Service) String() string {
+	return fmt.Sprintf("%d %d", m.Foo.Cfg.V, m.Bar.Cfg.V)
+}
diff --git a/internal/wire/testdata/FieldsOfValueStruct/foo/foo.go b/internal/wire/testdata/FieldsOfValueStruct/foo/foo.go
new file mode 100644
index 0000000..73afd91
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfValueStruct/foo/foo.go
@@ -0,0 +1,27 @@
+// Copyright 2019 The Wire Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package foo
+
+type Config struct {
+	V int
+}
+
+type Service struct {
+	Cfg *Config
+}
+
+func New(cfg *Config) *Service {
+	return &Service{Cfg: cfg}
+}
diff --git a/internal/wire/testdata/FieldsOfValueStruct/main/wire.go b/internal/wire/testdata/FieldsOfValueStruct/main/wire.go
new file mode 100644
index 0000000..7053288
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfValueStruct/main/wire.go
@@ -0,0 +1,49 @@
+// Copyright 2019 The Wire Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//+build wireinject
+
+package main
+
+import (
+	"fmt"
+
+	"example.com/bar"
+	"example.com/baz"
+	"example.com/foo"
+	"github.com/google/wire"
+)
+
+func newBazService() *baz.Service {
+	wire.Build(
+		baz.Service{},
+		wire.Value(&baz.Config{
+			Foo: &foo.Config{1},
+			Bar: &bar.Config{2},
+		}),
+		wire.FieldsOf(
+			new(*baz.Config),
+			"Foo",
+			"Bar",
+		),
+		foo.New,
+		bar.New,
+	)
+	return nil
+}
+
+func main() {
+	svc := newBazService()
+	fmt.Println(svc.String())
+}
diff --git a/internal/wire/testdata/FieldsOfValueStruct/pkg b/internal/wire/testdata/FieldsOfValueStruct/pkg
new file mode 100644
index 0000000..b7badd9
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfValueStruct/pkg
@@ -0,0 +1,1 @@
+example.com/main
diff --git a/internal/wire/testdata/FieldsOfValueStruct/want/program_out.txt b/internal/wire/testdata/FieldsOfValueStruct/want/program_out.txt
new file mode 100644
index 0000000..8d04f96
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfValueStruct/want/program_out.txt
@@ -0,0 +1,1 @@
+1 2
diff --git a/internal/wire/testdata/FieldsOfValueStruct/want/wire_gen.go b/internal/wire/testdata/FieldsOfValueStruct/want/wire_gen.go
new file mode 100644
index 0000000..80ebda1
--- /dev/null
+++ b/internal/wire/testdata/FieldsOfValueStruct/want/wire_gen.go
@@ -0,0 +1,42 @@
+// Code generated by Wire. DO NOT EDIT.
+
+//go:generate wire
+//+build !wireinject
+
+package main
+
+import (
+	"example.com/bar"
+	"example.com/baz"
+	"example.com/foo"
+	"fmt"
+)
+
+// Injectors from wire.go:
+
+func newBazService() *baz.Service {
+	config := _wireConfigValue
+	fooConfig := config.Foo
+	service := foo.New(fooConfig)
+	barConfig := config.Bar
+	barService := bar.New(barConfig, service)
+	bazService := &baz.Service{
+		Foo: service,
+		Bar: barService,
+	}
+	return bazService
+}
+
+var (
+	_wireConfigValue = &baz.Config{
+		Foo: &foo.Config{1},
+		Bar: &bar.Config{2},
+	}
+)
+
+// wire.go:
+
+func main() {
+	svc := newBazService()
+	fmt.Println(svc.String())
+}
diff --git a/internal/wire/testdata/MultipleSimilarPackages/main/wire.go b/internal/wire/testdata/MultipleSimilarPackages/main/wire.go
index 4e31b6d..88ddac5 100644
--- a/internal/wire/testdata/MultipleSimilarPackages/main/wire.go
+++ b/internal/wire/testdata/MultipleSimilarPackages/main/wire.go
@@ -28,22 +28,28 @@ import (
 type MainConfig struct {
 	Foo *foo.Config
 	Bar *bar.Config
-	Baz *baz.Config
+	baz *baz.Config
 }
 
 type MainService struct {
 	Foo *foo.Service
 	Bar *bar.Service
-	Baz *baz.Service
+	baz *baz.Service
 }
 
 func (m *MainService) String() string {
-	return fmt.Sprintf("%d %d %d", m.Foo.Cfg.V, m.Bar.Cfg.V, m.Baz.Cfg.V)
+	return fmt.Sprintf("%d %d %d", m.Foo.Cfg.V, m.Bar.Cfg.V, m.baz.Cfg.V)
 }
 
-func newMainService(*foo.Config, *bar.Config, *baz.Config) *MainService {
+func newMainService(MainConfig) *MainService {
 	wire.Build(
 		MainService{},
+		wire.FieldsOf(
+			new(MainConfig),
+			"Foo",
+			"Bar",
+			"baz",
+		),
 		foo.New,
 		bar.New,
 		baz.New,
@@ -52,11 +58,11 @@ func newMainService(*foo.Config, *bar.Config, *baz.Config) *MainService {
 }
 
 func main() {
-	cfg := &MainConfig{
+	cfg := MainConfig{
 		Foo: &foo.Config{1},
 		Bar: &bar.Config{2},
-		Baz: &baz.Config{3},
+		baz: &baz.Config{3},
 	}
-	svc := newMainService(cfg.Foo, cfg.Bar, cfg.Baz)
+	svc := newMainService(cfg)
 	fmt.Println(svc.String())
 }
diff --git a/internal/wire/testdata/MultipleSimilarPackages/want/wire_gen.go b/internal/wire/testdata/MultipleSimilarPackages/want/wire_gen.go
index 26ad830..dff7e7f 100644
--- a/internal/wire/testdata/MultipleSimilarPackages/want/wire_gen.go
+++ b/internal/wire/testdata/MultipleSimilarPackages/want/wire_gen.go
@@ -14,14 +14,17 @@ import (
 
 // Injectors from wire.go:
 
-func newMainService(config *foo.Config, barConfig *bar.Config, bazConfig *baz.Config) *MainService {
+func newMainService(mainConfig MainConfig) *MainService {
+	config := mainConfig.Foo
 	service := foo.New(config)
+	barConfig := mainConfig.Bar
 	barService := bar.New(barConfig, service)
+	bazConfig := mainConfig.baz
 	bazService := baz.New(bazConfig, barService)
 	mainService := &MainService{
 		Foo: service,
 		Bar: barService,
-		Baz: bazService,
+		baz: bazService,
 	}
 	return mainService
 }
@@ -31,25 +34,25 @@ func newMainService(config *foo.Config, barConfig *bar.Config, bazConfig *baz.Co
 type MainConfig struct {
 	Foo *foo.Config
 	Bar *bar.Config
-	Baz *baz.Config
+	baz *baz.Config
 }
 
 type MainService struct {
 	Foo *foo.Service
 	Bar *bar.Service
-	Baz *baz.Service
+	baz *baz.Service
 }
 
 func (m *MainService) String() string {
-	return fmt.Sprintf("%d %d %d", m.Foo.Cfg.V, m.Bar.Cfg.V, m.Baz.Cfg.V)
+	return fmt.Sprintf("%d %d %d", m.Foo.Cfg.V, m.Bar.Cfg.V, m.baz.Cfg.V)
 }
 
 func main() {
-	cfg := &MainConfig{
+	cfg := MainConfig{
 		Foo: &foo.Config{1},
 		Bar: &bar.Config{2},
-		Baz: &baz.Config{3},
+		baz: &baz.Config{3},
 	}
-	svc := newMainService(cfg.Foo, cfg.Bar, cfg.Baz)
+	svc := newMainService(cfg)
 	fmt.Println(svc.String())
 }
diff --git a/internal/wire/testdata/UnusedProviders/foo/foo.go b/internal/wire/testdata/UnusedProviders/foo/foo.go
index e018f77..8c7b3d7 100644
--- a/internal/wire/testdata/UnusedProviders/foo/foo.go
+++ b/internal/wire/testdata/UnusedProviders/foo/foo.go
@@ -69,3 +69,9 @@ func provideOneOfTwo() OneOfTwo {
 func provideTwoOfTwo() TwoOfTwo {
 	return 1
 }
+
+type S struct {
+	Cfg Config
+}
+
+type Config int
diff --git a/internal/wire/testdata/UnusedProviders/foo/wire.go b/internal/wire/testdata/UnusedProviders/foo/wire.go
index 0843020..689a863 100644
--- a/internal/wire/testdata/UnusedProviders/foo/wire.go
+++ b/internal/wire/testdata/UnusedProviders/foo/wire.go
@@ -29,6 +29,7 @@ func injectBar() Bar {
 		wire.Value("unused"),                  // not needed -> error
 		unusedSet,                             // nothing in set is needed -> error
 		wire.Bind((*Fooer)(nil), (*Foo)(nil)), // binding to Fooer is not needed -> error
+		wire.FieldsOf(new(S), "Cfg"),          // S.Cfg not needed -> error
 	)
 	return 0
 }
diff --git a/internal/wire/testdata/UnusedProviders/want/wire_errs.txt b/internal/wire/testdata/UnusedProviders/want/wire_errs.txt
index 3bfaac1..ca323ab 100644
--- a/internal/wire/testdata/UnusedProviders/want/wire_errs.txt
+++ b/internal/wire/testdata/UnusedProviders/want/wire_errs.txt
@@ -4,4 +4,6 @@ example.com/foo/wire.go:x:y: inject injectBar: unused provider "provideUnused"
 
 example.com/foo/wire.go:x:y: inject injectBar: unused value of type string
 
-example.com/foo/wire.go:x:y: inject injectBar: unused interface binding to type example.com/foo.Fooer
\ No newline at end of file
+example.com/foo/wire.go:x:y: inject injectBar: unused interface binding to type example.com/foo.Fooer
+
+example.com/foo/wire.go:x:y: inject injectBar: unused field "example.com/foo.S".Cfg
\ No newline at end of file