Skip to content

Commit

Permalink
wire: FieldsOf now provides a pointer to the field type as well as th…
Browse files Browse the repository at this point in the history
…e actual field type (#209)
  • Loading branch information
vangent committed Sep 3, 2019
1 parent 66f78fc commit 2b7d120
Show file tree
Hide file tree
Showing 9 changed files with 69 additions and 26 deletions.
22 changes: 17 additions & 5 deletions docs/guide.md
Expand Up @@ -233,11 +233,11 @@ have a provider in the same set that provides the concrete type.

### Struct Providers

Structs can also be marked as providers. Use the `wire.Struct` function to
inject a struct type and tell the injector which field(s) should be injected.
Structs can be constructed using provided types. Use the `wire.Struct` function
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.
For a given struct type `S`, this would provide both `S` and `*S`. For example,
given the following providers:
For the resulting struct type `S`, `wire.Struct` provides both `S` and `*S`. For
example, given the following providers:

```go
type Foo int
Expand Down Expand Up @@ -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
injector, especially when passing `*` to `wire.Struct`. You can tag a field with
Expand Down Expand Up @@ -412,6 +423,7 @@ func injectedMessage() string {
```

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

Expand Down
32 changes: 21 additions & 11 deletions internal/wire/analyze.go
Expand Up @@ -84,6 +84,10 @@ type call struct {

valueExpr ast.Expr
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
Expand Down Expand Up @@ -226,14 +230,18 @@ dfs:
index.Set(curr.t, errAbort)
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)}
// 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{
kind: selectorExpr,
pkg: f.Pkg,
name: f.Name,
out: curr.t,
args: args,
kind: selectorExpr,
pkg: f.Pkg,
name: f.Name,
out: curr.t,
args: args,
ptrToField: ptrToField,
})
default:
panic("unknown return value from ProviderSet.For")
Expand Down Expand Up @@ -382,12 +390,14 @@ func buildProviderMap(fset *token.FileSet, hasher typeutil.Hasher, set *Provider
}
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
for _, typ := range f.Out {
if prevSrc := srcMap.At(typ); prevSrc != nil {
ec.add(bindingConflictError(fset, typ, set, src, prevSrc.(*providerSetSrc)))
continue
}
providerMap.Set(typ, &ProvidedType{t: typ, f: f})
srcMap.Set(typ, src)
}
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
Expand Down
15 changes: 8 additions & 7 deletions internal/wire/parse.go
Expand Up @@ -220,7 +220,7 @@ type InjectorArgs struct {
Pos token.Pos
}

// Field describes a list of fields from a struct.
// Field describes a specific field selected from a struct.
type Field struct {
// Parent is the struct or pointer to the struct that the field belongs to.
Parent types.Type
Expand All @@ -231,8 +231,9 @@ type Field struct {
// Pos is the source position of the field declaration.
// defining these fields.
Pos token.Pos
// Out is the field's type.
Out types.Type
// Out is the field's provided types. The first element provides the
// 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
Expand Down Expand Up @@ -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
// *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) {
ref := objRef{
importPath: obj.Pkg().Path(),
Expand Down Expand Up @@ -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
// *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) {
exprPos := oc.fset.Position(expr.Pos())
expr = astutil.Unparen(expr)
Expand Down Expand Up @@ -988,7 +989,7 @@ func processInterfaceValue(fset *token.FileSet, info *types.Info, call *ast.Call
}, 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) {
// Assumes that call.Fun is wire.FieldsOf.

Expand Down Expand Up @@ -1034,7 +1035,7 @@ func processFieldsOf(fset *token.FileSet, info *types.Info, call *ast.CallExpr)
Name: v.Name(),
Pkg: v.Pkg(),
Pos: v.Pos(),
Out: v.Type(),
Out: []types.Type{v.Type(), types.NewPointer(v.Type())},
})
}
return fields, nil
Expand Down
1 change: 1 addition & 0 deletions internal/wire/testdata/FieldsOfStruct/foo/foo.go
Expand Up @@ -26,4 +26,5 @@ func provideS() S {

func main() {
fmt.Println(injectedMessage())
fmt.Println("pointer to " + *injectedMessagePtr())
}
7 changes: 7 additions & 0 deletions internal/wire/testdata/FieldsOfStruct/foo/wire.go
Expand Up @@ -26,3 +26,10 @@ func injectedMessage() string {
wire.FieldsOf(new(S), "Foo"))
return ""
}

func injectedMessagePtr() *string {
wire.Build(
provideS,
wire.FieldsOf(new(S), "Foo"))
return nil
}
1 change: 1 addition & 0 deletions internal/wire/testdata/FieldsOfStruct/want/program_out.txt
@@ -1 +1,2 @@
Hello, World!
pointer to Hello, World!
6 changes: 6 additions & 0 deletions internal/wire/testdata/FieldsOfStruct/want/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions internal/wire/wire.go
Expand Up @@ -731,10 +731,14 @@ func (ig *injectorGen) valueExpr(lname string, c *call) {

func (ig *injectorGen) fieldExpr(lname string, c *call) {
a := c.args[0]
ig.p("\t%s := ", lname)
if c.ptrToField {
ig.p("&")
}
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 {
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)
}
}

Expand Down
3 changes: 2 additions & 1 deletion wire.go
Expand Up @@ -146,7 +146,8 @@ func InterfaceValue(typ interface{}, x interface{}) ProvidedValue {
// A StructProvider represents a named 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
// Foo, Wire will use field-filling to provide both Foo and *Foo. The remaining
Expand Down

0 comments on commit 2b7d120

Please sign in to comment.