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 (
2018-05-08 16:26:38 -04:00
"errors"
2018-04-02 09:21:52 -07:00
"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-05-24 14:29:05 -07:00
// Pos is the position of the call to goose.NewSet or goose.Build that
2018-04-27 13:44:54 -04:00
// 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
2018-05-04 12:44:53 -04:00
Values [ ] * Value
2018-04-27 13:44:54 -04:00
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
}
2018-05-04 12:44:53 -04:00
// 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
}
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
2018-05-04 12:44:53 -04:00
objects map [ objRef ] interface { } // *Provider, *ProviderSet, *IfaceBinding, or *Value
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
2018-05-04 12:44:53 -04:00
// *Provider, a structProviderPair, an *IfaceBinding, a *ProviderSet,
// or a *Value.
2018-04-27 13:44:54 -04:00
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
2018-05-04 12:44:53 -04:00
// return a *Provider, a structProviderPair, an *IfaceBinding, a
// *ProviderSet, or a *Value.
2018-04-27 13:44:54 -04:00
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
2018-05-04 12:44:53 -04:00
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
2018-04-27 13:44:54 -04:00
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 ) {
2018-05-24 14:29:05 -07:00
// Assumes that call.Fun is goose.NewSet or goose.Build.
2018-04-27 13:44:54 -04:00
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 )
2018-05-04 12:44:53 -04:00
case * Value :
pset . Values = append ( pset . Values , item )
2018-04-27 13:44:54 -04:00
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 ( )
2018-05-08 16:26:38 -04:00
providerSig , err := funcOutput ( sig )
if err != nil {
return nil , fmt . Errorf ( "%v: wrong signature for provider %s: %v" , fset . Position ( fpos ) , fn . Name ( ) , err )
2018-03-30 21:34:08 -07:00
}
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 ( ) ) ,
2018-05-08 16:26:38 -04:00
Out : providerSig . out ,
HasCleanup : providerSig . cleanup ,
HasErr : providerSig . err ,
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-05-08 16:26:38 -04:00
type outputSignature struct {
out types . Type
cleanup bool
err bool
}
// funcOutput validates an injector or provider function's return signature.
func funcOutput ( sig * types . Signature ) ( outputSignature , error ) {
results := sig . Results ( )
switch results . Len ( ) {
case 0 :
return outputSignature { } , errors . New ( "no return values" )
case 1 :
return outputSignature { out : results . At ( 0 ) . Type ( ) } , nil
case 2 :
out := results . At ( 0 ) . Type ( )
switch t := results . At ( 1 ) . Type ( ) ; {
case types . Identical ( t , errorType ) :
return outputSignature { out : out , err : true } , nil
case types . Identical ( t , cleanupType ) :
return outputSignature { out : out , cleanup : true } , nil
default :
return outputSignature { } , fmt . Errorf ( "second return type is %s; must be error or func()" , types . TypeString ( t , nil ) )
}
case 3 :
if t := results . At ( 1 ) . Type ( ) ; ! types . Identical ( t , cleanupType ) {
return outputSignature { } , fmt . Errorf ( "second return type is %s; must be func()" , types . TypeString ( t , nil ) )
}
if t := results . At ( 2 ) . Type ( ) ; ! types . Identical ( t , errorType ) {
return outputSignature { } , fmt . Errorf ( "third return type is %s; must be error" , types . TypeString ( t , nil ) )
}
return outputSignature {
out : results . At ( 0 ) . Type ( ) ,
cleanup : true ,
err : true ,
} , nil
default :
return outputSignature { } , errors . New ( "too many return values" )
}
}
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.
2018-05-29 08:45:14 -07:00
ifaceArgType := info . TypeOf ( call . Args [ 0 ] )
ifacePtr , ok := ifaceArgType . ( * types . Pointer )
2018-04-02 09:21:52 -07:00
if ! ok {
2018-05-29 08:45:14 -07:00
return nil , fmt . Errorf ( "%v: first argument to bind must be a pointer to an interface type; found %s" , fset . Position ( call . Pos ( ) ) , types . TypeString ( ifaceArgType , nil ) )
}
methodSet , ok := ifacePtr . Elem ( ) . Underlying ( ) . ( * types . Interface )
if ! ok {
return nil , fmt . Errorf ( "%v: first argument to bind must be a pointer to an interface type; found %s" , fset . Position ( call . Pos ( ) ) , types . TypeString ( ifaceArgType , nil ) )
2018-04-02 09:21:52 -07:00
}
2018-04-27 13:44:54 -04:00
provided := info . TypeOf ( call . Args [ 1 ] )
2018-05-29 08:45:14 -07:00
if types . Identical ( ifacePtr . Elem ( ) , provided ) {
2018-04-27 13:44:54 -04:00
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 ) {
2018-05-29 08:45:14 -07:00
return nil , fmt . Errorf ( "%v: %s does not implement %s" , fset . Position ( call . Pos ( ) ) , types . TypeString ( provided , nil ) , types . TypeString ( ifaceArgType , nil ) )
2018-04-04 10:58:07 -07:00
}
2018-04-27 13:44:54 -04:00
return & IfaceBinding {
Pos : call . Pos ( ) ,
2018-05-29 08:45:14 -07:00
Iface : ifacePtr . Elem ( ) ,
2018-04-27 13:44:54 -04:00
Provided : provided ,
} , nil
2018-04-04 10:58:07 -07:00
}
2018-05-04 12:44:53 -04:00
// 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
}
2018-04-27 13:44:54 -04:00
// isInjector checks whether a given function declaration is an
2018-05-24 14:29:05 -07:00
// injector template, returning the goose.Build call. It returns nil if
2018-04-27 13:44:54 -04:00
// 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-05-24 14:29:05 -07:00
buildCall , ok := panicCall . Args [ 0 ] . ( * ast . CallExpr )
2018-04-27 13:44:54 -04:00
if ! ok {
return nil
2018-04-02 10:57:48 -07:00
}
2018-05-24 14:29:05 -07:00
buildObj := qualifiedIdentObject ( info , buildCall . Fun )
if ! isGooseImport ( buildObj . Pkg ( ) . Path ( ) ) || buildObj . Name ( ) != "Build" {
2018-04-27 13:44:54 -04:00
return nil
2018-04-02 10:57:48 -07:00
}
2018-05-24 14:29:05 -07:00
return buildCall
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
}