wire: FieldsOf now provides a pointer to the field type as well as the actual field type (#209)

This commit is contained in:
Robert van Gent
2019-09-03 12:57:57 -07:00
committed by GitHub
parent 66f78fc846
commit 2b7d1205a1
9 changed files with 69 additions and 26 deletions

View File

@@ -233,11 +233,11 @@ have a provider in the same set that provides the concrete type.
### Struct Providers ### Struct Providers
Structs can also be marked as providers. Use the `wire.Struct` function to Structs can be constructed using provided types. Use the `wire.Struct` function
inject a struct type and tell the injector which field(s) should be injected. to construct a struct type and tell the injector which field(s) should be injected.
The injector will fill in each field using the provider for the field's type. The injector will fill in each field using the provider for the field's type.
For a given struct type `S`, this would provide both `S` and `*S`. For example, For the resulting struct type `S`, `wire.Struct` provides both `S` and `*S`. For
given the following providers: example, given the following providers:
```go ```go
type Foo int type Foo int
@@ -298,7 +298,18 @@ func injectFooBar() FooBar {
} }
``` ```
And similarly if the injector needed a `*FooBar`. If the injector returned a `*FooBar` instead of a `FooBar`, the generated injector
would look like this:
```go
func injectFooBar() *FooBar {
foo := ProvideFoo()
fooBar := &FooBar{
MyFoo: foo,
}
return fooBar
}
```
It is sometimes useful to prevent certain fields from being filled in by the It is sometimes useful to prevent certain fields from being filled in by the
injector, especially when passing `*` to `wire.Struct`. You can tag a field with injector, especially when passing `*` to `wire.Struct`. You can tag a field with
@@ -412,6 +423,7 @@ func injectedMessage() string {
``` ```
You can add as many field names to a `wire.FieldsOf` function as you like. You can add as many field names to a `wire.FieldsOf` function as you like.
For a given field type `T`, `FieldsOf` provides both `T` and `*T`.
### Cleanup functions ### Cleanup functions

View File

@@ -84,6 +84,10 @@ type call struct {
valueExpr ast.Expr valueExpr ast.Expr
valueTypeInfo *types.Info valueTypeInfo *types.Info
// The following are only set for kind == selectorExpr:
ptrToField bool
} }
// solve finds the sequence of calls required to produce an output type // solve finds the sequence of calls required to produce an output type
@@ -226,14 +230,18 @@ dfs:
index.Set(curr.t, errAbort) index.Set(curr.t, errAbort)
continue dfs continue dfs
} }
// Use the args[0] to store the position of the parent struct. // Use args[0] to store the position of the parent struct.
args := []int{v.(int)} args := []int{v.(int)}
// len(f.Out) is always 2; if curr.t is the 2nd one, then the call must
// provide a pointer to the field.
ptrToField := types.Identical(curr.t, f.Out[1])
calls = append(calls, call{ calls = append(calls, call{
kind: selectorExpr, kind: selectorExpr,
pkg: f.Pkg, pkg: f.Pkg,
name: f.Name, name: f.Name,
out: curr.t, out: curr.t,
args: args, args: args,
ptrToField: ptrToField,
}) })
default: default:
panic("unknown return value from ProviderSet.For") panic("unknown return value from ProviderSet.For")
@@ -382,12 +390,14 @@ func buildProviderMap(fset *token.FileSet, hasher typeutil.Hasher, set *Provider
} }
for _, f := range set.Fields { for _, f := range set.Fields {
src := &providerSetSrc{Field: f} src := &providerSetSrc{Field: f}
if prevSrc := srcMap.At(f.Out); prevSrc != nil { for _, typ := range f.Out {
ec.add(bindingConflictError(fset, f.Out, set, src, prevSrc.(*providerSetSrc))) if prevSrc := srcMap.At(typ); prevSrc != nil {
ec.add(bindingConflictError(fset, typ, set, src, prevSrc.(*providerSetSrc)))
continue continue
} }
providerMap.Set(f.Out, &ProvidedType{t: f.Out, f: f}) providerMap.Set(typ, &ProvidedType{t: typ, f: f})
srcMap.Set(f.Out, src) srcMap.Set(typ, src)
}
} }
if len(ec.errors) > 0 { if len(ec.errors) > 0 {
return nil, nil, ec.errors return nil, nil, ec.errors

View File

@@ -220,7 +220,7 @@ type InjectorArgs struct {
Pos token.Pos Pos token.Pos
} }
// Field describes a list of fields from a struct. // Field describes a specific field selected from a struct.
type Field struct { type Field struct {
// Parent is the struct or pointer to the struct that the field belongs to. // Parent is the struct or pointer to the struct that the field belongs to.
Parent types.Type Parent types.Type
@@ -231,8 +231,9 @@ type Field struct {
// Pos is the source position of the field declaration. // Pos is the source position of the field declaration.
// defining these fields. // defining these fields.
Pos token.Pos Pos token.Pos
// Out is the field's type. // Out is the field's provided types. The first element provides the
Out types.Type // field type, the second element provides a pointer to it.
Out []types.Type
} }
// Load finds all the provider sets in the packages that match the given // Load finds all the provider sets in the packages that match the given
@@ -458,7 +459,7 @@ func newObjectCache(pkgs []*packages.Package) *objectCache {
} }
// get converts a Go object into a Wire structure. It may return a *Provider, an // get converts a Go object into a Wire structure. It may return a *Provider, an
// *IfaceBinding, a *ProviderSet, a *Value, or a *Fields. // *IfaceBinding, a *ProviderSet, a *Value, or a []*Field.
func (oc *objectCache) get(obj types.Object) (val interface{}, errs []error) { func (oc *objectCache) get(obj types.Object) (val interface{}, errs []error) {
ref := objRef{ ref := objRef{
importPath: obj.Pkg().Path(), importPath: obj.Pkg().Path(),
@@ -515,7 +516,7 @@ func (oc *objectCache) varDecl(obj *types.Var) *ast.ValueSpec {
} }
// processExpr converts an expression into a Wire structure. It may return a // processExpr converts an expression into a Wire structure. It may return a
// *Provider, an *IfaceBinding, a *ProviderSet, a *Value or a *Field. // *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) { func (oc *objectCache) processExpr(info *types.Info, pkgPath string, expr ast.Expr, varName string) (interface{}, []error) {
exprPos := oc.fset.Position(expr.Pos()) exprPos := oc.fset.Position(expr.Pos())
expr = astutil.Unparen(expr) expr = astutil.Unparen(expr)
@@ -988,7 +989,7 @@ func processInterfaceValue(fset *token.FileSet, info *types.Info, call *ast.Call
}, nil }, nil
} }
// processFieldsOf creates a list of fields from a wire.FieldsOf call. // processFieldsOf creates a slice of fields from a wire.FieldsOf call.
func processFieldsOf(fset *token.FileSet, info *types.Info, call *ast.CallExpr) ([]*Field, error) { func processFieldsOf(fset *token.FileSet, info *types.Info, call *ast.CallExpr) ([]*Field, error) {
// Assumes that call.Fun is wire.FieldsOf. // Assumes that call.Fun is wire.FieldsOf.
@@ -1034,7 +1035,7 @@ func processFieldsOf(fset *token.FileSet, info *types.Info, call *ast.CallExpr)
Name: v.Name(), Name: v.Name(),
Pkg: v.Pkg(), Pkg: v.Pkg(),
Pos: v.Pos(), Pos: v.Pos(),
Out: v.Type(), Out: []types.Type{v.Type(), types.NewPointer(v.Type())},
}) })
} }
return fields, nil return fields, nil

View File

@@ -26,4 +26,5 @@ func provideS() S {
func main() { func main() {
fmt.Println(injectedMessage()) fmt.Println(injectedMessage())
fmt.Println("pointer to " + *injectedMessagePtr())
} }

View File

@@ -26,3 +26,10 @@ func injectedMessage() string {
wire.FieldsOf(new(S), "Foo")) wire.FieldsOf(new(S), "Foo"))
return "" return ""
} }
func injectedMessagePtr() *string {
wire.Build(
provideS,
wire.FieldsOf(new(S), "Foo"))
return nil
}

View File

@@ -1 +1,2 @@
Hello, World! Hello, World!
pointer to Hello, World!

View File

@@ -12,3 +12,9 @@ func injectedMessage() string {
string2 := s.Foo string2 := s.Foo
return string2 return string2
} }
func injectedMessagePtr() *string {
s := provideS()
string2 := &s.Foo
return string2
}

View File

@@ -731,10 +731,14 @@ func (ig *injectorGen) valueExpr(lname string, c *call) {
func (ig *injectorGen) fieldExpr(lname string, c *call) { func (ig *injectorGen) fieldExpr(lname string, c *call) {
a := c.args[0] a := c.args[0]
ig.p("\t%s := ", lname)
if c.ptrToField {
ig.p("&")
}
if a < len(ig.paramNames) { if a < len(ig.paramNames) {
ig.p("\t%s := %s.%s\n", lname, ig.paramNames[a], c.name) ig.p("%s.%s\n", ig.paramNames[a], c.name)
} else { } else {
ig.p("\t%s := %s.%s\n", lname, ig.localNames[a-len(ig.paramNames)], c.name) ig.p("%s.%s\n", ig.localNames[a-len(ig.paramNames)], c.name)
} }
} }

View File

@@ -146,7 +146,8 @@ func InterfaceValue(typ interface{}, x interface{}) ProvidedValue {
// A StructProvider represents a named struct. // A StructProvider represents a named struct.
type StructProvider struct{} type StructProvider struct{}
// Struct specifies that the given struct type will be provided by filling in the fields in the struct that have the names given. // Struct specifies that the given struct type will be provided by filling in
// the fields in the struct that have the names given.
// //
// The first argument must be a pointer to the struct type. For a struct type // The first argument must be a pointer to the struct type. For a struct type
// Foo, Wire will use field-filling to provide both Foo and *Foo. The remaining // Foo, Wire will use field-filling to provide both Foo and *Foo. The remaining