mirror of https://github.com/go-python/gopy.git
352 lines
6.8 KiB
Go
352 lines
6.8 KiB
Go
// Copyright 2015 The go-python Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package bind
|
|
|
|
import (
|
|
"fmt"
|
|
"go/doc"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/go/types"
|
|
)
|
|
|
|
// Package ties types.Package and ast.Package together.
|
|
// Package also collects informations about structs and funcs.
|
|
type Package struct {
|
|
pkg *types.Package
|
|
doc *doc.Package
|
|
|
|
structs []Struct
|
|
funcs []Func
|
|
}
|
|
|
|
// NewPackage creates a new Package, tying types.Package and ast.Package together.
|
|
func NewPackage(pkg *types.Package, doc *doc.Package) (*Package, error) {
|
|
p := &Package{
|
|
pkg: pkg,
|
|
doc: doc,
|
|
}
|
|
err := p.process()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return p, err
|
|
}
|
|
|
|
// Name returns the package name.
|
|
func (p *Package) Name() string {
|
|
return p.pkg.Name()
|
|
}
|
|
|
|
// getDoc returns the doc string associated with types.Object
|
|
// parent is the name of the containing scope ("" for global scope)
|
|
func (p *Package) getDoc(parent string, o types.Object) string {
|
|
n := o.Name()
|
|
switch o.(type) {
|
|
case *types.Const:
|
|
for _, c := range p.doc.Consts {
|
|
for _, cn := range c.Names {
|
|
if n == cn {
|
|
return c.Doc
|
|
}
|
|
}
|
|
}
|
|
|
|
case *types.Var:
|
|
for _, v := range p.doc.Vars {
|
|
for _, vn := range v.Names {
|
|
if n == vn {
|
|
return v.Doc
|
|
}
|
|
}
|
|
}
|
|
|
|
case *types.Func:
|
|
doc := func() string {
|
|
if o.Parent() == nil {
|
|
for _, typ := range p.doc.Types {
|
|
if typ.Name != parent {
|
|
continue
|
|
}
|
|
for _, m := range typ.Methods {
|
|
if m.Name == n {
|
|
return m.Doc
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
for _, f := range p.doc.Funcs {
|
|
if n == f.Name {
|
|
return f.Doc
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}()
|
|
|
|
sig := o.Type().(*types.Signature)
|
|
|
|
parseFn := func(tup *types.Tuple) []string {
|
|
params := []string{}
|
|
if tup == nil {
|
|
return params
|
|
}
|
|
for i := 0; i < tup.Len(); i++ {
|
|
paramVar := tup.At(i)
|
|
paramType := pyTypeName(paramVar.Type())
|
|
if paramVar.Name() != "" {
|
|
paramType = fmt.Sprintf("%s %s", paramType, paramVar.Name())
|
|
}
|
|
params = append(params, paramType)
|
|
}
|
|
return params
|
|
}
|
|
|
|
params := parseFn(sig.Params())
|
|
results := parseFn(sig.Results())
|
|
|
|
paramString := strings.Join(params, ", ")
|
|
resultString := strings.Join(results, ", ")
|
|
|
|
//FIXME(sbinet): add receiver for methods?
|
|
docSig := fmt.Sprintf("%s(%s) %s", o.Name(), paramString, resultString)
|
|
|
|
if doc != "" {
|
|
doc = fmt.Sprintf("%s\n\n%s", docSig, doc)
|
|
} else {
|
|
doc = docSig
|
|
}
|
|
return doc
|
|
|
|
case *types.TypeName:
|
|
for _, t := range p.doc.Types {
|
|
if n == t.Name {
|
|
return t.Doc
|
|
}
|
|
}
|
|
|
|
default:
|
|
// TODO(sbinet)
|
|
panic(fmt.Errorf("not yet supported: %v (%T)", o, o))
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// process collects informations about a go package.
|
|
func (p *Package) process() error {
|
|
var err error
|
|
|
|
funcs := make(map[string]Func)
|
|
structs := make(map[string]Struct)
|
|
|
|
scope := p.pkg.Scope()
|
|
for _, name := range scope.Names() {
|
|
obj := scope.Lookup(name)
|
|
if !obj.Exported() {
|
|
continue
|
|
}
|
|
|
|
switch obj := obj.(type) {
|
|
case *types.Const:
|
|
p.addConst(obj)
|
|
|
|
case *types.Var:
|
|
p.addVar(obj)
|
|
|
|
case *types.Func:
|
|
funcs[name], err = newFunc(p, obj)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
case *types.TypeName:
|
|
named := obj.Type().(*types.Named)
|
|
switch typ := named.Underlying().(type) {
|
|
case *types.Struct:
|
|
structs[name], err = newStruct(p, obj, typ)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
default:
|
|
//TODO(sbinet)
|
|
panic(fmt.Errorf("not yet supported: %v (%T)", typ, obj))
|
|
}
|
|
|
|
default:
|
|
//TODO(sbinet)
|
|
panic(fmt.Errorf("not yet supported: %v (%T)", obj, obj))
|
|
}
|
|
|
|
}
|
|
|
|
// remove ctors from funcs.
|
|
// add methods.
|
|
for sname, s := range structs {
|
|
for name, fct := range funcs {
|
|
if fct.ret == nil {
|
|
continue
|
|
}
|
|
if fct.ret == s.obj.Type() {
|
|
delete(funcs, name)
|
|
s.ctors = append(s.ctors, fct)
|
|
structs[sname] = s
|
|
}
|
|
}
|
|
|
|
ptyp := types.NewPointer(s.obj.Type())
|
|
mset := types.NewMethodSet(ptyp)
|
|
for i := 0; i < mset.Len(); i++ {
|
|
meth := mset.At(i)
|
|
if !meth.Obj().Exported() {
|
|
continue
|
|
}
|
|
m, err := newMethod(p, &s, meth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.meths = append(s.meths, m)
|
|
}
|
|
p.structs = append(p.structs, s)
|
|
}
|
|
|
|
for _, fct := range funcs {
|
|
p.funcs = append(p.funcs, fct)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (p *Package) addConst(obj *types.Const) {
|
|
//TODO(sbinet)
|
|
panic(fmt.Errorf("not yet supported: %v (%T)", obj, obj))
|
|
}
|
|
|
|
func (p *Package) addVar(obj *types.Var) {
|
|
//TODO(sbinet)
|
|
panic(fmt.Errorf("not yet supported: %v (%T)", obj, obj))
|
|
}
|
|
|
|
// Struct collects informations about a go struct.
|
|
type Struct struct {
|
|
obj *types.TypeName
|
|
typ *types.Struct
|
|
|
|
id string
|
|
doc string
|
|
ctors []Func
|
|
meths []Method
|
|
}
|
|
|
|
func newStruct(p *Package, obj *types.TypeName, typ *types.Struct) (Struct, error) {
|
|
s := Struct{
|
|
obj: obj,
|
|
typ: typ,
|
|
id: obj.Pkg().Name() + "_" + obj.Name(),
|
|
doc: p.getDoc("", obj),
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// Method collects informations about a go method.
|
|
type Method struct {
|
|
sel *types.Selection
|
|
|
|
id string
|
|
doc string
|
|
ret types.Type // return type, if any
|
|
err bool // true if original go method has comma-error
|
|
}
|
|
|
|
func newMethod(p *Package, typ *Struct, sel *types.Selection) (Method, error) {
|
|
haserr := false
|
|
sig := sel.Type().(*types.Signature)
|
|
res := sig.Results()
|
|
var ret types.Type
|
|
|
|
switch res.Len() {
|
|
case 2:
|
|
if !isErrorType(res.At(1).Type()) {
|
|
return Method{}, fmt.Errorf(
|
|
"bind: second result value must be of type error: %s",
|
|
sel,
|
|
)
|
|
}
|
|
haserr = true
|
|
ret = res.At(0).Type()
|
|
|
|
case 1:
|
|
if isErrorType(res.At(0).Type()) {
|
|
haserr = true
|
|
ret = nil
|
|
} else {
|
|
ret = res.At(0).Type()
|
|
}
|
|
case 0:
|
|
ret = nil
|
|
default:
|
|
return Method{}, fmt.Errorf("bind: too many results to return: %v", sel)
|
|
}
|
|
|
|
return Method{
|
|
sel: sel,
|
|
id: typ.obj.Pkg().Name() + "_" + typ.obj.Name() + "_" + sel.Obj().Name(),
|
|
doc: p.getDoc(typ.obj.Name(), sel.Obj()),
|
|
ret: ret,
|
|
err: haserr,
|
|
}, nil
|
|
|
|
}
|
|
|
|
// Func collects informations about a go func.
|
|
type Func struct {
|
|
typ *types.Func
|
|
|
|
id string
|
|
doc string
|
|
ret types.Type // return type, if any
|
|
err bool // true if original go func has comma-error
|
|
}
|
|
|
|
func newFunc(p *Package, obj *types.Func) (Func, error) {
|
|
haserr := false
|
|
sig := obj.Type().(*types.Signature)
|
|
res := sig.Results()
|
|
var ret types.Type
|
|
|
|
switch res.Len() {
|
|
case 2:
|
|
if !isErrorType(res.At(1).Type()) {
|
|
return Func{}, fmt.Errorf(
|
|
"bind: second result value must be of type error: %s",
|
|
obj,
|
|
)
|
|
}
|
|
haserr = true
|
|
ret = res.At(0).Type()
|
|
|
|
case 1:
|
|
if isErrorType(res.At(0).Type()) {
|
|
haserr = true
|
|
ret = nil
|
|
} else {
|
|
ret = res.At(0).Type()
|
|
}
|
|
case 0:
|
|
ret = nil
|
|
default:
|
|
return Func{}, fmt.Errorf("bind: too many results to return: %v", obj)
|
|
}
|
|
|
|
return Func{
|
|
typ: obj,
|
|
id: obj.Pkg().Name() + "_" + obj.Name(),
|
|
doc: p.getDoc("", obj),
|
|
ret: ret,
|
|
err: haserr,
|
|
}, nil
|
|
}
|