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) + 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) + } + 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 } - 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) } } - 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/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 @@ +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 @@ +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 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 @@ +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 @@ +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 @@ +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 @@ +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 @@ +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 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 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 df274b1..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 @@ -135,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{} +}