goose: use marker functions instead of comments

To avoid making this CL too large, I did not migrate the existing goose
comments through the repository.  This will be addressed in a subsequent
CL.

Reviewed-by: Tuo Shan <shantuo@google.com>
This commit is contained in:
Ross Light
2018-04-27 13:44:54 -04:00
parent 13698e656a
commit f8e446fa17
59 changed files with 713 additions and 983 deletions

113
README.md
View File

@@ -13,34 +13,27 @@ might have hand-written.
### Defining Providers ### Defining Providers
The primary mechanism in goose is the **provider**: a function that can The primary mechanism in goose is the **provider**: a function that can
produce a value, annotated with the special `goose:provide` directive. These produce a value. These functions are ordinary Go code.
functions are otherwise ordinary Go code.
```go ```go
package foobarbaz package foobarbaz
type Foo int type Foo int
// goose:provide
// ProvideFoo returns a Foo. // ProvideFoo returns a Foo.
func ProvideFoo() Foo { func ProvideFoo() Foo {
return 42 return 42
} }
``` ```
Providers are always part of a **provider set**: if there is no provider set
named on the `//goose:provide` line, then the provider is added to the provider
set with the same name as the function (`ProvideFoo`, in this case).
Providers can specify dependencies with parameters: Providers can specify dependencies with parameters:
```go ```go
package foobarbaz package foobarbaz
type Bar int // ...
// goose:provide SuperSet type Bar int
// ProvideBar returns a Bar: a negative Foo. // ProvideBar returns a Bar: a negative Foo.
func ProvideBar(foo Foo) Bar { func ProvideBar(foo Foo) Bar {
@@ -58,9 +51,9 @@ import (
"errors" "errors"
) )
type Baz int // ...
// goose:provide SuperSet type Baz int
// ProvideBaz returns a value if Bar is not zero. // ProvideBaz returns a value if Bar is not zero.
func ProvideBaz(ctx context.Context, bar Bar) (Baz, error) { func ProvideBaz(ctx context.Context, bar Bar) (Baz, error) {
@@ -71,23 +64,36 @@ func ProvideBaz(ctx context.Context, bar Bar) (Baz, error) {
} }
``` ```
Provider sets can import other provider sets. To add the `ProvideFoo` set to Providers can be grouped in **provider sets**. To add these providers to a new
`SuperSet`: set called `SuperSet`, use the `goose.NewSet` function:
```go ```go
// goose:import SuperSet ProvideFoo package foobarbaz
import (
// ...
"codename/goose"
)
// ...
var SuperSet = goose.NewSet(ProvideFoo, ProvideBar, ProvideBaz)
``` ```
You can also import provider sets in another package, provided that you have a You can also add other provider sets into a provider set.
Go import for the package:
```go ```go
// goose:import SuperSet "example.com/some/other/pkg".OtherSet package foobarbaz
```
A provider set reference is an optional import qualifier (either a package name import (
or a quoted import path, as seen above) ending with a dot, followed by the // ...
provider set name. "example.com/some/other/pkg"
)
// ...
var MegaSet = goose.NewSet(SuperSet, pkg.OtherSet)
```
### Injectors ### Injectors
@@ -95,32 +101,34 @@ An application wires up these providers with an **injector**: a function that
calls providers in dependency order. With goose, you write the injector's calls providers in dependency order. With goose, you write the injector's
signature, then goose generates the function's body. signature, then goose generates the function's body.
An injector is declared by writing a function declaration without a body in a An injector is declared by writing a function declaration whose body is a call
file guarded by a `gooseinject` build tag. Let's say that the above providers to `panic()` with a call to `goose.Use` as its argument. Let's say that the
were defined in a package called `example.com/foobarbaz`. The following would above providers were defined in a package called `example.com/foobarbaz`. The
declare an injector to obtain a `Baz`: following would declare an injector to obtain a `Baz`:
```go ```go
//+build gooseinject // +build gooseinject
// ^ build tag makes sure the stub is not built in the final build
package main package main
import ( import (
"context" "context"
"codename/goose"
"example.com/foobarbaz" "example.com/foobarbaz"
) )
// goose:use foobarbaz.SuperSet func initializeApp(ctx context.Context) (foobarbaz.Baz, error) {
panic(goose.Use(foobarbaz.MegaSet))
func initializeApp(ctx context.Context) (foobarbaz.Baz, error) }
``` ```
Like providers, injectors can be parameterized on inputs (which then get sent to Like providers, injectors can be parameterized on inputs (which then get sent to
providers) and can return errors. Each `goose:use` directive specifies a providers) and can return errors. Arguments to `goose.Use` are the same as
provider set to use in the injection. An injector can have one or more `goose.NewSet`: they form a provider set. This is the provider set that gets
`goose:use` directives. `goose:use` directives use the same syntax as used during code generation for that injector.
`goose:import` to reference provider sets.
You can generate the injector by invoking goose in the package directory: You can generate the injector by invoking goose in the package directory:
@@ -164,7 +172,7 @@ func initializeApp(ctx context.Context) (foobarbaz.Baz, error) {
``` ```
As you can see, the output is very close to what a developer would write As you can see, the output is very close to what a developer would write
themselves. Further, there is no dependency on goose at runtime: all of the themselves. Further, there is little dependency on goose at runtime: all of the
written code is just normal Go code, and can be used without goose. written code is just normal Go code, and can be used without goose.
[`go generate`]: https://blog.golang.org/generate [`go generate`]: https://blog.golang.org/generate
@@ -228,19 +236,21 @@ func (b *Bar) Foo() string {
return string(*b) return string(*b)
} }
//goose:provide BarFooer func ProvideBar() *Bar {
func provideBar() *Bar {
b := new(Bar) b := new(Bar)
*b = "Hello, World!" *b = "Hello, World!"
return b return b
} }
//goose:bind BarFooer Fooer *Bar var BarFooer = goose.NewSet(
ProvideBar,
goose.Bind(Fooer(nil), (*Bar)(nil)))
``` ```
The syntax is provider set name, interface type, and finally the concrete type. The first argument to `goose.Bind` is a nil value for the interface type and the
An interface binding does not necessarily need to have a provider in the same second argument is a zero value of the concrete type. An interface binding does
set that provides the concrete type. not necessarily need to have a provider in the same set that provides the
concrete type.
[type identity]: https://golang.org/ref/spec#Type_identity [type identity]: https://golang.org/ref/spec#Type_identity
[return concrete types]: https://github.com/golang/go/wiki/CodeReviewComments#interfaces [return concrete types]: https://github.com/golang/go/wiki/CodeReviewComments#interfaces
@@ -256,32 +266,31 @@ following providers:
type Foo int type Foo int
type Bar int type Bar int
//goose:provide Foo func ProvideFoo() Foo {
func provideFoo() Foo {
// ... // ...
} }
//goose:provide Bar func ProvideBar() Bar {
func provideBar() Bar {
// ... // ...
} }
//goose:provide
type FooBar struct { type FooBar struct {
Foo Foo Foo Foo
Bar Bar Bar Bar
} }
var Set = goose.NewSet(
ProvideFoo,
ProvideBar,
FooBar{})
``` ```
A generated injector for `FooBar` would look like this: A generated injector for `FooBar` would look like this:
```go ```go
func injectFooBar() FooBar { func injectFooBar() FooBar {
foo := provideFoo() foo := ProvideFoo()
bar := provideBar() bar := ProvideBar()
fooBar := FooBar{ fooBar := FooBar{
Foo: foo, Foo: foo,
Bar: bar, Bar: bar,
@@ -300,8 +309,6 @@ this to either return an aggregated cleanup function to the caller or to clean
up the resource if a later provider returns an error. up the resource if a later provider returns an error.
```go ```go
//goose:provide
func provideFile(log Logger, path Path) (*os.File, func(), error) { func provideFile(log Logger, path Path) (*os.File, func(), error) {
f, err := os.Open(string(path)) f, err := os.Open(string(path))
if err != nil { if err != nil {

View File

@@ -13,6 +13,7 @@ import (
"path/filepath" "path/filepath"
"reflect" "reflect"
"sort" "sort"
"strconv"
"strings" "strings"
"codename/goose/internal/goose" "codename/goose/internal/goose"
@@ -71,9 +72,9 @@ func generate(pkg string) error {
// show runs the show subcommand. // show runs the show subcommand.
// //
// Given one or more packages, show will find all the declared provider // Given one or more packages, show will find all the provider sets
// sets and print what other provider sets it imports and what outputs // declared as top-level variables and print what other provider sets it
// it can produce, given possible inputs. // imports and what outputs it can produce, given possible inputs.
func show(pkgs ...string) error { func show(pkgs ...string) error {
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
@@ -89,11 +90,12 @@ func show(pkgs ...string) error {
} }
sort.Slice(keys, func(i, j int) bool { sort.Slice(keys, func(i, j int) bool {
if keys[i].ImportPath == keys[j].ImportPath { if keys[i].ImportPath == keys[j].ImportPath {
return keys[i].Name < keys[j].Name return keys[i].VarName < keys[j].VarName
} }
return keys[i].ImportPath < keys[j].ImportPath return keys[i].ImportPath < keys[j].ImportPath
}) })
// ANSI color codes. // ANSI color codes.
// TODO(light): Possibly use github.com/fatih/color?
const ( const (
reset = "\x1b[0m" reset = "\x1b[0m"
redBold = "\x1b[0;1;31m" redBold = "\x1b[0;1;31m"
@@ -116,7 +118,7 @@ func show(pkgs ...string) error {
switch v := v.(type) { switch v := v.(type) {
case *goose.Provider: case *goose.Provider:
out[types.TypeString(t, nil)] = v.Pos out[types.TypeString(t, nil)] = v.Pos
case goose.IfaceBinding: case *goose.IfaceBinding:
out[types.TypeString(t, nil)] = v.Pos out[types.TypeString(t, nil)] = v.Pos
default: default:
panic("unreachable") panic("unreachable")
@@ -134,19 +136,19 @@ func show(pkgs ...string) error {
type outGroup struct { type outGroup struct {
name string name string
inputs *typeutil.Map // values are not important inputs *typeutil.Map // values are not important
outputs *typeutil.Map // values are either *goose.Provider or goose.IfaceBinding outputs *typeutil.Map // values are either *goose.Provider or *goose.IfaceBinding
} }
// gather flattens a provider set into outputs grouped by the inputs // gather flattens a provider set into outputs grouped by the inputs
// required to create them. As it flattens the provider set, it records // required to create them. As it flattens the provider set, it records
// the visited provider sets as imports. // the visited named provider sets as imports.
func gather(info *goose.Info, key goose.ProviderSetID) (_ []outGroup, imports map[string]struct{}) { func gather(info *goose.Info, key goose.ProviderSetID) (_ []outGroup, imports map[string]struct{}) {
hash := typeutil.MakeHasher() hash := typeutil.MakeHasher()
// Map types to providers and bindings. // Map types to providers and bindings.
pm := new(typeutil.Map) pm := new(typeutil.Map)
pm.SetHasher(hash) pm.SetHasher(hash)
next := []goose.ProviderSetID{key} next := []*goose.ProviderSet{info.Sets[key]}
visited := make(map[goose.ProviderSetID]struct{}) visited := make(map[*goose.ProviderSet]struct{})
imports = make(map[string]struct{}) imports = make(map[string]struct{})
for len(next) > 0 { for len(next) > 0 {
curr := next[len(next)-1] curr := next[len(next)-1]
@@ -155,18 +157,17 @@ func gather(info *goose.Info, key goose.ProviderSetID) (_ []outGroup, imports ma
continue continue
} }
visited[curr] = struct{}{} visited[curr] = struct{}{}
if curr != key { if curr.Name != "" && !(curr.PkgPath == key.ImportPath && curr.Name == key.VarName) {
imports[curr.String()] = struct{}{} imports[formatProviderSetName(curr.PkgPath, curr.Name)] = struct{}{}
} }
set := info.All[curr] for _, p := range curr.Providers {
for _, p := range set.Providers {
pm.Set(p.Out, p) pm.Set(p.Out, p)
} }
for _, b := range set.Bindings { for _, b := range curr.Bindings {
pm.Set(b.Iface, b) pm.Set(b.Iface, b)
} }
for _, imp := range set.Imports { for _, imp := range curr.Imports {
next = append(next, imp.ProviderSetID) next = append(next, imp)
} }
} }
@@ -238,7 +239,7 @@ func gather(info *goose.Info, key goose.ProviderSetID) (_ []outGroup, imports ma
inputs: in, inputs: in,
outputs: out, outputs: out,
}) })
case goose.IfaceBinding: case *goose.IfaceBinding:
i, ok := inputVisited.At(p.Provided).(int) i, ok := inputVisited.At(p.Provided).(int)
if !ok { if !ok {
stk = append(stk, curr, p.Provided) stk = append(stk, curr, p.Provided)
@@ -327,3 +328,8 @@ func sortSet(set interface{}) []string {
sort.Strings(a) sort.Strings(a)
return a return a
} }
func formatProviderSetName(importPath, varName string) string {
// Since varName is an identifier, it doesn't make sense to quote.
return strconv.Quote(importPath) + "." + varName
}

38
goose.go Normal file
View File

@@ -0,0 +1,38 @@
// Package goose contains directives for goose code generation.
package goose
// ProviderSet is a marker type that collects a group of providers.
type ProviderSet struct{}
// NewSet creates a new provider set that includes the providers in
// its arguments. Each argument is either an exported function value,
// an exported struct (zero) value, or a call to Bind.
func NewSet(...interface{}) ProviderSet {
return ProviderSet{}
}
// Use is placed in the body of an injector function to declare the
// providers to use. Its arguments are the same as NewSet. Its return
// value is an error message that can be sent to panic.
//
// Example:
//
// func injector(ctx context.Context) (*sql.DB, error) {
// panic(Use(otherpkg.Foo, myProviderFunc, goose.Bind()))
// }
func Use(...interface{}) string {
return "implementation not generated, run goose"
}
// A Binding maps an interface to a concrete type.
type Binding struct{}
// Bind declares that a concrete type should be used to satisfy a
// dependency on iface.
//
// Example:
//
// var MySet = goose.NewSet(goose.Bind(MyInterface(nil), new(MyStruct)))
func Bind(iface, to interface{}) Binding {
return Binding{}
}

View File

@@ -42,7 +42,7 @@ type call struct {
// solve finds the sequence of calls required to produce an output type // solve finds the sequence of calls required to produce an output type
// with an optional set of provided inputs. // with an optional set of provided inputs.
func solve(mc *providerSetCache, out types.Type, given []types.Type, sets []symref) ([]call, error) { func solve(fset *token.FileSet, out types.Type, given []types.Type, set *ProviderSet) ([]call, error) {
for i, g := range given { for i, g := range given {
for _, h := range given[:i] { for _, h := range given[:i] {
if types.Identical(g, h) { if types.Identical(g, h) {
@@ -50,7 +50,7 @@ func solve(mc *providerSetCache, out types.Type, given []types.Type, sets []symr
} }
} }
} }
providers, err := buildProviderMap(mc, sets) providers, err := buildProviderMap(fset, set)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -61,7 +61,7 @@ func solve(mc *providerSetCache, out types.Type, given []types.Type, sets []symr
for i, g := range given { for i, g := range given {
if p := providers.At(g); p != nil { if p := providers.At(g); p != nil {
pp := p.(*Provider) pp := p.(*Provider)
return nil, fmt.Errorf("input of %s conflicts with provider %s at %s", types.TypeString(g, nil), pp.Name, mc.fset.Position(pp.Pos)) return nil, fmt.Errorf("input of %s conflicts with provider %s at %s", types.TypeString(g, nil), pp.Name, fset.Position(pp.Pos))
} }
index.Set(g, i) index.Set(g, i)
} }
@@ -135,88 +135,70 @@ func solve(mc *providerSetCache, out types.Type, given []types.Type, sets []symr
return calls, nil return calls, nil
} }
func buildProviderMap(mc *providerSetCache, sets []symref) (*typeutil.Map, error) { func buildProviderMap(fset *token.FileSet, set *ProviderSet) (*typeutil.Map, error) {
type nextEnt struct {
to symref
from symref
pos token.Pos
}
type binding struct { type binding struct {
IfaceBinding *IfaceBinding
pset symref set *ProviderSet
from symref
} }
pm := new(typeutil.Map) // to *providerInfo providerMap := new(typeutil.Map) // to *Provider
setMap := new(typeutil.Map) // to *ProviderSet, for error messages
var bindings []binding var bindings []binding
visited := make(map[symref]struct{}) visited := make(map[*ProviderSet]struct{})
var next []nextEnt next := []*ProviderSet{set}
for _, ref := range sets {
next = append(next, nextEnt{to: ref})
}
for len(next) > 0 { for len(next) > 0 {
curr := next[0] curr := next[0]
copy(next, next[1:]) copy(next, next[1:])
next = next[:len(next)-1] next = next[:len(next)-1]
if _, skip := visited[curr.to]; skip { if _, skip := visited[curr]; skip {
continue continue
} }
visited[curr.to] = struct{}{} visited[curr] = struct{}{}
pset, err := mc.get(curr.to) for _, p := range curr.Providers {
if err != nil { if providerMap.At(p.Out) != nil {
if !curr.pos.IsValid() { return nil, bindingConflictError(fset, p.Pos, p.Out, setMap.At(p.Out).(*ProviderSet))
return nil, err
} }
return nil, fmt.Errorf("%v: %v", mc.fset.Position(curr.pos), err) providerMap.Set(p.Out, p)
setMap.Set(p.Out, curr)
} }
for _, p := range pset.Providers { for _, b := range curr.Bindings {
if prev := pm.At(p.Out); prev != nil {
pos := mc.fset.Position(p.Pos)
typ := types.TypeString(p.Out, nil)
prevPos := mc.fset.Position(prev.(*Provider).Pos)
if curr.from.importPath == "" {
// Provider set is imported directly by injector.
return nil, fmt.Errorf("%v: multiple bindings for %s (added by injector, previous binding at %v)", pos, typ, prevPos)
}
return nil, fmt.Errorf("%v: multiple bindings for %s (imported by %v, previous binding at %v)", pos, typ, curr.from, prevPos)
}
pm.Set(p.Out, p)
}
for _, b := range pset.Bindings {
bindings = append(bindings, binding{ bindings = append(bindings, binding{
IfaceBinding: b, IfaceBinding: b,
pset: curr.to, set: curr,
from: curr.from,
}) })
} }
for _, imp := range pset.Imports { for _, imp := range curr.Imports {
next = append(next, nextEnt{to: imp.symref(), from: curr.to, pos: imp.Pos}) next = append(next, imp)
} }
} }
// Validate that bindings have their concrete type provided in the set.
// TODO(light): Move this validation up into provider set creation.
for _, b := range bindings { for _, b := range bindings {
if prev := pm.At(b.Iface); prev != nil { if providerMap.At(b.Iface) != nil {
pos := mc.fset.Position(b.Pos) return nil, bindingConflictError(fset, b.Pos, b.Iface, setMap.At(b.Iface).(*ProviderSet))
typ := types.TypeString(b.Iface, nil)
// TODO(light): Error message for conflicting with another interface binding will point at provider instead of binding.
prevPos := mc.fset.Position(prev.(*Provider).Pos)
if b.from.importPath == "" {
// Provider set is imported directly by injector.
return nil, fmt.Errorf("%v: multiple bindings for %s (added by injector, previous binding at %v)", pos, typ, prevPos)
}
return nil, fmt.Errorf("%v: multiple bindings for %s (imported by %v, previous binding at %v)", pos, typ, b.from, prevPos)
} }
concrete := pm.At(b.Provided) concrete := providerMap.At(b.Provided)
if concrete == nil { if concrete == nil {
pos := mc.fset.Position(b.Pos) pos := fset.Position(b.Pos)
typ := types.TypeString(b.Provided, nil) typ := types.TypeString(b.Provided, nil)
if b.from.importPath == "" { return nil, fmt.Errorf("%v: no binding for %s", pos, typ)
// Concrete provider is imported directly by injector.
return nil, fmt.Errorf("%v: no binding for %s", pos, typ)
}
return nil, fmt.Errorf("%v: no binding for %s (imported by %v)", pos, typ, b.from)
} }
pm.Set(b.Iface, concrete) providerMap.Set(b.Iface, concrete)
setMap.Set(b.Iface, b.set)
} }
return pm, nil return providerMap, nil
}
// bindingConflictError creates a new error describing multiple bindings
// for the same output type.
func bindingConflictError(fset *token.FileSet, pos token.Pos, typ types.Type, prevSet *ProviderSet) error {
position := fset.Position(pos)
typString := types.TypeString(typ, nil)
if prevSet.Name == "" {
prevPosition := fset.Position(prevSet.Pos)
return fmt.Errorf("%v: multiple bindings for %s (previous binding at %v)",
position, typString, prevPosition)
}
return fmt.Errorf("%v: multiple bindings for %s (previous binding in %q.%s)",
position, typString, prevSet.PkgPath, prevSet.Name)
} }

View File

@@ -8,7 +8,7 @@ import (
"go/ast" "go/ast"
"go/build" "go/build"
"go/format" "go/format"
"go/parser" "go/token"
"go/types" "go/types"
"sort" "sort"
"strconv" "strconv"
@@ -22,8 +22,24 @@ import (
// Generate performs dependency injection for a single package, // Generate performs dependency injection for a single package,
// returning the gofmt'd Go source code. // returning the gofmt'd Go source code.
func Generate(bctx *build.Context, wd string, pkg string) ([]byte, error) { func Generate(bctx *build.Context, wd string, pkg string) ([]byte, error) {
conf := newLoaderConfig(bctx, wd, true) mainPkg, err := bctx.Import(pkg, wd, build.FindOnly)
if err != nil {
return nil, fmt.Errorf("load: %v", err)
}
// TODO(light): Stop errors from printing to stderr.
conf := &loader.Config{
Build: new(build.Context),
Cwd: wd,
TypeCheckFuncBodies: func(path string) bool {
return path == mainPkg.ImportPath
},
}
*conf.Build = *bctx
n := len(conf.Build.BuildTags)
// TODO(light): Only apply gooseinject build tag on main package.
conf.Build.BuildTags = append(conf.Build.BuildTags[:n:n], "gooseinject")
conf.Import(pkg) conf.Import(pkg)
prog, err := conf.Load() prog, err := conf.Load()
if err != nil { if err != nil {
return nil, fmt.Errorf("load: %v", err) return nil, fmt.Errorf("load: %v", err)
@@ -34,47 +50,23 @@ func Generate(bctx *build.Context, wd string, pkg string) ([]byte, error) {
} }
pkgInfo := prog.InitialPackages()[0] pkgInfo := prog.InitialPackages()[0]
g := newGen(prog, pkgInfo.Pkg.Path()) g := newGen(prog, pkgInfo.Pkg.Path())
r := newImportResolver(conf, prog.Fset) oc := newObjectCache(prog)
mc := newProviderSetCache(prog, r)
for _, f := range pkgInfo.Files { for _, f := range pkgInfo.Files {
if !isInjectFile(f) {
continue
}
fileScope := pkgInfo.Scopes[f]
groups := parseFile(prog.Fset, f)
for _, decl := range f.Decls { for _, decl := range f.Decls {
fn, ok := decl.(*ast.FuncDecl) fn, ok := decl.(*ast.FuncDecl)
if !ok { if !ok {
continue continue
} }
var dg directiveGroup useCall := isInjector(&pkgInfo.Info, fn)
for _, dg = range groups { if useCall == nil {
if dg.decl == decl { continue
break
}
} }
if dg.decl != decl { set, err := oc.processNewSet(pkgInfo, useCall)
dg = directiveGroup{} if err != nil {
} return nil, fmt.Errorf("%v: %v", prog.Fset.Position(fn.Pos()), err)
var sets []symref
for _, d := range dg.dirs {
if d.kind != "use" {
return nil, fmt.Errorf("%v: cannot use %s directive on inject function", prog.Fset.Position(d.pos), d.kind)
}
args := d.args()
if len(args) == 0 {
return nil, fmt.Errorf("%v: goose:use must have at least one provider set reference", prog.Fset.Position(d.pos))
}
for _, arg := range args {
ref, err := parseSymbolRef(r, arg, fileScope, g.currPackage, d.pos)
if err != nil {
return nil, fmt.Errorf("%v: %v", prog.Fset.Position(d.pos), err)
}
sets = append(sets, ref)
}
} }
sig := pkgInfo.ObjectOf(fn.Name).Type().(*types.Signature) sig := pkgInfo.ObjectOf(fn.Name).Type().(*types.Signature)
if err := g.inject(mc, fn.Name.Name, sig, sets); err != nil { if err := g.inject(prog.Fset, fn.Name.Name, sig, set); err != nil {
return nil, fmt.Errorf("%v: %v", prog.Fset.Position(fn.Pos()), err) return nil, fmt.Errorf("%v: %v", prog.Fset.Position(fn.Pos()), err)
} }
} }
@@ -89,23 +81,6 @@ func Generate(bctx *build.Context, wd string, pkg string) ([]byte, error) {
return fmtSrc, nil return fmtSrc, nil
} }
func newLoaderConfig(bctx *build.Context, wd string, inject bool) *loader.Config {
// TODO(light): Stop errors from printing to stderr.
conf := &loader.Config{
Build: bctx,
ParserMode: parser.ParseComments,
Cwd: wd,
TypeCheckFuncBodies: func(string) bool { return false },
}
if inject {
conf.Build = new(build.Context)
*conf.Build = *bctx
n := len(conf.Build.BuildTags)
conf.Build.BuildTags = append(conf.Build.BuildTags[:n:n], "gooseinject")
}
return conf
}
// gen is the generator state. // gen is the generator state.
type gen struct { type gen struct {
currPackage string currPackage string
@@ -150,7 +125,7 @@ func (g *gen) frame() []byte {
} }
// inject emits the code for an injector. // inject emits the code for an injector.
func (g *gen) inject(mc *providerSetCache, name string, sig *types.Signature, sets []symref) error { func (g *gen) inject(fset *token.FileSet, name string, sig *types.Signature, set *ProviderSet) error {
results := sig.Results() results := sig.Results()
var returnsCleanup, returnsErr bool var returnsCleanup, returnsErr bool
switch results.Len() { switch results.Len() {
@@ -184,7 +159,7 @@ func (g *gen) inject(mc *providerSetCache, name string, sig *types.Signature, se
for i := 0; i < params.Len(); i++ { for i := 0; i < params.Len(); i++ {
given[i] = params.At(i).Type() given[i] = params.At(i).Type()
} }
calls, err := solve(mc, outType, given, sets) calls, err := solve(fset, outType, given, set)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -29,13 +29,19 @@ func TestGoose(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// The marker function package source is needed to have the test cases
// type check. loadTestCase places this file at the well-known import path.
gooseGo, err := ioutil.ReadFile(filepath.Join("..", "..", "goose.go"))
if err != nil {
t.Fatal(err)
}
tests := make([]*testCase, 0, len(testdataEnts)) tests := make([]*testCase, 0, len(testdataEnts))
for _, ent := range testdataEnts { for _, ent := range testdataEnts {
name := ent.Name() name := ent.Name()
if !ent.IsDir() || strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") { if !ent.IsDir() || strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") {
continue continue
} }
test, err := loadTestCase(filepath.Join(testRoot, name)) test, err := loadTestCase(filepath.Join(testRoot, name), gooseGo)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@@ -227,7 +233,7 @@ type testCase struct {
// out.txt file containing the expected output, or the magic string "ERROR" // out.txt file containing the expected output, or the magic string "ERROR"
// if this test should cause generation to fail // if this test should cause generation to fail
// ... any Go files found recursively placed under GOPATH/src/... // ... any Go files found recursively placed under GOPATH/src/...
func loadTestCase(root string) (*testCase, error) { func loadTestCase(root string, gooseGoSrc []byte) (*testCase, error) {
name := filepath.Base(root) name := filepath.Base(root)
pkg, err := ioutil.ReadFile(filepath.Join(root, "pkg")) pkg, err := ioutil.ReadFile(filepath.Join(root, "pkg"))
if err != nil { if err != nil {
@@ -242,7 +248,9 @@ func loadTestCase(root string) (*testCase, error) {
wantError = true wantError = true
out = nil out = nil
} }
goFiles := make(map[string][]byte) goFiles := map[string][]byte{
"codename/goose/goose.go": gooseGoSrc,
}
err = filepath.Walk(root, func(src string, info os.FileInfo, err error) error { err = filepath.Walk(root, func(src string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return err

File diff suppressed because it is too large Load Diff

View File

@@ -1,37 +0,0 @@
package goose
import (
"testing"
)
func TestDirectiveArgs(t *testing.T) {
tests := []struct {
line string
args []string
}{
{"", []string{}},
{" \t ", []string{}},
{"foo", []string{"foo"}},
{"foo bar", []string{"foo", "bar"}},
{" foo \t bar ", []string{"foo", "bar"}},
{"foo \"bar \t baz\" fido", []string{"foo", "\"bar \t baz\"", "fido"}},
{"foo \"bar \t baz\".quux fido", []string{"foo", "\"bar \t baz\".quux", "fido"}},
}
eq := func(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
for _, test := range tests {
got := (directive{line: test.line}).args()
if !eq(got, test.args) {
t.Errorf("directive{line: %q}.args() = %q; want %q", test.line, got, test.args)
}
}
}

View File

@@ -1,6 +1,10 @@
package main package main
import "fmt" import (
"fmt"
"codename/goose"
)
func main() { func main() {
fmt.Println(injectFooBar()) fmt.Println(injectFooBar())
@@ -9,12 +13,14 @@ func main() {
type Foo int type Foo int
type FooBar int type FooBar int
//goose:provide Set var Set = goose.NewSet(
provideFoo,
provideFooBar)
func provideFoo() Foo { func provideFoo() Foo {
return 41 return 41
} }
//goose:provide Set
func provideFooBar(foo Foo) FooBar { func provideFooBar(foo Foo) FooBar {
return FooBar(foo) + 1 return FooBar(foo) + 1
} }

View File

@@ -2,6 +2,10 @@
package main package main
//goose:use Set import (
"codename/goose"
)
func injectFooBar() FooBar func injectFooBar() FooBar {
panic(goose.Use(Set))
}

View File

@@ -1,6 +1,8 @@
package main package main
import "fmt" import (
"fmt"
)
func main() { func main() {
bar, cleanup := injectBar() bar, cleanup := injectBar()
@@ -12,14 +14,12 @@ func main() {
type Foo int type Foo int
type Bar int type Bar int
//goose:provide Foo
func provideFoo() (*Foo, func()) { func provideFoo() (*Foo, func()) {
foo := new(Foo) foo := new(Foo)
*foo = 42 *foo = 42
return foo, func() { *foo = 0 } return foo, func() { *foo = 0 }
} }
//goose:provide Bar
func provideBar(foo *Foo) (*Bar, func()) { func provideBar(foo *Foo) (*Bar, func()) {
bar := new(Bar) bar := new(Bar)
*bar = 77 *bar = 77

View File

@@ -2,7 +2,10 @@
package main package main
//goose:use Foo import (
//goose:use Bar "codename/goose"
)
func injectBar() (*Bar, func()) func injectBar() (*Bar, func()) {
panic(goose.Use(provideFoo, provideBar))
}

View File

@@ -0,0 +1,11 @@
package main
import (
"fmt"
)
func main() {
fmt.Println(injectedMessage())
}
var myFakeSet struct{}

View File

@@ -0,0 +1,11 @@
//+build gooseinject
package main
import (
"codename/goose"
)
func injectedMessage() string {
panic(goose.Use(myFakeSet))
}

View File

@@ -3,7 +3,8 @@ package main
import ( import (
"fmt" "fmt"
_ "foo" "codename/goose"
"foo"
) )
func main() { func main() {
@@ -16,11 +17,12 @@ func (b *Bar) Foo() string {
return string(*b) return string(*b)
} }
//goose:provide
func provideBar() *Bar { func provideBar() *Bar {
b := new(Bar) b := new(Bar)
*b = "Hello, World!" *b = "Hello, World!"
return b return b
} }
//goose:bind provideBar "foo".Fooer *Bar var Set = goose.NewSet(
provideBar,
goose.Bind(foo.Fooer(nil), (*Bar)(nil)))

View File

@@ -2,8 +2,11 @@
package main package main
import "foo" import (
"codename/goose"
"foo"
)
//goose:use provideBar func injectFooer() foo.Fooer {
panic(goose.Use(Set))
func injectFooer() foo.Fooer }

View File

@@ -1,6 +1,10 @@
package main package main
import "fmt" import (
"fmt"
"codename/goose"
)
func main() { func main() {
fmt.Println(injectFooBar(40)) fmt.Println(injectFooBar(40))
@@ -10,12 +14,14 @@ type Foo int
type Bar int type Bar int
type FooBar int type FooBar int
//goose:provide Set var Set = goose.NewSet(
provideBar,
provideFooBar)
func provideBar() Bar { func provideBar() Bar {
return 2 return 2
} }
//goose:provide Set
func provideFooBar(foo Foo, bar Bar) FooBar { func provideFooBar(foo Foo, bar Bar) FooBar {
return FooBar(foo) + FooBar(bar) return FooBar(foo) + FooBar(bar)
} }

View File

@@ -2,6 +2,10 @@
package main package main
//goose:use Set import (
"codename/goose"
)
func injectFooBar(foo Foo) FooBar func injectFooBar(foo Foo) FooBar {
panic(goose.Use(Set))
}

View File

@@ -1,6 +1,10 @@
package main package main
import "fmt" import (
"fmt"
"codename/goose"
)
func main() { func main() {
// I'm on the fence as to whether this should be an error (versus an // I'm on the fence as to whether this should be an error (versus an
@@ -12,12 +16,14 @@ func main() {
type Foo int type Foo int
type Bar int type Bar int
//goose:provide Set var Set = goose.NewSet(
provideFoo,
provideBar)
func provideFoo() Foo { func provideFoo() Foo {
return -888 return -888
} }
//goose:provide Set
func provideBar(foo Foo) Bar { func provideBar(foo Foo) Bar {
return 2 return 2
} }

View File

@@ -2,6 +2,10 @@
package main package main
//goose:use Set import (
"codename/goose"
)
func injectBar(foo Foo) Bar func injectBar(foo Foo) Bar {
panic(goose.Use(Set))
}

View File

@@ -1,6 +1,10 @@
package main package main
import "fmt" import (
"fmt"
"codename/goose"
)
func main() { func main() {
fmt.Println(injectFooer().Foo()) fmt.Println(injectFooer().Foo())
@@ -16,11 +20,12 @@ func (b *Bar) Foo() string {
return string(*b) return string(*b)
} }
//goose:provide
func provideBar() *Bar { func provideBar() *Bar {
b := new(Bar) b := new(Bar)
*b = "Hello, World!" *b = "Hello, World!"
return b return b
} }
//goose:bind provideBar Fooer *Bar var Set = goose.NewSet(
provideBar,
goose.Bind(Fooer(nil), (*Bar)(nil)))

View File

@@ -2,6 +2,10 @@
package main package main
//goose:use provideBar import (
"codename/goose"
)
func injectFooer() Fooer func injectFooer() Fooer {
panic(goose.Use(Set))
}

View File

@@ -28,8 +28,6 @@ func (b *Bar) Foo() string {
return string(*b) return string(*b)
} }
//goose:provide
//goose:bind provideBar Fooer *Bar
func provideBar() *Bar { func provideBar() *Bar {
mu.Lock() mu.Lock()
provideBarCalls++ provideBarCalls++
@@ -44,7 +42,6 @@ var (
provideBarCalls int provideBarCalls int
) )
//goose:provide
func provideFooBar(fooer Fooer, bar *Bar) FooBar { func provideFooBar(fooer Fooer, bar *Bar) FooBar {
return FooBar{fooer, bar} return FooBar{fooer, bar}
} }

View File

@@ -2,7 +2,13 @@
package main package main
//goose:use provideBar import (
//goose:use provideFooBar "codename/goose"
)
func injectFooBar() FooBar func injectFooBar() FooBar {
panic(goose.Use(
provideBar,
provideFooBar,
goose.Bind(Fooer(nil), (*Bar)(nil))))
}

View File

@@ -1,14 +0,0 @@
package main
import "fmt"
func main() {
fmt.Println(injectedMessage())
}
//goose:provide Set
// provideMessage provides a friendly user greeting.
func provideMessage() string {
return "Hello, World!"
}

View File

@@ -1,5 +0,0 @@
//+build gooseinject
package main
func injectedMessage() string

View File

@@ -1,22 +0,0 @@
package main
import "fmt"
func main() {
fmt.Println(injectFooBar())
}
type Foo int
type FooBar int
//goose:provide Foo
func provideFoo() Foo {
return 41
}
//goose:provide FooBar
func provideFooBar(foo Foo) FooBar {
return FooBar(foo) + 1
}
//goose:import Set Foo FooBar

View File

@@ -1,7 +0,0 @@
//+build gooseinject
package main
//goose:use Set
func injectFooBar() FooBar

View File

@@ -1 +0,0 @@
42

View File

@@ -1 +0,0 @@
foo

View File

@@ -1,20 +0,0 @@
package main
import "fmt"
func main() {
fmt.Println(injectFooBar())
}
type Foo int
type FooBar int
//goose:provide Foo
func provideFoo() Foo {
return 41
}
//goose:provide FooBar
func provideFooBar(foo Foo) FooBar {
return FooBar(foo) + 1
}

View File

@@ -1,7 +0,0 @@
//+build gooseinject
package main
//goose:use Foo FooBar
func injectFooBar() FooBar

View File

@@ -1 +0,0 @@
42

View File

@@ -1 +0,0 @@
foo

View File

@@ -17,8 +17,6 @@ func main() {
fmt.Println(c) fmt.Println(c)
} }
//goose:provide
func provide(ctx stdcontext.Context) (context, error) { func provide(ctx stdcontext.Context) (context, error) {
return context{}, nil return context{}, nil
} }

View File

@@ -4,8 +4,10 @@ package main
import ( import (
stdcontext "context" stdcontext "context"
"codename/goose"
) )
//goose:use provide func inject(context stdcontext.Context, err struct{}) (context, error) {
panic(goose.Use(provide))
func inject(context stdcontext.Context, err struct{}) (context, error) }

View File

@@ -6,8 +6,6 @@ func main() {
fmt.Println(injectedMessage()) fmt.Println(injectedMessage())
} }
//goose:provide
// provideMessage provides a friendly user greeting. // provideMessage provides a friendly user greeting.
func provideMessage() string { func provideMessage() string {
return "Hello, World!" return "Hello, World!"

View File

@@ -2,6 +2,10 @@
package main package main
//goose:use provideMessage import (
"codename/goose"
)
func injectedMessage() string func injectedMessage() string {
panic(goose.Use(provideMessage))
}

View File

@@ -16,7 +16,6 @@ func (b Bar) Foo() string {
return string(b) return string(b)
} }
//goose:provide
func provideBar() Bar { func provideBar() Bar {
return "Hello, World!" return "Hello, World!"
} }

View File

@@ -2,6 +2,10 @@
package main package main
//goose:use provideBar import (
"codename/goose"
)
func injectFooer() Fooer func injectFooer() Fooer {
panic(goose.Use(provideBar))
}

View File

@@ -17,8 +17,6 @@ func main() {
fmt.Println(c) fmt.Println(c)
} }
//goose:provide
func provide(ctx stdcontext.Context) (context, error) { func provide(ctx stdcontext.Context) (context, error) {
return context{}, nil return context{}, nil
} }

View File

@@ -4,11 +4,13 @@ package main
import ( import (
stdcontext "context" stdcontext "context"
"codename/goose"
) )
// The notable characteristic of this test is that there are no // The notable characteristic of this test is that there are no
// parameter names on the inject stub. // parameter names on the inject stub.
//goose:use provide func inject(stdcontext.Context, struct{}) (context, error) {
panic(goose.Use(provide))
func inject(stdcontext.Context, struct{}) (context, error) }

View File

@@ -25,14 +25,12 @@ type Foo int
type Bar int type Bar int
type Baz int type Baz int
//goose:provide Foo
func provideFoo() (*Foo, func()) { func provideFoo() (*Foo, func()) {
foo := new(Foo) foo := new(Foo)
*foo = 42 *foo = 42
return foo, func() { *foo = 0; cleanedFoo = true } return foo, func() { *foo = 0; cleanedFoo = true }
} }
//goose:provide Bar
func provideBar(foo *Foo) (*Bar, func(), error) { func provideBar(foo *Foo) (*Bar, func(), error) {
bar := new(Bar) bar := new(Bar)
*bar = 77 *bar = 77
@@ -45,7 +43,6 @@ func provideBar(foo *Foo) (*Bar, func(), error) {
}, nil }, nil
} }
//goose:provide Baz
func provideBaz(bar *Bar) (Baz, error) { func provideBaz(bar *Bar) (Baz, error) {
return 0, errors.New("bork!") return 0, errors.New("bork!")
} }

View File

@@ -2,8 +2,10 @@
package main package main
//goose:use Foo import (
//goose:use Bar "codename/goose"
//goose:use Baz )
func injectBaz() (Baz, func(), error) func injectBaz() (Baz, func(), error) {
panic(goose.Use(provideFoo, provideBar, provideBaz))
}

View File

@@ -2,7 +2,6 @@ package bar
type Bar int type Bar int
//goose:provide Bar
func ProvideBar() Bar { func ProvideBar() Bar {
return 1 return 1
} }

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"bar" "bar"
"codename/goose"
) )
func main() { func main() {
@@ -13,14 +14,15 @@ func main() {
type Foo int type Foo int
type FooBar int type FooBar int
//goose:provide Set var Set = goose.NewSet(
provideFoo,
bar.ProvideBar,
provideFooBar)
func provideFoo() Foo { func provideFoo() Foo {
return 41 return 41
} }
//goose:import Set "bar".Bar
//goose:provide Set
func provideFooBar(foo Foo, barVal bar.Bar) FooBar { func provideFooBar(foo Foo, barVal bar.Bar) FooBar {
return FooBar(foo) + FooBar(barVal) return FooBar(foo) + FooBar(barVal)
} }

View File

@@ -2,6 +2,10 @@
package main package main
//goose:use Set import (
"codename/goose"
)
func injectFooBar() FooBar func injectFooBar() FooBar {
panic(goose.Use(Set))
}

View File

@@ -1,8 +1,12 @@
package main package main
import "errors" import (
import "fmt" "errors"
import "strings" "fmt"
"strings"
"codename/goose"
)
func main() { func main() {
foo, err := injectFoo() foo, err := injectFoo()
@@ -16,7 +20,8 @@ func main() {
type Foo int type Foo int
//goose:provide Set
func provideFoo() (Foo, error) { func provideFoo() (Foo, error) {
return 42, errors.New("there is no Foo") return 42, errors.New("there is no Foo")
} }
var Set = goose.NewSet(provideFoo)

View File

@@ -2,6 +2,10 @@
package main package main
//goose:use Set import (
"codename/goose"
)
func injectFoo() (Foo, error) func injectFoo() (Foo, error) {
panic(goose.Use(Set))
}

View File

@@ -1,6 +1,10 @@
package main package main
import "fmt" import (
"fmt"
"codename/goose"
)
func main() { func main() {
fb := injectFooBar() fb := injectFooBar()
@@ -10,18 +14,20 @@ func main() {
type Foo int type Foo int
type Bar int type Bar int
//goose:provide Set
type FooBar struct { type FooBar struct {
Foo Foo Foo Foo
Bar Bar Bar Bar
} }
//goose:provide Set
func provideFoo() Foo { func provideFoo() Foo {
return 41 return 41
} }
//goose:provide Set
func provideBar() Bar { func provideBar() Bar {
return 1 return 1
} }
var Set = goose.NewSet(
FooBar{},
provideFoo,
provideBar)

View File

@@ -2,6 +2,10 @@
package main package main
//goose:use Set import (
"codename/goose"
)
func injectFooBar() FooBar func injectFooBar() FooBar {
panic(goose.Use(Set))
}

View File

@@ -1,6 +1,10 @@
package main package main
import "fmt" import (
"fmt"
"codename/goose"
)
func main() { func main() {
fb := injectFooBar() fb := injectFooBar()
@@ -10,18 +14,20 @@ func main() {
type Foo int type Foo int
type Bar int type Bar int
//goose:provide Set
type FooBar struct { type FooBar struct {
Foo Foo Foo Foo
Bar Bar Bar Bar
} }
//goose:provide Set
func provideFoo() Foo { func provideFoo() Foo {
return 41 return 41
} }
//goose:provide Set
func provideBar() Bar { func provideBar() Bar {
return 1 return 1
} }
var Set = goose.NewSet(
FooBar{},
provideFoo,
provideBar)

View File

@@ -2,6 +2,10 @@
package main package main
//goose:use Set import (
"codename/goose"
)
func injectFooBar() *FooBar func injectFooBar() *FooBar {
panic(goose.Use(Set))
}

View File

@@ -1,6 +1,10 @@
package main package main
import "fmt" import (
"fmt"
"codename/goose"
)
func main() { func main() {
fmt.Println(injectFooBar()) fmt.Println(injectFooBar())
@@ -10,17 +14,19 @@ type Foo int
type Bar int type Bar int
type FooBar int type FooBar int
//goose:provide Set
func provideFoo() Foo { func provideFoo() Foo {
return 40 return 40
} }
//goose:provide Set
func provideBar() Bar { func provideBar() Bar {
return 2 return 2
} }
//goose:provide Set
func provideFooBar(foo Foo, bar Bar) FooBar { func provideFooBar(foo Foo, bar Bar) FooBar {
return FooBar(foo) + FooBar(bar) return FooBar(foo) + FooBar(bar)
} }
var Set = goose.NewSet(
provideFoo,
provideBar,
provideFooBar)

View File

@@ -2,6 +2,10 @@
package main package main
//goose:use Set import (
"codename/goose"
)
func injectFooBar() FooBar func injectFooBar() FooBar {
panic(goose.Use(Set))
}

View File

@@ -3,9 +3,10 @@
package main package main
import ( import (
_ "bar" "bar"
"codename/goose"
) )
//goose:use "bar".Message func injectedMessage() string {
panic(goose.Use(bar.ProvideMessage))
func injectedMessage() string }

View File

@@ -1,8 +1,6 @@
// Package bar is the vendored copy of bar which contains the real provider. // Package bar is the vendored copy of bar which contains the real provider.
package bar package bar
//goose:provide Message
// ProvideMessage provides a friendly user greeting. // ProvideMessage provides a friendly user greeting.
func ProvideMessage() string { func ProvideMessage() string {
return "Hello, World!" return "Hello, World!"