goose: add goose.Value directive

Subsumes previous usage of goose.Optional.

Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Tuo Shan <shantuo@google.com>
This commit is contained in:
Ross Light
2018-05-04 12:44:53 -04:00
parent 235a7d8f80
commit 10676a814b
37 changed files with 706 additions and 60 deletions

View File

@@ -41,6 +41,7 @@ type ProviderSet struct {
Providers []*Provider
Bindings []*IfaceBinding
Values []*Value
Imports []*ProviderSet
}
@@ -100,6 +101,21 @@ type ProviderInput struct {
// TODO(light): Move field name into this struct.
}
// Value describes a value expression.
type Value struct {
// Pos is the source position of the expression defining this value.
Pos token.Pos
// Out is the type this value produces.
Out types.Type
// expr is the expression passed to goose.Value.
expr ast.Expr
// info is the type info for the expression.
info *types.Info
}
// Load finds all the provider sets in the given packages, as well as
// the provider sets' transitive dependencies.
func Load(bctx *build.Context, wd string, pkgs []string) (*Info, error) {
@@ -163,7 +179,7 @@ func (id ProviderSetID) String() string {
// objectCache is a lazily evaluated mapping of objects to goose structures.
type objectCache struct {
prog *loader.Program
objects map[objRef]interface{} // *Provider or *ProviderSet
objects map[objRef]interface{} // *Provider, *ProviderSet, *IfaceBinding, or *Value
}
type objRef struct {
@@ -179,7 +195,8 @@ func newObjectCache(prog *loader.Program) *objectCache {
}
// get converts a Go object into a goose structure. It may return a
// *Provider, a structProviderPair, an *IfaceBinding, or a *ProviderSet.
// *Provider, a structProviderPair, an *IfaceBinding, a *ProviderSet,
// or a *Value.
func (oc *objectCache) get(obj types.Object) (interface{}, error) {
ref := objRef{
importPath: obj.Pkg().Path(),
@@ -239,8 +256,8 @@ func (oc *objectCache) varDecl(obj *types.Var) *ast.ValueSpec {
}
// processExpr converts an expression into a goose structure. It may
// return a *Provider, a structProviderPair, an *IfaceBinding, or a
// *ProviderSet.
// return a *Provider, a structProviderPair, an *IfaceBinding, a
// *ProviderSet, or a *Value.
func (oc *objectCache) processExpr(pkg *loader.PackageInfo, expr ast.Expr) (interface{}, error) {
exprPos := oc.prog.Fset.Position(expr.Pos())
expr = astutil.Unparen(expr)
@@ -269,6 +286,12 @@ func (oc *objectCache) processExpr(pkg *loader.PackageInfo, expr ast.Expr) (inte
return nil, fmt.Errorf("%v: %v", exprPos, err)
}
return b, nil
case "Value":
v, err := processValue(oc.prog.Fset, &pkg.Info, call)
if err != nil {
return nil, fmt.Errorf("%v: %v", exprPos, err)
}
return v, nil
default:
return nil, fmt.Errorf("%v: unknown pattern", exprPos)
}
@@ -312,6 +335,8 @@ func (oc *objectCache) processNewSet(pkg *loader.PackageInfo, call *ast.CallExpr
pset.Bindings = append(pset.Bindings, item)
case structProviderPair:
pset.Providers = append(pset.Providers, item.provider, item.ptrProvider)
case *Value:
pset.Values = append(pset.Values, item)
default:
panic("unknown item type")
}
@@ -471,6 +496,48 @@ func processBind(fset *token.FileSet, info *types.Info, call *ast.CallExpr) (*If
}, nil
}
// processValue creates a value from a goose.Value call.
func processValue(fset *token.FileSet, info *types.Info, call *ast.CallExpr) (*Value, error) {
// Assumes that call.Fun is goose.Value.
if len(call.Args) != 1 {
return nil, fmt.Errorf("%v: call to Value takes exactly one argument", fset.Position(call.Pos()))
}
ok := true
ast.Inspect(call.Args[0], func(node ast.Node) bool {
switch node.(type) {
case nil, *ast.ArrayType, *ast.BasicLit, *ast.BinaryExpr, *ast.ChanType, *ast.CompositeLit, *ast.FuncType, *ast.Ident, *ast.IndexExpr, *ast.InterfaceType, *ast.KeyValueExpr, *ast.MapType, *ast.ParenExpr, *ast.SelectorExpr, *ast.SliceExpr, *ast.StarExpr, *ast.StructType, *ast.TypeAssertExpr:
// Good!
case *ast.UnaryExpr:
expr := node.(*ast.UnaryExpr)
if expr.Op == token.ARROW {
ok = false
return false
}
case *ast.CallExpr:
// Only acceptable if it's a type conversion.
call := node.(*ast.CallExpr)
if _, isFunc := info.TypeOf(call.Fun).(*types.Signature); isFunc {
ok = false
return false
}
default:
ok = false
return false
}
return true
})
if !ok {
return nil, fmt.Errorf("%v: argument to Value is too complex", fset.Position(call.Pos()))
}
return &Value{
Pos: call.Args[0].Pos(),
Out: info.TypeOf(call.Args[0]),
expr: call.Args[0],
info: info,
}, nil
}
// isInjector checks whether a given function declaration is an
// injector template, returning the goose.Use call. It returns nil if
// the function is not an injector template.