2018-05-01 14:46:39 -04:00
|
|
|
// Copyright 2018 Google LLC
|
|
|
|
|
//
|
|
|
|
|
// 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.
|
|
|
|
|
|
2018-04-02 09:21:52 -07:00
|
|
|
package goose
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"go/ast"
|
2018-04-04 10:58:07 -07:00
|
|
|
"go/build"
|
2018-04-02 09:21:52 -07:00
|
|
|
"go/token"
|
|
|
|
|
"go/types"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
|
2018-04-27 13:44:54 -04:00
|
|
|
"golang.org/x/tools/go/ast/astutil"
|
2018-04-02 09:21:52 -07:00
|
|
|
"golang.org/x/tools/go/loader"
|
|
|
|
|
)
|
|
|
|
|
|
2018-04-04 14:42:56 -07:00
|
|
|
// A ProviderSet describes a set of providers. The zero value is an empty
|
|
|
|
|
// ProviderSet.
|
|
|
|
|
type ProviderSet struct {
|
2018-04-27 13:44:54 -04:00
|
|
|
// Pos is the position of the call to goose.NewSet or goose.Use that
|
|
|
|
|
// created the set.
|
|
|
|
|
Pos token.Pos
|
|
|
|
|
// PkgPath is the import path of the package that declared this set.
|
|
|
|
|
PkgPath string
|
|
|
|
|
// Name is the variable name of the set, if it came from a package
|
|
|
|
|
// variable.
|
|
|
|
|
Name string
|
|
|
|
|
|
2018-04-04 14:42:56 -07:00
|
|
|
Providers []*Provider
|
2018-04-27 13:44:54 -04:00
|
|
|
Bindings []*IfaceBinding
|
|
|
|
|
Imports []*ProviderSet
|
2018-04-02 09:21:52 -07:00
|
|
|
}
|
|
|
|
|
|
2018-04-04 14:42:56 -07:00
|
|
|
// An IfaceBinding declares that a type should be used to satisfy inputs
|
2018-04-02 14:08:17 -07:00
|
|
|
// of the given interface type.
|
2018-04-04 14:42:56 -07:00
|
|
|
type IfaceBinding struct {
|
|
|
|
|
// Iface is the interface type, which is what can be injected.
|
|
|
|
|
Iface types.Type
|
2018-04-03 21:11:53 -07:00
|
|
|
|
2018-04-04 14:42:56 -07:00
|
|
|
// Provided is always a type that is assignable to Iface.
|
|
|
|
|
Provided types.Type
|
2018-04-03 21:11:53 -07:00
|
|
|
|
2018-04-04 14:42:56 -07:00
|
|
|
// Pos is the position where the binding was declared.
|
|
|
|
|
Pos token.Pos
|
2018-04-02 14:08:17 -07:00
|
|
|
}
|
|
|
|
|
|
2018-04-04 14:42:56 -07:00
|
|
|
// Provider records the signature of a provider. A provider is a
|
|
|
|
|
// single Go object, either a function or a named struct type.
|
|
|
|
|
type Provider struct {
|
|
|
|
|
// ImportPath is the package path that the Go object resides in.
|
|
|
|
|
ImportPath string
|
2018-04-03 21:11:53 -07:00
|
|
|
|
2018-04-04 14:42:56 -07:00
|
|
|
// Name is the name of the Go object.
|
|
|
|
|
Name string
|
2018-04-03 21:11:53 -07:00
|
|
|
|
2018-04-04 14:42:56 -07:00
|
|
|
// Pos is the source position of the func keyword or type spec
|
2018-04-03 21:11:53 -07:00
|
|
|
// defining this provider.
|
2018-04-04 14:42:56 -07:00
|
|
|
Pos token.Pos
|
2018-04-03 21:11:53 -07:00
|
|
|
|
2018-04-04 14:42:56 -07:00
|
|
|
// Args is the list of data dependencies this provider has.
|
|
|
|
|
Args []ProviderInput
|
2018-04-03 21:11:53 -07:00
|
|
|
|
2018-04-04 14:42:56 -07:00
|
|
|
// IsStruct is true if this provider is a named struct type.
|
2018-04-03 21:11:53 -07:00
|
|
|
// Otherwise it's a function.
|
2018-04-04 14:42:56 -07:00
|
|
|
IsStruct bool
|
2018-04-03 21:11:53 -07:00
|
|
|
|
2018-04-04 14:42:56 -07:00
|
|
|
// Fields lists the field names to populate. This will map 1:1 with
|
2018-04-03 21:11:53 -07:00
|
|
|
// elements in Args.
|
2018-04-04 14:42:56 -07:00
|
|
|
Fields []string
|
2018-04-03 21:11:53 -07:00
|
|
|
|
2018-04-04 14:42:56 -07:00
|
|
|
// Out is the type this provider produces.
|
|
|
|
|
Out types.Type
|
2018-04-03 21:11:53 -07:00
|
|
|
|
2018-04-04 14:42:56 -07:00
|
|
|
// HasCleanup reports whether the provider function returns a cleanup
|
2018-04-03 21:11:53 -07:00
|
|
|
// function. (Always false for structs.)
|
2018-04-04 14:42:56 -07:00
|
|
|
HasCleanup bool
|
2018-04-03 21:11:53 -07:00
|
|
|
|
2018-04-04 14:42:56 -07:00
|
|
|
// HasErr reports whether the provider function can return an error.
|
2018-04-03 21:11:53 -07:00
|
|
|
// (Always false for structs.)
|
2018-04-04 14:42:56 -07:00
|
|
|
HasErr bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ProviderInput describes an incoming edge in the provider graph.
|
|
|
|
|
type ProviderInput struct {
|
2018-04-26 14:23:06 -04:00
|
|
|
Type types.Type
|
|
|
|
|
|
|
|
|
|
// TODO(light): Move field name into this struct.
|
2018-04-04 14:42:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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) {
|
2018-04-27 13:44:54 -04:00
|
|
|
// TODO(light): Stop errors from printing to stderr.
|
|
|
|
|
conf := &loader.Config{
|
|
|
|
|
Build: bctx,
|
|
|
|
|
Cwd: wd,
|
|
|
|
|
TypeCheckFuncBodies: func(string) bool { return false },
|
|
|
|
|
}
|
2018-04-04 14:42:56 -07:00
|
|
|
for _, p := range pkgs {
|
|
|
|
|
conf.Import(p)
|
|
|
|
|
}
|
|
|
|
|
prog, err := conf.Load()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("load: %v", err)
|
|
|
|
|
}
|
|
|
|
|
info := &Info{
|
|
|
|
|
Fset: prog.Fset,
|
|
|
|
|
Sets: make(map[ProviderSetID]*ProviderSet),
|
|
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
oc := newObjectCache(prog)
|
|
|
|
|
for _, pkgInfo := range prog.InitialPackages() {
|
|
|
|
|
scope := pkgInfo.Pkg.Scope()
|
|
|
|
|
for _, name := range scope.Names() {
|
|
|
|
|
item, err := oc.get(scope.Lookup(name))
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
2018-04-04 14:42:56 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
pset, ok := item.(*ProviderSet)
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
2018-04-04 14:42:56 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
// pset.Name may not equal name, since it could be an alias to
|
|
|
|
|
// another provider set.
|
|
|
|
|
id := ProviderSetID{ImportPath: pset.PkgPath, VarName: name}
|
|
|
|
|
info.Sets[id] = pset
|
2018-04-04 14:42:56 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return info, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Info holds the result of Load.
|
|
|
|
|
type Info struct {
|
|
|
|
|
Fset *token.FileSet
|
|
|
|
|
|
|
|
|
|
// Sets contains all the provider sets in the initial packages.
|
|
|
|
|
Sets map[ProviderSetID]*ProviderSet
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-27 13:44:54 -04:00
|
|
|
// A ProviderSetID identifies a named provider set.
|
2018-04-04 14:42:56 -07:00
|
|
|
type ProviderSetID struct {
|
|
|
|
|
ImportPath string
|
2018-04-27 13:44:54 -04:00
|
|
|
VarName string
|
2018-04-04 14:42:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// String returns the ID as ""path/to/pkg".Foo".
|
|
|
|
|
func (id ProviderSetID) String() string {
|
2018-04-27 13:44:54 -04:00
|
|
|
return strconv.Quote(id.ImportPath) + "." + id.VarName
|
2018-04-02 09:21:52 -07:00
|
|
|
}
|
|
|
|
|
|
2018-04-27 13:44:54 -04:00
|
|
|
// objectCache is a lazily evaluated mapping of objects to goose structures.
|
|
|
|
|
type objectCache struct {
|
|
|
|
|
prog *loader.Program
|
|
|
|
|
objects map[objRef]interface{} // *Provider or *ProviderSet
|
2018-03-30 21:34:08 -07:00
|
|
|
}
|
|
|
|
|
|
2018-04-27 13:44:54 -04:00
|
|
|
type objRef struct {
|
|
|
|
|
importPath string
|
|
|
|
|
name string
|
2018-03-30 21:34:08 -07:00
|
|
|
}
|
|
|
|
|
|
2018-04-27 13:44:54 -04:00
|
|
|
func newObjectCache(prog *loader.Program) *objectCache {
|
|
|
|
|
return &objectCache{
|
|
|
|
|
prog: prog,
|
|
|
|
|
objects: make(map[objRef]interface{}),
|
2018-03-30 21:34:08 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-27 13:44:54 -04:00
|
|
|
// get converts a Go object into a goose structure. It may return a
|
|
|
|
|
// *Provider, a structProviderPair, an *IfaceBinding, or a *ProviderSet.
|
|
|
|
|
func (oc *objectCache) get(obj types.Object) (interface{}, error) {
|
|
|
|
|
ref := objRef{
|
|
|
|
|
importPath: obj.Pkg().Path(),
|
|
|
|
|
name: obj.Name(),
|
|
|
|
|
}
|
|
|
|
|
if val, cached := oc.objects[ref]; cached {
|
|
|
|
|
if val == nil {
|
|
|
|
|
return nil, fmt.Errorf("%v is not a provider or a provider set", obj)
|
2018-04-02 14:08:17 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
return val, nil
|
|
|
|
|
}
|
|
|
|
|
switch obj := obj.(type) {
|
|
|
|
|
case *types.Var:
|
|
|
|
|
spec := oc.varDecl(obj)
|
|
|
|
|
if len(spec.Values) == 0 {
|
|
|
|
|
return nil, fmt.Errorf("%v is not a provider or a provider set", obj)
|
2018-04-02 14:08:17 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
var i int
|
|
|
|
|
for i = range spec.Names {
|
|
|
|
|
if spec.Names[i].Name == obj.Name() {
|
|
|
|
|
break
|
2018-04-02 14:08:17 -07:00
|
|
|
}
|
|
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
return oc.processExpr(oc.prog.Package(obj.Pkg().Path()), spec.Values[i])
|
|
|
|
|
case *types.Func:
|
|
|
|
|
p, err := processFuncProvider(oc.prog.Fset, obj)
|
|
|
|
|
if err != nil {
|
|
|
|
|
oc.objects[ref] = nil
|
|
|
|
|
return nil, err
|
2018-03-30 21:34:08 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
oc.objects[ref] = p
|
|
|
|
|
return p, nil
|
|
|
|
|
default:
|
|
|
|
|
oc.objects[ref] = nil
|
|
|
|
|
return nil, fmt.Errorf("%v is not a provider or a provider set", obj)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// varDecl finds the declaration that defines the given variable.
|
|
|
|
|
func (oc *objectCache) varDecl(obj *types.Var) *ast.ValueSpec {
|
|
|
|
|
// TODO(light): Walk files to build object -> declaration mapping, if more performant.
|
|
|
|
|
// Recommended by https://golang.org/s/types-tutorial
|
|
|
|
|
pkg := oc.prog.Package(obj.Pkg().Path())
|
|
|
|
|
pos := obj.Pos()
|
|
|
|
|
for _, f := range pkg.Files {
|
|
|
|
|
tokenFile := oc.prog.Fset.File(f.Pos())
|
|
|
|
|
if base := tokenFile.Base(); base <= int(pos) && int(pos) < base+tokenFile.Size() {
|
|
|
|
|
path, _ := astutil.PathEnclosingInterval(f, pos, pos)
|
|
|
|
|
for _, node := range path {
|
|
|
|
|
if spec, ok := node.(*ast.ValueSpec); ok {
|
|
|
|
|
return spec
|
2018-04-02 10:57:48 -07:00
|
|
|
}
|
2018-04-02 09:21:52 -07:00
|
|
|
}
|
2018-03-30 21:34:08 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-27 13:44:54 -04:00
|
|
|
// processExpr converts an expression into a goose structure. It may
|
|
|
|
|
// return a *Provider, a structProviderPair, an *IfaceBinding, or a
|
|
|
|
|
// *ProviderSet.
|
|
|
|
|
func (oc *objectCache) processExpr(pkg *loader.PackageInfo, expr ast.Expr) (interface{}, error) {
|
|
|
|
|
exprPos := oc.prog.Fset.Position(expr.Pos())
|
|
|
|
|
expr = astutil.Unparen(expr)
|
|
|
|
|
if obj := qualifiedIdentObject(&pkg.Info, expr); obj != nil {
|
|
|
|
|
item, err := oc.get(obj)
|
2018-04-03 21:11:53 -07:00
|
|
|
if err != nil {
|
2018-04-27 13:44:54 -04:00
|
|
|
return nil, fmt.Errorf("%v: %v", exprPos, err)
|
2018-04-03 21:11:53 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
return item, nil
|
|
|
|
|
}
|
|
|
|
|
if call, ok := expr.(*ast.CallExpr); ok {
|
|
|
|
|
fnObj := qualifiedIdentObject(&pkg.Info, call.Fun)
|
|
|
|
|
if fnObj == nil || !isGooseImport(fnObj.Pkg().Path()) {
|
|
|
|
|
return nil, fmt.Errorf("%v: unknown pattern", exprPos)
|
2018-04-03 21:11:53 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
switch fnObj.Name() {
|
|
|
|
|
case "NewSet":
|
|
|
|
|
pset, err := oc.processNewSet(pkg, call)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("%v: %v", exprPos, err)
|
2018-04-03 21:11:53 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
return pset, nil
|
|
|
|
|
case "Bind":
|
|
|
|
|
b, err := processBind(oc.prog.Fset, &pkg.Info, call)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("%v: %v", exprPos, err)
|
2018-04-03 21:11:53 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
return b, nil
|
|
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("%v: unknown pattern", exprPos)
|
2018-04-03 21:11:53 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
}
|
|
|
|
|
if tn := structArgType(&pkg.Info, expr); tn != nil {
|
|
|
|
|
p, err := processStructProvider(oc.prog.Fset, tn)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("%v: %v", exprPos, err)
|
2018-04-03 21:11:53 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
ptrp := new(Provider)
|
|
|
|
|
*ptrp = *p
|
|
|
|
|
ptrp.Out = types.NewPointer(p.Out)
|
|
|
|
|
return structProviderPair{p, ptrp}, nil
|
|
|
|
|
}
|
|
|
|
|
return nil, fmt.Errorf("%v: unknown pattern", exprPos)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type structProviderPair struct {
|
|
|
|
|
provider *Provider
|
|
|
|
|
ptrProvider *Provider
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (oc *objectCache) processNewSet(pkg *loader.PackageInfo, call *ast.CallExpr) (*ProviderSet, error) {
|
|
|
|
|
// Assumes that call.Fun is goose.NewSet or goose.Use.
|
|
|
|
|
|
|
|
|
|
pset := &ProviderSet{
|
|
|
|
|
Pos: call.Pos(),
|
|
|
|
|
PkgPath: pkg.Pkg.Path(),
|
|
|
|
|
}
|
|
|
|
|
for _, arg := range call.Args {
|
|
|
|
|
item, err := oc.processExpr(pkg, arg)
|
2018-04-03 21:11:53 -07:00
|
|
|
if err != nil {
|
2018-04-27 13:44:54 -04:00
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
switch item := item.(type) {
|
|
|
|
|
case *Provider:
|
|
|
|
|
pset.Providers = append(pset.Providers, item)
|
|
|
|
|
case *ProviderSet:
|
|
|
|
|
pset.Imports = append(pset.Imports, item)
|
|
|
|
|
case *IfaceBinding:
|
|
|
|
|
pset.Bindings = append(pset.Bindings, item)
|
|
|
|
|
case structProviderPair:
|
|
|
|
|
pset.Providers = append(pset.Providers, item.provider, item.ptrProvider)
|
|
|
|
|
default:
|
|
|
|
|
panic("unknown item type")
|
2018-04-03 21:11:53 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
}
|
|
|
|
|
return pset, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// structArgType attempts to interpret an expression as a simple struct type.
|
|
|
|
|
// It assumes any parentheses have been stripped.
|
|
|
|
|
func structArgType(info *types.Info, expr ast.Expr) *types.TypeName {
|
|
|
|
|
lit, ok := expr.(*ast.CompositeLit)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
tn, ok := qualifiedIdentObject(info, lit.Type).(*types.TypeName)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if _, isStruct := tn.Type().Underlying().(*types.Struct); !isStruct {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return tn
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// qualifiedIdentObject finds the object for an identifier or a
|
|
|
|
|
// qualified identifier, or nil if the object could not be found.
|
|
|
|
|
func qualifiedIdentObject(info *types.Info, expr ast.Expr) types.Object {
|
|
|
|
|
switch expr := expr.(type) {
|
|
|
|
|
case *ast.Ident:
|
|
|
|
|
return info.ObjectOf(expr)
|
|
|
|
|
case *ast.SelectorExpr:
|
|
|
|
|
pkgName, ok := expr.X.(*ast.Ident)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil
|
2018-04-03 21:11:53 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
if _, ok := info.ObjectOf(pkgName).(*types.PkgName); !ok {
|
|
|
|
|
return nil
|
2018-04-02 09:21:52 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
return info.ObjectOf(expr.Sel)
|
2018-04-03 21:11:53 -07:00
|
|
|
default:
|
2018-04-27 13:44:54 -04:00
|
|
|
return nil
|
2018-04-03 21:11:53 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-27 13:44:54 -04:00
|
|
|
// processFuncProvider creates a provider for a function declaration.
|
|
|
|
|
func processFuncProvider(fset *token.FileSet, fn *types.Func) (*Provider, error) {
|
2018-04-03 21:11:53 -07:00
|
|
|
sig := fn.Type().(*types.Signature)
|
|
|
|
|
|
2018-03-30 21:34:08 -07:00
|
|
|
fpos := fn.Pos()
|
|
|
|
|
r := sig.Results()
|
2018-04-03 13:13:15 -07:00
|
|
|
var hasCleanup, hasErr bool
|
2018-03-30 21:34:08 -07:00
|
|
|
switch r.Len() {
|
|
|
|
|
case 1:
|
2018-04-03 13:13:15 -07:00
|
|
|
hasCleanup, hasErr = false, false
|
2018-03-30 21:34:08 -07:00
|
|
|
case 2:
|
2018-04-03 13:13:15 -07:00
|
|
|
switch t := r.At(1).Type(); {
|
|
|
|
|
case types.Identical(t, errorType):
|
|
|
|
|
hasCleanup, hasErr = false, true
|
|
|
|
|
case types.Identical(t, cleanupType):
|
|
|
|
|
hasCleanup, hasErr = true, false
|
|
|
|
|
default:
|
2018-04-27 13:44:54 -04:00
|
|
|
return nil, fmt.Errorf("%v: wrong signature for provider %s: second return type must be error or func()", fset.Position(fpos), fn.Name())
|
2018-03-30 21:34:08 -07:00
|
|
|
}
|
2018-04-03 13:13:15 -07:00
|
|
|
case 3:
|
|
|
|
|
if t := r.At(1).Type(); !types.Identical(t, cleanupType) {
|
2018-04-27 13:44:54 -04:00
|
|
|
return nil, fmt.Errorf("%v: wrong signature for provider %s: second return type must be func()", fset.Position(fpos), fn.Name())
|
2018-04-03 13:13:15 -07:00
|
|
|
}
|
|
|
|
|
if t := r.At(2).Type(); !types.Identical(t, errorType) {
|
2018-04-27 13:44:54 -04:00
|
|
|
return nil, fmt.Errorf("%v: wrong signature for provider %s: third return type must be error", fset.Position(fpos), fn.Name())
|
2018-04-03 13:13:15 -07:00
|
|
|
}
|
|
|
|
|
hasCleanup, hasErr = true, true
|
2018-03-30 21:34:08 -07:00
|
|
|
default:
|
2018-04-27 13:44:54 -04:00
|
|
|
return nil, fmt.Errorf("%v: wrong signature for provider %s: must have one return value and optional error", fset.Position(fpos), fn.Name())
|
2018-03-30 21:34:08 -07:00
|
|
|
}
|
|
|
|
|
out := r.At(0).Type()
|
|
|
|
|
params := sig.Params()
|
2018-04-04 14:42:56 -07:00
|
|
|
provider := &Provider{
|
2018-04-27 13:44:54 -04:00
|
|
|
ImportPath: fn.Pkg().Path(),
|
2018-04-04 14:42:56 -07:00
|
|
|
Name: fn.Name(),
|
|
|
|
|
Pos: fn.Pos(),
|
|
|
|
|
Args: make([]ProviderInput, params.Len()),
|
|
|
|
|
Out: out,
|
|
|
|
|
HasCleanup: hasCleanup,
|
|
|
|
|
HasErr: hasErr,
|
2018-03-30 21:34:08 -07:00
|
|
|
}
|
|
|
|
|
for i := 0; i < params.Len(); i++ {
|
2018-04-04 14:42:56 -07:00
|
|
|
provider.Args[i] = ProviderInput{
|
2018-04-26 14:23:06 -04:00
|
|
|
Type: params.At(i).Type(),
|
2018-03-30 21:34:08 -07:00
|
|
|
}
|
|
|
|
|
for j := 0; j < i; j++ {
|
2018-04-04 14:42:56 -07:00
|
|
|
if types.Identical(provider.Args[i].Type, provider.Args[j].Type) {
|
2018-04-27 13:44:54 -04:00
|
|
|
return nil, fmt.Errorf("%v: provider has multiple parameters of type %s", fset.Position(fpos), types.TypeString(provider.Args[j].Type, nil))
|
2018-03-30 21:34:08 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-03 21:11:53 -07:00
|
|
|
return provider, nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-27 13:44:54 -04:00
|
|
|
// processStructProvider creates a provider for a named struct type.
|
|
|
|
|
// It only produces the non-pointer variant.
|
|
|
|
|
func processStructProvider(fset *token.FileSet, typeName *types.TypeName) (*Provider, error) {
|
2018-04-03 21:11:53 -07:00
|
|
|
out := typeName.Type()
|
2018-04-27 13:44:54 -04:00
|
|
|
st, ok := out.Underlying().(*types.Struct)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, fmt.Errorf("%v does not name a struct", typeName)
|
|
|
|
|
}
|
2018-04-03 21:11:53 -07:00
|
|
|
|
|
|
|
|
pos := typeName.Pos()
|
2018-04-04 14:42:56 -07:00
|
|
|
provider := &Provider{
|
2018-04-27 13:44:54 -04:00
|
|
|
ImportPath: typeName.Pkg().Path(),
|
2018-04-04 14:42:56 -07:00
|
|
|
Name: typeName.Name(),
|
|
|
|
|
Pos: pos,
|
|
|
|
|
Args: make([]ProviderInput, st.NumFields()),
|
|
|
|
|
Fields: make([]string, st.NumFields()),
|
|
|
|
|
IsStruct: true,
|
|
|
|
|
Out: out,
|
2018-04-03 21:11:53 -07:00
|
|
|
}
|
|
|
|
|
for i := 0; i < st.NumFields(); i++ {
|
|
|
|
|
f := st.Field(i)
|
2018-04-04 14:42:56 -07:00
|
|
|
provider.Args[i] = ProviderInput{
|
2018-04-26 14:23:06 -04:00
|
|
|
Type: f.Type(),
|
2018-04-03 21:11:53 -07:00
|
|
|
}
|
2018-04-04 14:42:56 -07:00
|
|
|
provider.Fields[i] = f.Name()
|
2018-04-03 21:11:53 -07:00
|
|
|
for j := 0; j < i; j++ {
|
2018-04-04 14:42:56 -07:00
|
|
|
if types.Identical(provider.Args[i].Type, provider.Args[j].Type) {
|
2018-04-27 13:44:54 -04:00
|
|
|
return nil, fmt.Errorf("%v: provider struct has multiple fields of type %s", fset.Position(pos), types.TypeString(provider.Args[j].Type, nil))
|
2018-04-03 21:11:53 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return provider, nil
|
2018-04-02 09:21:52 -07:00
|
|
|
}
|
|
|
|
|
|
2018-04-27 13:44:54 -04:00
|
|
|
// processBind creates an interface binding from a goose.Bind call.
|
|
|
|
|
func processBind(fset *token.FileSet, info *types.Info, call *ast.CallExpr) (*IfaceBinding, error) {
|
|
|
|
|
// Assumes that call.Fun is goose.Bind.
|
2018-04-02 09:21:52 -07:00
|
|
|
|
2018-04-27 13:44:54 -04:00
|
|
|
if len(call.Args) != 2 {
|
|
|
|
|
return nil, fmt.Errorf("%v: call to Bind takes exactly two arguments", fset.Position(call.Pos()))
|
2018-04-02 09:21:52 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
// TODO(light): Verify that arguments are simple expressions.
|
|
|
|
|
iface := info.TypeOf(call.Args[0])
|
|
|
|
|
methodSet, ok := iface.Underlying().(*types.Interface)
|
2018-04-02 09:21:52 -07:00
|
|
|
if !ok {
|
2018-04-27 13:44:54 -04:00
|
|
|
return nil, fmt.Errorf("%v: first argument to bind must be of interface type; found %s", fset.Position(call.Pos()), types.TypeString(iface, nil))
|
2018-04-02 09:21:52 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
provided := info.TypeOf(call.Args[1])
|
|
|
|
|
if types.Identical(iface, provided) {
|
|
|
|
|
return nil, fmt.Errorf("%v: cannot bind interface to itself", fset.Position(call.Pos()))
|
2018-04-02 14:08:17 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
if !types.Implements(provided, methodSet) {
|
|
|
|
|
return nil, fmt.Errorf("%v: %s does not implement %s", fset.Position(call.Pos()), types.TypeString(provided, nil), types.TypeString(iface, nil))
|
2018-04-04 10:58:07 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
return &IfaceBinding{
|
|
|
|
|
Pos: call.Pos(),
|
|
|
|
|
Iface: iface,
|
|
|
|
|
Provided: provided,
|
|
|
|
|
}, nil
|
2018-04-04 10:58:07 -07:00
|
|
|
}
|
|
|
|
|
|
2018-04-27 13:44:54 -04:00
|
|
|
// 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.
|
|
|
|
|
func isInjector(info *types.Info, fn *ast.FuncDecl) *ast.CallExpr {
|
|
|
|
|
if fn.Body == nil {
|
|
|
|
|
return nil
|
2018-04-02 14:08:17 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
var only *ast.ExprStmt
|
|
|
|
|
for _, stmt := range fn.Body.List {
|
|
|
|
|
switch stmt := stmt.(type) {
|
|
|
|
|
case *ast.ExprStmt:
|
|
|
|
|
if only != nil {
|
|
|
|
|
return nil
|
2018-03-30 21:34:08 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
only = stmt
|
|
|
|
|
case *ast.EmptyStmt:
|
|
|
|
|
// Do nothing.
|
|
|
|
|
default:
|
|
|
|
|
return nil
|
2018-03-30 21:34:08 -07:00
|
|
|
}
|
|
|
|
|
}
|
2018-05-01 14:46:39 -04:00
|
|
|
if only == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
panicCall, ok := only.X.(*ast.CallExpr)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil
|
2018-03-30 21:34:08 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
panicIdent, ok := panicCall.Fun.(*ast.Ident)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil
|
2018-03-30 21:34:08 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
if info.ObjectOf(panicIdent) != types.Universe.Lookup("panic") {
|
|
|
|
|
return nil
|
2018-04-02 09:21:52 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
if len(panicCall.Args) != 1 {
|
|
|
|
|
return nil
|
2018-03-30 21:34:08 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
useCall, ok := panicCall.Args[0].(*ast.CallExpr)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil
|
2018-04-02 10:57:48 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
useObj := qualifiedIdentObject(info, useCall.Fun)
|
|
|
|
|
if !isGooseImport(useObj.Pkg().Path()) || useObj.Name() != "Use" {
|
|
|
|
|
return nil
|
2018-04-02 10:57:48 -07:00
|
|
|
}
|
2018-04-27 13:44:54 -04:00
|
|
|
return useCall
|
2018-04-02 10:57:48 -07:00
|
|
|
}
|
|
|
|
|
|
2018-04-27 13:44:54 -04:00
|
|
|
func isGooseImport(path string) bool {
|
|
|
|
|
// TODO(light): This is depending on details of the current loader.
|
|
|
|
|
const vendorPart = "vendor/"
|
|
|
|
|
if i := strings.LastIndex(path, vendorPart); i != -1 && (i == 0 || path[i-1] == '/') {
|
|
|
|
|
path = path[i+len(vendorPart):]
|
2018-04-02 09:21:52 -07:00
|
|
|
}
|
2018-05-01 14:46:39 -04:00
|
|
|
return path == "github.com/google/go-cloud/goose"
|
2018-04-02 09:21:52 -07:00
|
|
|
}
|
2018-03-30 21:34:08 -07:00
|
|
|
|
|
|
|
|
// paramIndex returns the index of the parameter with the given name, or
|
|
|
|
|
// -1 if no such parameter exists.
|
|
|
|
|
func paramIndex(params *types.Tuple, name string) int {
|
|
|
|
|
for i := 0; i < params.Len(); i++ {
|
|
|
|
|
if params.At(i).Name() == name {
|
|
|
|
|
return i
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return -1
|
|
|
|
|
}
|