Before this change, binding unsupported basic types such as uint failed with an error. Instead, add them to the list of ignored types so that no error is generated and a comment is generated explaining why the offending function, constant or variable was skipped. Unsigned integers are probably easy to support in ObjC, but leave them unsupported for now. While here, improve the printing of the ignored types in the explaining comments. Fixes golang/go#24762 Change-Id: I0d9ab471b2245728270f6ee588f554d4a105d500 Reviewed-on: https://go-review.googlesource.com/105377 Reviewed-by: Daniel Martí <mvdan@mvdan.cc> Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com> Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
570 lines
13 KiB
Go
570 lines
13 KiB
Go
// 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
|
|
Files []*ast.File
|
|
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 the name of each exported package-level declaration to its extracted documentation.
|
|
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)})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// parseDocs extracts documentation from a package in a form useful for lookups.
|
|
func (g *Generator) parseDocs() {
|
|
d := make(pkgDocs)
|
|
for _, f := range g.Files {
|
|
for _, decl := range f.Decls {
|
|
switch decl := decl.(type) {
|
|
case *ast.GenDecl:
|
|
for _, spec := range decl.Specs {
|
|
switch spec := spec.(type) {
|
|
case *ast.TypeSpec:
|
|
d.addType(spec, decl.Doc)
|
|
case *ast.ValueSpec:
|
|
d.addValue(spec, decl.Doc)
|
|
}
|
|
}
|
|
case *ast.FuncDecl:
|
|
d.addFunc(decl)
|
|
}
|
|
}
|
|
}
|
|
g.docs = d
|
|
}
|
|
|
|
func (d pkgDocs) addValue(t *ast.ValueSpec, outerDoc *ast.CommentGroup) {
|
|
for _, n := range t.Names {
|
|
if !ast.IsExported(n.Name) {
|
|
continue
|
|
}
|
|
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 !ast.IsExported(fn) {
|
|
return
|
|
}
|
|
if r := f.Recv; r != nil {
|
|
// f is a method.
|
|
n := typeName(r.List[0].Type)
|
|
pd, exists := d[n]
|
|
if !exists {
|
|
pd = &pkgDoc{members: make(map[string]string)}
|
|
d[n] = pd
|
|
}
|
|
pd.members[fn] = doc.Text()
|
|
} else {
|
|
// f is a function.
|
|
d[fn] = &pkgDoc{doc: doc.Text()}
|
|
}
|
|
}
|
|
|
|
func (d pkgDocs) addType(t *ast.TypeSpec, outerDoc *ast.CommentGroup) {
|
|
if !ast.IsExported(t.Name.Name) {
|
|
return
|
|
}
|
|
doc := t.Doc
|
|
if doc == nil {
|
|
doc = outerDoc
|
|
}
|
|
pd := d[t.Name.Name]
|
|
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 {
|
|
if field.Names == nil {
|
|
// Anonymous field. Extract name from its type.
|
|
if n := typeName(field.Type); ast.IsExported(n) {
|
|
pd.members[n] = field.Doc.Text()
|
|
}
|
|
}
|
|
for _, n := range field.Names {
|
|
if ast.IsExported(n.Name) {
|
|
pd.members[n.Name] = field.Doc.Text()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// typeName returns the type name T for expressions on the
|
|
// T, *T, **T (etc.) form.
|
|
func typeName(t ast.Expr) string {
|
|
switch t := t.(type) {
|
|
case *ast.StarExpr:
|
|
return typeName(t.X)
|
|
case *ast.Ident:
|
|
return t.Name
|
|
case *ast.SelectorExpr:
|
|
return t.Sel.Name
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
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:
|
|
switch t.Kind() {
|
|
case types.Bool, types.UntypedBool,
|
|
types.Int,
|
|
types.Int8, types.Uint8, // types.Byte
|
|
types.Int16,
|
|
types.Int32, types.UntypedRune, // types.Rune
|
|
types.Int64, types.UntypedInt,
|
|
types.Float32,
|
|
types.Float64, types.UntypedFloat,
|
|
types.String, types.UntypedString:
|
|
return true
|
|
}
|
|
return false
|
|
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)
|
|
}
|
|
}
|