The current iOS binding generator only generates returns if the function being bound does not return an error. If a second error return type is also present, the binder always generates both the primary as well as the error as an output parameter. This is undersirable because most decent functions in Go will also return errors, so all of those get converted to plain methods iOS side, each of them requiring allocating the return variable first and only then execute the call. This gets even more annoying with the Swift error wrapping protocol which converts errors to throw statements automatically, but which still needs the ugly pre- allocs caused by the genrated bindings not returning the result, just placing it in an output argument. This CL changes that so that if a nullable result is being returned by a bound method from Go, then it is generated as a proper return and not an output argument. This allows erroring functions to still be called as a function in ObjC, and even more elegantly drop even the error part in Swift. Change-Id: I35152d7d2fd2a132eba836fa23be8fd4f317f097 Reviewed-on: https://go-review.googlesource.com/34072 Reviewed-by: Elias Naur <elias.naur@gmail.com>
173 lines
3.7 KiB
Go
173 lines
3.7 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 (
|
|
"fmt"
|
|
"go/types"
|
|
"log"
|
|
"strings"
|
|
)
|
|
|
|
type ifaceSummary struct {
|
|
iface *types.Interface
|
|
callable []*types.Func
|
|
implementable bool
|
|
}
|
|
|
|
func makeIfaceSummary(iface *types.Interface) ifaceSummary {
|
|
summary := ifaceSummary{
|
|
iface: iface,
|
|
implementable: true,
|
|
}
|
|
methodset := types.NewMethodSet(iface)
|
|
for i := 0; i < methodset.Len(); i++ {
|
|
obj := methodset.At(i).Obj()
|
|
if !obj.Exported() {
|
|
summary.implementable = false
|
|
continue
|
|
}
|
|
m, ok := obj.(*types.Func)
|
|
if !ok {
|
|
log.Panicf("unexpected methodset obj: %s (%T)", obj, obj)
|
|
}
|
|
if !isImplementable(m.Type().(*types.Signature)) {
|
|
summary.implementable = false
|
|
}
|
|
if isCallable(m) {
|
|
summary.callable = append(summary.callable, m)
|
|
}
|
|
}
|
|
return summary
|
|
}
|
|
|
|
func isCallable(t *types.Func) bool {
|
|
// TODO(crawshaw): functions that are not implementable from
|
|
// another language may still be callable (for example, a
|
|
// returned value with an unexported type can be treated as
|
|
// an opaque value by the caller). This restriction could be
|
|
// lifted.
|
|
return isImplementable(t.Type().(*types.Signature))
|
|
}
|
|
|
|
func isImplementable(sig *types.Signature) bool {
|
|
params := sig.Params()
|
|
for i := 0; i < params.Len(); i++ {
|
|
if !isExported(params.At(i).Type()) {
|
|
return false
|
|
}
|
|
}
|
|
res := sig.Results()
|
|
for i := 0; i < res.Len(); i++ {
|
|
if !isExported(res.At(i).Type()) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func exportedMethodSet(T types.Type) []*types.Func {
|
|
var methods []*types.Func
|
|
methodset := types.NewMethodSet(T)
|
|
for i := 0; i < methodset.Len(); i++ {
|
|
obj := methodset.At(i).Obj()
|
|
if !obj.Exported() {
|
|
continue
|
|
}
|
|
// Skip methods from the embedded classes, so that
|
|
// only methods that are implemented in Go are included.
|
|
if pref := pkgFirstElem(obj.Pkg()); pref == "Java" || pref == "ObjC" {
|
|
continue
|
|
}
|
|
switch obj := obj.(type) {
|
|
case *types.Func:
|
|
methods = append(methods, obj)
|
|
default:
|
|
log.Panicf("unexpected methodset obj: %s", obj)
|
|
}
|
|
}
|
|
return methods
|
|
}
|
|
|
|
func exportedFields(T *types.Struct) []*types.Var {
|
|
var fields []*types.Var
|
|
for i := 0; i < T.NumFields(); i++ {
|
|
f := T.Field(i)
|
|
if !f.Exported() {
|
|
continue
|
|
}
|
|
fields = append(fields, f)
|
|
}
|
|
return fields
|
|
}
|
|
|
|
func isErrorType(t types.Type) bool {
|
|
return types.Identical(t, types.Universe.Lookup("error").Type())
|
|
}
|
|
|
|
func isExported(t types.Type) bool {
|
|
if isErrorType(t) {
|
|
return true
|
|
}
|
|
switch t := t.(type) {
|
|
case *types.Basic:
|
|
return true
|
|
case *types.Named:
|
|
return t.Obj().Exported()
|
|
case *types.Pointer:
|
|
return isExported(t.Elem())
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func isRefType(t types.Type) bool {
|
|
if isErrorType(t) {
|
|
return false
|
|
}
|
|
switch t := t.(type) {
|
|
case *types.Named:
|
|
switch u := t.Underlying().(type) {
|
|
case *types.Interface:
|
|
return true
|
|
default:
|
|
panic(fmt.Sprintf("unsupported named type: %s / %T", u, u))
|
|
}
|
|
case *types.Pointer:
|
|
return isRefType(t.Elem())
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isNullableType(t types.Type) bool {
|
|
return types.AssignableTo(types.Typ[types.UntypedNil].Underlying(), t) || t.String() == "string" // string is mapped to NSString*, which is nullable
|
|
}
|
|
|
|
func typePkgFirstElem(t types.Type) string {
|
|
nt, ok := t.(*types.Named)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
return pkgFirstElem(nt.Obj().Pkg())
|
|
}
|
|
|
|
func pkgFirstElem(p *types.Package) string {
|
|
if p == nil {
|
|
return ""
|
|
}
|
|
path := p.Path()
|
|
idx := strings.Index(path, "/")
|
|
if idx == -1 {
|
|
return ""
|
|
}
|
|
return path[:idx]
|
|
}
|
|
|
|
func isWrapperType(t types.Type) bool {
|
|
e := typePkgFirstElem(t)
|
|
return e == "Java" || e == "ObjC"
|
|
}
|