blob: 0670f5af06be30dffb31a0bf6787f0bb3cd88deb [file] [log] [blame]
// Copyright 2015 The Go 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 (
"bytes"
"fmt"
"go/ast"
"go/token"
"go/types"
"io"
"regexp"
"strings"
"unicode"
"unicode/utf8"
)
type (
ErrorList []error
// varMode describes the lifetime of an argument or
// return value. Modes are used to guide the conversion
// of string and byte slice values accross the language
// barrier. The same conversion mode must be used for
// both the conversion before a foreign call and the
// corresponding conversion after the call.
// See the mode* constants for a description of
// each mode.
varMode int
)
const (
// modeTransient are for function arguments that
// are not used after the function returns.
// Transient byte slices don't need copying
// when passed accross the language barrier.
modeTransient varMode = iota
// modeRetained are for returned values and for function
// arguments that are used after the function returns.
// Retained byte slices need an intermediate copy.
modeRetained
)
func (list ErrorList) Error() string {
buf := new(bytes.Buffer)
for i, err := range list {
if i > 0 {
buf.WriteRune('\n')
}
io.WriteString(buf, err.Error())
}
return buf.String()
}
// interfaceInfo comes from Init and collects the auxillary information
// needed to generate bindings for an exported Go interface in a bound
// package.
type interfaceInfo struct {
obj *types.TypeName
t *types.Interface
summary ifaceSummary
}
// structInfo comes from Init and collects the auxillary information
// needed to generate bindings for an exported Go struct in a bound
// package.
type structInfo struct {
obj *types.TypeName
t *types.Struct
}
// Generator contains the common Go package information
// needed for the specific Go, Java, ObjC generators.
//
// After setting Printer, Fset, AllPkg, Pkg, the Init
// method is used to initialize the auxiliary information
// about the package to be generated, Pkg.
type Generator struct {
*Printer
Fset *token.FileSet
AllPkg []*types.Package
AST *ast.Package
Pkg *types.Package
err ErrorList
// fields set by init.
pkgName string
pkgPrefix string
funcs []*types.Func
constants []*types.Const
vars []*types.Var
interfaces []interfaceInfo
structs []structInfo
otherNames []*types.TypeName
// allIntf contains interfaces from all bound packages.
allIntf []interfaceInfo
docs pkgDocs
}
// A pkgDocs maps identifier names to its extracted documentation for a package.
type pkgDocs map[string]*pkgDoc
type pkgDoc struct {
doc string
// Struct or interface fields and methods.
members map[string]string
}
// pkgPrefix returns a prefix that disambiguates symbol names for binding
// multiple packages.
//
// TODO(elias.naur): Avoid (and test) name clashes from multiple packages
// with the same name. Perhaps use the index from the order the package is
// generated.
func pkgPrefix(pkg *types.Package) string {
// The error type has no package
if pkg == nil {
return ""
}
return pkg.Name()
}
func (g *Generator) Init() {
if g.Pkg != nil {
g.pkgName = g.Pkg.Name()
}
g.pkgPrefix = pkgPrefix(g.Pkg)
if g.Pkg != nil {
g.parseDocs()
scope := g.Pkg.Scope()
hasExported := false
for _, name := range scope.Names() {
obj := scope.Lookup(name)
if !obj.Exported() {
continue
}
hasExported = true
switch obj := obj.(type) {
case *types.Func:
if isCallable(obj) {
g.funcs = append(g.funcs, obj)
}
case *types.TypeName:
named := obj.Type().(*types.Named)
switch t := named.Underlying().(type) {
case *types.Struct:
g.structs = append(g.structs, structInfo{obj, t})
case *types.Interface:
g.interfaces = append(g.interfaces, interfaceInfo{obj, t, makeIfaceSummary(t)})
default:
g.otherNames = append(g.otherNames, obj)
}
case *types.Const:
g.constants = append(g.constants, obj)
case *types.Var:
g.vars = append(g.vars, obj)
default:
g.errorf("unsupported exported type for %s: %T", obj.Name(), obj)
}
}
if !hasExported {
g.errorf("no exported names in the package %q", g.Pkg.Path())
}
} else {
// Bind the single supported type from the universe scope, error.
errType := types.Universe.Lookup("error").(*types.TypeName)
t := errType.Type().Underlying().(*types.Interface)
g.interfaces = append(g.interfaces, interfaceInfo{errType, t, makeIfaceSummary(t)})
}
for _, p := range g.AllPkg {
scope := p.Scope()
for _, name := range scope.Names() {
obj := scope.Lookup(name)
if !obj.Exported() {
continue
}
if obj, ok := obj.(*types.TypeName); ok {
named := obj.Type().(*types.Named)
if t, ok := named.Underlying().(*types.Interface); ok {
g.allIntf = append(g.allIntf, interfaceInfo{obj, t, makeIfaceSummary(t)})
}
}
}
}
}
func (d pkgDocs) Visit(n ast.Node) ast.Visitor {
switch n := n.(type) {
case *ast.GenDecl:
outerDoc := n.Doc
for _, spec := range n.Specs {
switch t := spec.(type) {
case *ast.TypeSpec:
d.addType(t, outerDoc)
case *ast.ValueSpec:
d.addValue(t, outerDoc)
}
}
return nil
case *ast.FuncDecl:
d.addFunc(n)
return nil
default:
return d
}
}
func (d pkgDocs) addValue(t *ast.ValueSpec, outerDoc *ast.CommentGroup) {
for _, n := range t.Names {
doc := t.Doc
if doc == nil {
doc = outerDoc
}
if doc != nil {
d[n.Name] = &pkgDoc{doc: doc.Text()}
}
}
}
func (d pkgDocs) addFunc(f *ast.FuncDecl) {
doc := f.Doc
if doc == nil {
return
}
fn := f.Name.Name
if r := f.Recv; r != nil {
// f is a method.
switch t := r.List[0].Type.(type) {
case *ast.Ident:
d.addMethod(t.Name, fn, doc.Text())
case *ast.StarExpr:
sname := t.X.(*ast.Ident).Name
d.addMethod(sname, fn, doc.Text())
}
} else {
// f is a function.
d[fn] = &pkgDoc{doc: doc.Text()}
}
}
func (d pkgDocs) addType(t *ast.TypeSpec, outerDoc *ast.CommentGroup) {
doc := t.Doc
if doc == nil {
doc = outerDoc
}
pd, exists := d[t.Name.Name]
if !exists {
pd = &pkgDoc{members: make(map[string]string)}
d[t.Name.Name] = pd
}
if doc != nil {
pd.doc = doc.Text()
}
var fields *ast.FieldList
switch t := t.Type.(type) {
case *ast.StructType:
fields = t.Fields
case *ast.InterfaceType:
fields = t.Methods
}
if fields != nil {
for _, field := range fields.List {
if field.Doc != nil {
pd.members[field.Names[0].Name] = field.Doc.Text()
}
}
}
}
func (d pkgDocs) addMethod(sname, fname, doc string) {
pd, exists := d[sname]
if !exists {
pd = &pkgDoc{members: make(map[string]string)}
d[sname] = pd
}
pd.members[fname] = doc
}
// parseDocs extracts documentation from a package in a form useful for lookups.
func (g *Generator) parseDocs() {
g.docs = make(pkgDocs)
if g.AST != nil {
ast.Walk(g.docs, g.AST)
}
}
func (d *pkgDoc) Visit(n ast.Node) ast.Visitor {
switch n := n.(type) {
case *ast.Field:
d.members[n.Names[0].Name] = n.Doc.Text()
}
return d
}
func (d *pkgDoc) Doc() string {
if d == nil {
return ""
}
return d.doc
}
func (d *pkgDoc) Member(n string) string {
if d == nil {
return ""
}
return d.members[n]
}
// constructorType returns the type T for a function of the forms:
//
// func NewT...(...) *T
// func NewT...(...) (*T, error)
func (g *Generator) constructorType(f *types.Func) *types.TypeName {
sig := f.Type().(*types.Signature)
res := sig.Results()
if res.Len() != 1 && !(res.Len() == 2 && isErrorType(res.At(1).Type())) {
return nil
}
rt := res.At(0).Type()
pt, ok := rt.(*types.Pointer)
if !ok {
return nil
}
nt, ok := pt.Elem().(*types.Named)
if !ok {
return nil
}
obj := nt.Obj()
if !strings.HasPrefix(f.Name(), "New"+obj.Name()) {
return nil
}
return obj
}
func toCFlag(v bool) int {
if v {
return 1
}
return 0
}
func (g *Generator) errorf(format string, args ...interface{}) {
g.err = append(g.err, fmt.Errorf(format, args...))
}
// cgoType returns the name of a Cgo type suitable for converting a value of
// the given type.
func (g *Generator) cgoType(t types.Type) string {
switch t := t.(type) {
case *types.Basic:
switch t.Kind() {
case types.Bool, types.UntypedBool:
return "char"
case types.Int:
return "nint"
case types.Int8:
return "int8_t"
case types.Int16:
return "int16_t"
case types.Int32, types.UntypedRune: // types.Rune
return "int32_t"
case types.Int64, types.UntypedInt:
return "int64_t"
case types.Uint8: // types.Byte
return "uint8_t"
// TODO(crawshaw): case types.Uint, types.Uint16, types.Uint32, types.Uint64:
case types.Float32:
return "float"
case types.Float64, types.UntypedFloat:
return "double"
case types.String:
return "nstring"
default:
g.errorf("unsupported basic type: %s", t)
}
case *types.Slice:
switch e := t.Elem().(type) {
case *types.Basic:
switch e.Kind() {
case types.Uint8: // Byte.
return "nbyteslice"
default:
g.errorf("unsupported slice type: %s", t)
}
default:
g.errorf("unsupported slice type: %s", t)
}
case *types.Pointer:
if _, ok := t.Elem().(*types.Named); ok {
return g.cgoType(t.Elem())
}
g.errorf("unsupported pointer to type: %s", t)
case *types.Named:
return "int32_t"
default:
g.errorf("unsupported type: %s", t)
}
return "TODO"
}
func (g *Generator) genInterfaceMethodSignature(m *types.Func, iName string, header bool, g_paramName func(*types.Tuple, int) string) {
sig := m.Type().(*types.Signature)
params := sig.Params()
res := sig.Results()
if res.Len() == 0 {
g.Printf("void ")
} else {
if res.Len() == 1 {
g.Printf("%s ", g.cgoType(res.At(0).Type()))
} else {
if header {
g.Printf("typedef struct cproxy%s_%s_%s_return {\n", g.pkgPrefix, iName, m.Name())
g.Indent()
for i := 0; i < res.Len(); i++ {
t := res.At(i).Type()
g.Printf("%s r%d;\n", g.cgoType(t), i)
}
g.Outdent()
g.Printf("} cproxy%s_%s_%s_return;\n", g.pkgPrefix, iName, m.Name())
}
g.Printf("struct cproxy%s_%s_%s_return ", g.pkgPrefix, iName, m.Name())
}
}
g.Printf("cproxy%s_%s_%s(int32_t refnum", g.pkgPrefix, iName, m.Name())
for i := 0; i < params.Len(); i++ {
t := params.At(i).Type()
g.Printf(", %s %s", g.cgoType(t), g_paramName(params, i))
}
g.Printf(")")
if header {
g.Printf(";\n")
} else {
g.Printf(" {\n")
}
}
func (g *Generator) validPkg(pkg *types.Package) bool {
for _, p := range g.AllPkg {
if p == pkg {
return true
}
}
return false
}
// isSigSupported returns whether the generators can handle a given
// function signature
func (g *Generator) isSigSupported(t types.Type) bool {
sig := t.(*types.Signature)
params := sig.Params()
for i := 0; i < params.Len(); i++ {
if !g.isSupported(params.At(i).Type()) {
return false
}
}
res := sig.Results()
for i := 0; i < res.Len(); i++ {
if !g.isSupported(res.At(i).Type()) {
return false
}
}
return true
}
// isSupported returns whether the generators can handle the type.
func (g *Generator) isSupported(t types.Type) bool {
if isErrorType(t) || isWrapperType(t) {
return true
}
switch t := t.(type) {
case *types.Basic:
return true
case *types.Slice:
switch e := t.Elem().(type) {
case *types.Basic:
return e.Kind() == types.Uint8
}
case *types.Pointer:
switch t := t.Elem().(type) {
case *types.Named:
return g.validPkg(t.Obj().Pkg())
}
case *types.Named:
switch t.Underlying().(type) {
case *types.Interface, *types.Pointer:
return g.validPkg(t.Obj().Pkg())
}
}
return false
}
var paramRE = regexp.MustCompile(`^p[0-9]*$`)
// basicParamName replaces incompatible name with a p0-pN name.
// Missing names, or existing names of the form p[0-9] are incompatible.
func basicParamName(params *types.Tuple, pos int) string {
name := params.At(pos).Name()
if name == "" || name[0] == '_' || paramRE.MatchString(name) {
name = fmt.Sprintf("p%d", pos)
}
return name
}
func lowerFirst(s string) string {
if s == "" {
return ""
}
var conv []rune
for len(s) > 0 {
r, n := utf8.DecodeRuneInString(s)
if !unicode.IsUpper(r) {
if l := len(conv); l > 1 {
conv[l-1] = unicode.ToUpper(conv[l-1])
}
return string(conv) + s
}
conv = append(conv, unicode.ToLower(r))
s = s[n:]
}
return string(conv)
}
// newNameSanitizer returns a functions that replaces all dashes and dots
// with underscores, as well as avoiding reserved words by suffixing such
// identifiers with underscores.
func newNameSanitizer(res []string) func(s string) string {
reserved := make(map[string]bool)
for _, word := range res {
reserved[word] = true
}
symbols := strings.NewReplacer(
"-", "_",
".", "_",
)
return func(s string) string {
if reserved[s] {
return s + "_"
}
return symbols.Replace(s)
}
}