2
0
mirror of synced 2025-02-23 23:08:14 +00:00
mobile/bind/genobjc.go
Elias Naur 89b8360218 bind: remove error wrappers to preserve error instance identity
CL 24800 changed the error representation from strings to objects.
However, since native errors types are not immediately compatible
across languages, wrapper types were introduced to bridge the gap.

This CL remove those wrappers and instead special case the error
proxy types to conform to their language error protocol.

Specifically:

 - The ObjC proxy for Go errors now extends NSError and calls
   initWithDomain to store the error message.
 - The Go proxy for ObjC NSError return the localizedDescription
    property for calls to Error.
 - The Java proxy for Go errors ow extends Exception and
   overrides getMessage() to return the error message.
 - The Go proxy for Java Exceptions returns getMessage whenever
   Error is called.

The end result is that error values behave more like normal objects
across the language boundary. In particular, instance identity is
now preserved: an error passed across the boundary and back will
result in the same instance.

There are two semantic changes that followed this change:

 - The domain for wrapped Go errors is now always "go".
   The domain wasn't useful before this CL: the domains were set to
   the package name of function or method where the error happened
   to cross the language boundary.
 - If a Go method that returns an error is implemented in ObjC, the
   implementation must now both return NO _and_ set the error result
   for the calling Go code to receive a non-nil error.
   Before this CL, because errors were always wrapped, a nil ObjC
   could be represented with a non-nil wrapper.

Change-Id: Idb415b6b13ecf79ccceb60f675059942bfc48fec
Reviewed-on: https://go-review.googlesource.com/29298
Reviewed-by: David Crawshaw <crawshaw@golang.org>
2016-10-04 09:11:42 +00:00

1065 lines
28 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/constant"
"go/types"
"math"
"strings"
)
// TODO(hyangah): handle method name conflicts.
// - struct with SetF method and exported F field.
// - method names conflicting with NSObject methods. e.g. Init
// - interface type with InitWithRef.
// TODO(hyangah): error code/domain propagation
type objcGen struct {
prefix string // prefix arg passed by flag.
// fields set by init.
namePrefix string
*Generator
}
func (g *objcGen) init() {
g.Generator.Init()
g.namePrefix = g.namePrefixOf(g.Pkg)
}
func (g *objcGen) namePrefixOf(pkg *types.Package) string {
if pkg == nil {
return "GoUniverse"
}
p := g.prefix
if p == "" {
p = "Go"
}
return p + strings.Title(pkg.Name())
}
func (g *objcGen) genGoH() error {
var pkgPath string
if g.Pkg != nil {
pkgPath = g.Pkg.Path()
}
g.Printf(objcPreamble, pkgPath, g.gobindOpts(), pkgPath)
g.Printf("#ifndef __%s_H__\n", g.pkgName)
g.Printf("#define __%s_H__\n\n", g.pkgName)
g.Printf("#include <stdint.h>\n")
g.Printf("#include <objc/objc.h>\n")
for _, i := range g.interfaces {
if !i.summary.implementable {
continue
}
for _, m := range i.summary.callable {
if !g.isSigSupported(m.Type()) {
g.Printf("// skipped method %s.%s with unsupported parameter or return types\n\n", i.obj.Name(), m.Name())
continue
}
g.genInterfaceMethodSignature(m, i.obj.Name(), true)
g.Printf("\n")
}
}
g.Printf("#endif\n")
if len(g.err) > 0 {
return g.err
}
return nil
}
func (g *objcGen) genH() error {
var pkgPath string
if g.Pkg != nil {
pkgPath = g.Pkg.Path()
}
g.Printf(objcPreamble, pkgPath, g.gobindOpts(), pkgPath)
g.Printf("#ifndef __%s_H__\n", g.namePrefix)
g.Printf("#define __%s_H__\n", g.namePrefix)
g.Printf("\n")
g.Printf("#include <Foundation/Foundation.h>\n")
g.Printf("#include \"GoUniverse.h\"\n")
if g.Pkg != nil {
for _, pkg := range g.Pkg.Imports() {
if g.validPkg(pkg) {
g.Printf("#include %q\n", g.namePrefixOf(pkg)+".h")
}
}
}
g.Printf("\n")
// Forward declaration of @class and @protocol
for _, s := range g.structs {
g.Printf("@class %s%s;\n", g.namePrefix, s.obj.Name())
}
for _, i := range g.interfaces {
g.Printf("@protocol %s%s;\n", g.namePrefix, i.obj.Name())
if i.summary.implementable {
g.Printf("@class %s%s;\n", g.namePrefix, i.obj.Name())
// Forward declaration for other cases will be handled at the beginning of genM.
}
}
if len(g.structs) > 0 || len(g.interfaces) > 0 {
g.Printf("\n")
}
// @interfaces
for _, s := range g.structs {
g.genStructH(s.obj, s.t)
g.Printf("\n")
}
for _, i := range g.interfaces {
g.genInterfaceH(i.obj, i.t)
g.Printf("\n")
}
// const
// TODO: prefix with k?, or use a class method?
for _, obj := range g.constants {
if _, ok := obj.Type().(*types.Basic); !ok {
g.Printf("// skipped const %s with unsupported type: %T\n\n", obj.Name(), obj)
continue
}
switch b := obj.Type().(*types.Basic); b.Kind() {
case types.String, types.UntypedString:
g.Printf("FOUNDATION_EXPORT NSString* const %s%s;\n", g.namePrefix, obj.Name())
default:
g.Printf("FOUNDATION_EXPORT const %s %s%s;\n", g.objcType(obj.Type()), g.namePrefix, obj.Name())
}
}
if len(g.constants) > 0 {
g.Printf("\n")
}
// var
if len(g.vars) > 0 {
g.Printf("@interface %s : NSObject\n", g.namePrefix)
for _, obj := range g.vars {
if t := obj.Type(); !g.isSupported(t) {
g.Printf("// skipped variable %s with unsupported type: %T\n\n", obj.Name(), t)
continue
}
objcType := g.objcType(obj.Type())
g.Printf("+ (%s) %s;\n", objcType, objcNameReplacer(lowerFirst(obj.Name())))
g.Printf("+ (void) set%s:(%s)v;\n", obj.Name(), objcType)
g.Printf("\n")
}
g.Printf("@end\n\n")
}
// static functions.
for _, obj := range g.funcs {
g.genFuncH(obj)
g.Printf("\n")
}
for _, i := range g.interfaces {
if i.summary.implementable {
g.Printf("@class %s%s;\n\n", g.namePrefix, i.obj.Name())
}
}
for _, i := range g.interfaces {
if i.summary.implementable {
// @interface Interface -- similar to what genStructH does.
g.genInterfaceInterface(i.obj, i.summary, true)
g.Printf("\n")
}
}
g.Printf("#endif\n")
if len(g.err) > 0 {
return g.err
}
return nil
}
func (g *objcGen) gobindOpts() string {
opts := []string{"-lang=objc"}
if g.prefix != "" {
opts = append(opts, "-prefix="+g.prefix)
}
return strings.Join(opts, " ")
}
func (g *objcGen) genM() error {
var pkgPath string
if g.Pkg != nil {
pkgPath = g.Pkg.Path()
}
g.Printf(objcPreamble, pkgPath, g.gobindOpts(), pkgPath)
g.Printf("#include <Foundation/Foundation.h>\n")
g.Printf("#include \"seq.h\"\n")
g.Printf("#include \"_cgo_export.h\"\n")
g.Printf("#include %q\n", g.namePrefix+".h")
g.Printf("\n")
// struct
for _, s := range g.structs {
g.genStructM(s.obj, s.t)
g.Printf("\n")
}
// interface
var needProxy []*types.TypeName
for _, i := range g.interfaces {
if g.genInterfaceM(i.obj, i.t) {
needProxy = append(needProxy, i.obj)
}
g.Printf("\n")
}
// const
for _, o := range g.constants {
g.genConstM(o)
}
if len(g.constants) > 0 {
g.Printf("\n")
}
// vars
if len(g.vars) > 0 {
g.Printf("@implementation %s\n", g.namePrefix)
for _, o := range g.vars {
g.genVarM(o)
}
g.Printf("@end\n\n")
}
g.Printf("\n")
for _, obj := range g.funcs {
if !g.isSigSupported(obj.Type()) {
g.Printf("// skipped function %s with unsupported parameter or return types\n\n", obj.Name())
continue
}
g.genFuncM(obj)
g.Printf("\n")
}
for _, i := range g.interfaces {
for _, m := range i.summary.callable {
if !g.isSigSupported(m.Type()) {
g.Printf("// skipped method %s.%s with unsupported parameter or return types\n\n", i.obj.Name(), m.Name())
continue
}
g.genInterfaceMethodProxy(i.obj, m)
}
}
g.Printf("__attribute__((constructor)) static void init() {\n")
g.Indent()
g.Printf("init_seq();\n")
g.Outdent()
g.Printf("}\n")
if len(g.err) > 0 {
return g.err
}
return nil
}
func (g *objcGen) genVarM(o *types.Var) {
if t := o.Type(); !g.isSupported(t) {
g.Printf("// skipped variable %s with unsupported type: %T\n\n", o.Name(), t)
return
}
objcType := g.objcType(o.Type())
// setter
g.Printf("+ (void) set%s:(%s)v {\n", o.Name(), objcType)
g.Indent()
g.genWrite("v", o.Type(), modeRetained)
g.Printf("var_set%s_%s(_v);\n", g.pkgPrefix, o.Name())
g.genRelease("v", o.Type(), modeRetained)
g.Outdent()
g.Printf("}\n\n")
// getter
g.Printf("+ (%s) %s {\n", objcType, objcNameReplacer(lowerFirst(o.Name())))
g.Indent()
g.Printf("%s r0 = ", g.cgoType(o.Type()))
g.Printf("var_get%s_%s();\n", g.pkgPrefix, o.Name())
g.genRead("_r0", "r0", o.Type(), modeRetained)
g.Printf("return _r0;\n")
g.Outdent()
g.Printf("}\n\n")
}
func (g *objcGen) genConstM(o *types.Const) {
if _, ok := o.Type().(*types.Basic); !ok {
g.Printf("// skipped const %s with unsupported type: %T\n\n", o.Name(), o)
return
}
cName := fmt.Sprintf("%s%s", g.namePrefix, o.Name())
objcType := g.objcType(o.Type())
switch b := o.Type().(*types.Basic); b.Kind() {
case types.Bool, types.UntypedBool:
v := "NO"
if constant.BoolVal(o.Val()) {
v = "YES"
}
g.Printf("const BOOL %s = %s;\n", cName, v)
case types.String, types.UntypedString:
g.Printf("NSString* const %s = @%s;\n", cName, constExactString(o))
case types.Int, types.Int8, types.Int16, types.Int32:
g.Printf("const %s %s = %s;\n", objcType, cName, o.Val())
case types.Int64, types.UntypedInt:
i, exact := constant.Int64Val(o.Val())
if !exact {
g.errorf("const value %s for %s cannot be represented as %s", o.Val(), o.Name(), objcType)
return
}
if i == math.MinInt64 {
// -9223372036854775808LL does not work because 922337203685477508 is
// larger than max int64.
g.Printf("const int64_t %s = %dLL-1;\n", cName, i+1)
} else {
g.Printf("const int64_t %s = %dLL;\n", cName, i)
}
case types.Float32, types.Float64, types.UntypedFloat:
f, _ := constant.Float64Val(o.Val())
if math.IsInf(f, 0) || math.Abs(f) > math.MaxFloat64 {
g.errorf("const value %s for %s cannot be represented as double", o.Val(), o.Name())
return
}
g.Printf("const %s %s = %g;\n", objcType, cName, f)
default:
g.errorf("unsupported const type %s for %s", b, o.Name())
}
}
type funcSummary struct {
name string
ret string
sig *types.Signature
params, retParams []paramInfo
}
type paramInfo struct {
typ types.Type
name string
}
func (g *objcGen) funcSummary(obj *types.Func) *funcSummary {
sig := obj.Type().(*types.Signature)
s := &funcSummary{name: obj.Name(), sig: sig}
params := sig.Params()
for i := 0; i < params.Len(); i++ {
p := params.At(i)
v := paramInfo{
typ: p.Type(),
name: paramName(params, i),
}
s.params = append(s.params, v)
}
res := sig.Results()
switch res.Len() {
case 0:
s.ret = "void"
case 1:
p := res.At(0)
if isErrorType(p.Type()) {
s.retParams = append(s.retParams, paramInfo{
typ: p.Type(),
name: "error",
})
s.ret = "BOOL"
} else {
name := p.Name()
if name == "" || paramRE.MatchString(name) {
name = "ret0_"
}
typ := p.Type()
s.retParams = append(s.retParams, paramInfo{typ: typ, name: name})
s.ret = g.objcType(typ)
}
case 2:
name := res.At(0).Name()
if name == "" || paramRE.MatchString(name) {
name = "ret0_"
}
s.retParams = append(s.retParams, paramInfo{
typ: res.At(0).Type(),
name: name,
})
if !isErrorType(res.At(1).Type()) {
g.errorf("second result value must be of type error: %s", obj)
return nil
}
s.retParams = append(s.retParams, paramInfo{
typ: res.At(1).Type(),
name: "error", // TODO(hyangah): name collision check.
})
s.ret = "BOOL"
default:
// TODO(hyangah): relax the constraint on multiple return params.
g.errorf("too many result values: %s", obj)
return nil
}
return s
}
func (s *funcSummary) asFunc(g *objcGen) string {
var params []string
for _, p := range s.params {
params = append(params, g.objcType(p.typ)+" "+p.name)
}
if !s.returnsVal() {
for _, p := range s.retParams {
params = append(params, g.objcType(p.typ)+"* "+p.name)
}
}
return fmt.Sprintf("%s %s%s(%s)", s.ret, g.namePrefix, s.name, strings.Join(params, ", "))
}
func (s *funcSummary) asMethod(g *objcGen) string {
var params []string
for i, p := range s.params {
var key string
if i != 0 {
key = p.name
}
params = append(params, fmt.Sprintf("%s:(%s)%s", key, g.objcType(p.typ), p.name))
}
if !s.returnsVal() {
for _, p := range s.retParams {
var key string
if len(params) > 0 {
key = p.name
}
params = append(params, fmt.Sprintf("%s:(%s)%s", key, g.objcType(p.typ)+"*", p.name))
}
}
return fmt.Sprintf("(%s)%s%s", s.ret, objcNameReplacer(lowerFirst(s.name)), strings.Join(params, " "))
}
func (s *funcSummary) callMethod(g *objcGen) string {
var params []string
for i, p := range s.params {
var key string
if i != 0 {
key = p.name
}
params = append(params, fmt.Sprintf("%s:_%s", key, p.name))
}
if !s.returnsVal() {
for _, p := range s.retParams {
var key string
if len(params) > 0 {
key = p.name
}
params = append(params, fmt.Sprintf("%s:&%s", key, p.name))
}
}
return fmt.Sprintf("%s%s", objcNameReplacer(lowerFirst(s.name)), strings.Join(params, " "))
}
func (s *funcSummary) returnsVal() bool {
return len(s.retParams) == 1 && !isErrorType(s.retParams[0].typ)
}
func (g *objcGen) genFuncH(obj *types.Func) {
if !g.isSigSupported(obj.Type()) {
g.Printf("// skipped function %s with unsupported parameter or return types\n\n", obj.Name())
return
}
if s := g.funcSummary(obj); s != nil {
g.Printf("FOUNDATION_EXPORT %s;\n", s.asFunc(g))
}
}
func (g *objcGen) genFuncM(obj *types.Func) {
s := g.funcSummary(obj)
if s == nil {
return
}
g.Printf("%s {\n", s.asFunc(g))
g.Indent()
g.genFunc(s, "")
g.Outdent()
g.Printf("}\n")
}
func (g *objcGen) genGetter(oName string, f *types.Var) {
t := f.Type()
g.Printf("- (%s)%s {\n", g.objcType(t), objcNameReplacer(lowerFirst(f.Name())))
g.Indent()
g.Printf("int32_t refnum = go_seq_go_to_refnum(self._ref);\n")
g.Printf("%s r0 = ", g.cgoType(f.Type()))
g.Printf("proxy%s_%s_%s_Get(refnum);\n", g.pkgPrefix, oName, f.Name())
g.genRead("_r0", "r0", f.Type(), modeRetained)
g.Printf("return _r0;\n")
g.Outdent()
g.Printf("}\n\n")
}
func (g *objcGen) genSetter(oName string, f *types.Var) {
t := f.Type()
g.Printf("- (void)set%s:(%s)v {\n", f.Name(), g.objcType(t))
g.Indent()
g.Printf("int32_t refnum = go_seq_go_to_refnum(self._ref);\n")
g.genWrite("v", f.Type(), modeRetained)
g.Printf("proxy%s_%s_%s_Set(refnum, _v);\n", g.pkgPrefix, oName, f.Name())
g.genRelease("v", f.Type(), modeRetained)
g.Outdent()
g.Printf("}\n\n")
}
func (g *objcGen) genWrite(varName string, t types.Type, mode varMode) {
switch t := t.(type) {
case *types.Basic:
switch t.Kind() {
case types.String:
g.Printf("nstring _%s = go_seq_from_objc_string(%s);\n", varName, varName)
default:
g.Printf("%s _%s = (%s)%s;\n", g.cgoType(t), varName, g.cgoType(t), varName)
}
case *types.Slice:
switch e := t.Elem().(type) {
case *types.Basic:
switch e.Kind() {
case types.Uint8: // Byte.
g.Printf("nbyteslice _%s = go_seq_from_objc_bytearray(%s, %d);\n", varName, varName, toCFlag(mode == modeRetained))
default:
g.errorf("unsupported type: %s", t)
}
default:
g.errorf("unsupported type: %s", t)
}
case *types.Named:
switch u := t.Underlying().(type) {
case *types.Interface:
g.genRefWrite(varName, t)
default:
g.errorf("unsupported named type: %s / %T", u, u)
}
case *types.Pointer:
g.genRefWrite(varName, t)
default:
g.Printf("%s _%s = (%s)%s;\n", g.cgoType(t), varName, g.cgoType(t), varName)
}
}
func (g *objcGen) genRefWrite(varName string, t types.Type) {
g.Printf("int32_t _%s;\n", varName)
g.Printf("if ([(id<NSObject>)(%s) isKindOfClass:[%s class]]) {\n", varName, g.refTypeBase(t))
g.Indent()
g.Printf("id<goSeqRefInterface> %[1]s_proxy = (id<goSeqRefInterface>)(%[1]s);\n", varName)
g.Printf("_%s = go_seq_go_to_refnum(%s_proxy._ref);\n", varName, varName)
g.Outdent()
g.Printf("} else {\n")
g.Indent()
g.Printf("_%s = go_seq_to_refnum(%s);\n", varName, varName)
g.Outdent()
g.Printf("}\n")
}
func (g *objcGen) genRefRead(toName, fromName string, t types.Type) {
ptype := g.refTypeBase(t)
g.Printf("%s* %s = nil;\n", ptype, toName)
g.Printf("GoSeqRef* %s_ref = go_seq_from_refnum(%s);\n", toName, fromName)
g.Printf("if (%s_ref != NULL) {\n", toName)
g.Printf(" %s = %s_ref.obj;\n", toName, toName)
g.Printf(" if (%s == nil) {\n", toName)
g.Printf(" %s = [[%s alloc] initWithRef:%s_ref];\n", toName, ptype, toName)
g.Printf(" }\n")
g.Printf("}\n")
}
func (g *objcGen) genRead(toName, fromName string, t types.Type, mode varMode) {
switch t := t.(type) {
case *types.Basic:
switch t.Kind() {
case types.String:
g.Printf("NSString *%s = go_seq_to_objc_string(%s);\n", toName, fromName)
case types.Bool:
g.Printf("BOOL %s = %s ? YES : NO;\n", toName, fromName)
default:
g.Printf("%s %s = (%s)%s;\n", g.objcType(t), toName, g.objcType(t), fromName)
}
case *types.Slice:
switch e := t.Elem().(type) {
case *types.Basic:
switch e.Kind() {
case types.Uint8: // Byte.
g.Printf("NSData *%s = go_seq_to_objc_bytearray(%s, %d);\n", toName, fromName, toCFlag(mode == modeRetained))
default:
g.errorf("unsupported type: %s", t)
}
default:
g.errorf("unsupported type: %s", t)
}
case *types.Pointer:
switch t := t.Elem().(type) {
case *types.Named:
g.genRefRead(toName, fromName, types.NewPointer(t))
default:
g.errorf("unsupported type %s", t)
}
case *types.Named:
switch t.Underlying().(type) {
case *types.Interface, *types.Pointer:
g.genRefRead(toName, fromName, t)
default:
g.errorf("unsupported, direct named type %s", t)
}
default:
g.Printf("%s %s = (%s)%s;\n", g.objcType(t), toName, g.objcType(t), fromName)
}
}
func (g *objcGen) genFunc(s *funcSummary, objName string) {
if objName != "" {
g.Printf("int32_t refnum = go_seq_go_to_refnum(self._ref);\n")
}
for _, p := range s.params {
g.genWrite(p.name, p.typ, modeTransient)
}
resPrefix := ""
if len(s.retParams) > 0 {
if len(s.retParams) == 1 {
g.Printf("%s r0 = ", g.cgoType(s.retParams[0].typ))
} else {
resPrefix = "res."
g.Printf("struct proxy%s_%s_%s_return res = ", g.pkgPrefix, objName, s.name)
}
}
g.Printf("proxy%s_%s_%s(", g.pkgPrefix, objName, s.name)
if objName != "" {
g.Printf("refnum")
}
for i, p := range s.params {
if i > 0 || objName != "" {
g.Printf(", ")
}
g.Printf("_%s", p.name)
}
g.Printf(");\n")
for _, p := range s.params {
g.genRelease(p.name, p.typ, modeTransient)
}
for i, r := range s.retParams {
g.genRead("_"+r.name, fmt.Sprintf("%sr%d", resPrefix, i), r.typ, modeRetained)
}
if !s.returnsVal() {
for _, p := range s.retParams {
if isErrorType(p.typ) {
g.Printf("if (_%s != nil && %s != nil) {\n", p.name, p.name)
g.Indent()
g.Printf("*%s = _%s;\n", p.name, p.name)
g.Outdent()
g.Printf("}\n")
} else {
g.Printf("*%s = _%s;\n", p.name, p.name)
}
}
}
if n := len(s.retParams); n > 0 {
p := s.retParams[n-1]
if isErrorType(p.typ) {
g.Printf("return (_%s == nil);\n", p.name)
} else {
g.Printf("return _%s;\n", p.name)
}
}
}
func (g *objcGen) genInterfaceInterface(obj *types.TypeName, summary ifaceSummary, isProtocol bool) {
g.Printf("@interface %[1]s%[2]s : ", g.namePrefix, obj.Name())
if isErrorType(obj.Type()) {
g.Printf("NSError")
} else {
g.Printf("NSObject")
}
if isProtocol {
g.Printf(" <%[1]s%[2]s>", g.namePrefix, obj.Name())
}
g.Printf(" {\n}\n")
g.Printf("@property(strong, readonly) id _ref;\n")
g.Printf("\n")
g.Printf("- (instancetype)initWithRef:(id)ref;\n")
for _, m := range summary.callable {
if !g.isSigSupported(m.Type()) {
g.Printf("// skipped method %s.%s with unsupported parameter or return types\n\n", obj.Name(), m.Name())
continue
}
s := g.funcSummary(m)
g.Printf("- %s;\n", s.asMethod(g))
}
g.Printf("@end\n")
}
func (g *objcGen) genInterfaceH(obj *types.TypeName, t *types.Interface) {
summary := makeIfaceSummary(t)
if !summary.implementable {
g.genInterfaceInterface(obj, summary, false)
return
}
g.Printf("@protocol %s%s\n", g.namePrefix, obj.Name())
for _, m := range makeIfaceSummary(t).callable {
if !g.isSigSupported(m.Type()) {
g.Printf("// skipped method %s.%s with unsupported parameter or return types\n\n", obj.Name(), m.Name())
continue
}
s := g.funcSummary(m)
g.Printf("- %s;\n", s.asMethod(g))
}
g.Printf("@end\n")
}
func (g *objcGen) genInterfaceM(obj *types.TypeName, t *types.Interface) bool {
summary := makeIfaceSummary(t)
// @implementation Interface -- similar to what genStructM does.
g.Printf("@implementation %s%s {\n", g.namePrefix, obj.Name())
g.Printf("}\n")
g.Printf("\n")
g.Printf("- (instancetype)initWithRef:(id)ref {\n")
g.Indent()
if isErrorType(obj.Type()) {
g.Printf("if (self) {\n")
g.Printf(" __ref = ref;\n")
g.Printf(" self = [super initWithDomain:@\"go\" code:1 userInfo:@{NSLocalizedDescriptionKey: [self error]}];\n")
g.Printf("}\n")
} else {
g.Printf("self = [super init];\n")
g.Printf("if (self) { __ref = ref; }\n")
}
g.Printf("return self;\n")
g.Outdent()
g.Printf("}\n")
g.Printf("\n")
for _, m := range summary.callable {
if !g.isSigSupported(m.Type()) {
g.Printf("// skipped method %s.%s with unsupported parameter or return types\n\n", obj.Name(), m.Name())
continue
}
s := g.funcSummary(m)
g.Printf("- %s {\n", s.asMethod(g))
g.Indent()
g.genFunc(s, obj.Name())
g.Outdent()
g.Printf("}\n\n")
}
g.Printf("@end\n")
g.Printf("\n")
return summary.implementable
}
func (g *objcGen) genInterfaceMethodProxy(obj *types.TypeName, m *types.Func) {
oName := obj.Name()
s := g.funcSummary(m)
g.genInterfaceMethodSignature(m, oName, false)
g.Indent()
g.Printf("@autoreleasepool {\n")
g.Indent()
g.Printf("%s* o = go_seq_objc_from_refnum(refnum);\n", g.refTypeBase(obj.Type()))
for _, p := range s.params {
g.genRead("_"+p.name, p.name, p.typ, modeTransient)
}
// call method
if !s.returnsVal() {
for _, p := range s.retParams {
if isErrorType(p.typ) {
g.Printf("NSError* %s = nil;\n", p.name)
} else {
g.Printf("%s %s;\n", g.objcType(p.typ), p.name)
}
}
}
if isErrorType(obj.Type()) && m.Name() == "Error" {
// As a special case, ObjC NSErrors are passed to Go pretending to implement the Go error interface.
// They don't actually have an Error method, so calls to to it needs to be rerouted.
g.Printf("NSString *returnVal = [o localizedDescription];\n")
} else {
if s.ret == "void" {
g.Printf("[o %s];\n", s.callMethod(g))
} else {
g.Printf("%s returnVal = [o %s];\n", s.ret, s.callMethod(g))
}
}
if len(s.retParams) > 0 {
if s.returnsVal() { // len(s.retParams) == 1 && s.retParams[0] != error
p := s.retParams[0]
g.genWrite("returnVal", p.typ, modeRetained)
g.Printf("return _returnVal;\n")
} else {
var rets []string
for i, p := range s.retParams {
if isErrorType(p.typ) {
g.Printf("id<GoUniverseerror> _%s = nil;\n", p.name)
if i == len(s.retParams)-1 { // last param.
g.Printf("if (!returnVal) {\n")
} else {
g.Printf("if (%s != nil) {\n", p.name)
}
g.Indent()
g.Printf("_%[1]s = %[1]s;\n", p.name)
g.Outdent()
g.Printf("}\n")
g.genWrite("_"+p.name, p.typ, modeRetained)
rets = append(rets, "__"+p.name)
} else {
g.genWrite(p.name, p.typ, modeRetained)
rets = append(rets, "_"+p.name)
}
}
if len(rets) > 1 {
g.Printf("cproxy%s_%s_%s_return _sres = {\n", g.pkgPrefix, oName, m.Name())
g.Printf(" %s\n", strings.Join(rets, ", "))
g.Printf("};\n")
g.Printf("return _sres;\n")
} else {
g.Printf("return %s;\n", rets[0])
}
}
}
g.Outdent()
g.Printf("}\n")
g.Outdent()
g.Printf("}\n\n")
}
// genRelease cleans up arguments that weren't copied in genWrite.
func (g *objcGen) genRelease(varName string, t types.Type, mode varMode) {
switch t := t.(type) {
case *types.Slice:
switch e := t.Elem().(type) {
case *types.Basic:
switch e.Kind() {
case types.Uint8: // Byte.
if mode == modeTransient {
// If the argument was not mutable, go_seq_from_objc_bytearray created a copy.
// Free it here.
g.Printf("if (![%s isKindOfClass:[NSMutableData class]]) {\n", varName)
g.Printf(" free(_%s.ptr);\n", varName)
g.Printf("}\n")
}
}
}
}
}
func (g *objcGen) genStructH(obj *types.TypeName, t *types.Struct) {
g.Printf("@interface %s%s : NSObject {\n", g.namePrefix, obj.Name())
g.Printf("}\n")
g.Printf("@property(strong, readonly) id _ref;\n")
g.Printf("\n")
g.Printf("- (id)initWithRef:(id)ref;\n")
// accessors to exported fields.
for _, f := range exportedFields(t) {
if t := f.Type(); !g.isSupported(t) {
g.Printf("// skipped field %s.%s with unsupported type: %T\n\n", obj.Name(), f.Name(), t)
continue
}
name, typ := f.Name(), g.objcFieldType(f.Type())
g.Printf("- (%s)%s;\n", typ, objcNameReplacer(lowerFirst(name)))
g.Printf("- (void)set%s:(%s)v;\n", name, typ)
}
// exported methods
for _, m := range exportedMethodSet(types.NewPointer(obj.Type())) {
if !g.isSigSupported(m.Type()) {
g.Printf("// skipped method %s.%s with unsupported parameter or return types\n\n", obj.Name(), m.Name())
continue
}
s := g.funcSummary(m)
g.Printf("- %s;\n", objcNameReplacer(lowerFirst(s.asMethod(g))))
}
g.Printf("@end\n")
}
func (g *objcGen) genStructM(obj *types.TypeName, t *types.Struct) {
fields := exportedFields(t)
methods := exportedMethodSet(types.NewPointer(obj.Type()))
g.Printf("\n")
g.Printf("@implementation %s%s {\n", g.namePrefix, obj.Name())
g.Printf("}\n\n")
g.Printf("- (id)initWithRef:(id)ref {\n")
g.Indent()
g.Printf("self = [super init];\n")
g.Printf("if (self) { __ref = ref; }\n")
g.Printf("return self;\n")
g.Outdent()
g.Printf("}\n\n")
for _, f := range fields {
if !g.isSupported(f.Type()) {
g.Printf("// skipped unsupported field %s with type %T\n\n", f.Name(), f)
continue
}
g.genGetter(obj.Name(), f)
g.genSetter(obj.Name(), f)
}
for _, m := range methods {
if !g.isSigSupported(m.Type()) {
g.Printf("// skipped method %s.%s with unsupported parameter or return types\n\n", obj.Name(), m.Name())
continue
}
s := g.funcSummary(m)
g.Printf("- %s {\n", s.asMethod(g))
g.Indent()
g.genFunc(s, obj.Name())
g.Outdent()
g.Printf("}\n\n")
}
g.Printf("@end\n")
}
func (g *objcGen) errorf(format string, args ...interface{}) {
g.err = append(g.err, fmt.Errorf(format, args...))
}
func (g *objcGen) refTypeBase(typ types.Type) string {
switch typ := typ.(type) {
case *types.Pointer:
if _, ok := typ.Elem().(*types.Named); ok {
return g.objcType(typ.Elem())
}
case *types.Named:
n := typ.Obj()
if isErrorType(typ) || g.validPkg(n.Pkg()) {
switch typ.Underlying().(type) {
case *types.Interface, *types.Struct:
return g.namePrefixOf(n.Pkg()) + n.Name()
}
}
}
// fallback to whatever objcType returns. This must not happen.
return g.objcType(typ)
}
func (g *objcGen) objcFieldType(t types.Type) string {
if isErrorType(t) {
return "NSError*"
}
return g.objcType(t)
}
func (g *objcGen) objcType(typ types.Type) string {
if isErrorType(typ) {
return "NSError*"
}
switch typ := typ.(type) {
case *types.Basic:
switch typ.Kind() {
case types.Bool, types.UntypedBool:
return "BOOL"
case types.Int:
return "long"
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:
// byte is an alias of uint8, and the alias is lost.
return "byte"
case types.Uint16:
return "uint16_t"
case types.Uint32:
return "uint32_t"
case types.Uint64:
return "uint64_t"
case types.Float32:
return "float"
case types.Float64, types.UntypedFloat:
return "double"
case types.String, types.UntypedString:
return "NSString*"
default:
g.errorf("unsupported type: %s", typ)
return "TODO"
}
case *types.Slice:
elem := g.objcType(typ.Elem())
// Special case: NSData seems to be a better option for byte slice.
if elem == "byte" {
return "NSData*"
}
// TODO(hyangah): support other slice types: NSArray or CFArrayRef.
// Investigate the performance implication.
g.errorf("unsupported type: %s", typ)
return "TODO"
case *types.Pointer:
if _, ok := typ.Elem().(*types.Named); ok {
return g.objcType(typ.Elem()) + "*"
}
g.errorf("unsupported pointer to type: %s", typ)
return "TODO"
case *types.Named:
n := typ.Obj()
if !isErrorType(typ) && !g.validPkg(n.Pkg()) {
g.errorf("type %s is in package %s, which is not bound", n.Name(), n.Pkg().Name())
return "TODO"
}
switch t := typ.Underlying().(type) {
case *types.Interface:
if makeIfaceSummary(t).implementable {
return "id<" + g.namePrefixOf(n.Pkg()) + n.Name() + ">"
} else {
return g.namePrefixOf(n.Pkg()) + n.Name() + "*"
}
case *types.Struct:
return g.namePrefixOf(n.Pkg()) + n.Name()
}
g.errorf("unsupported, named type %s", typ)
return "TODO"
default:
g.errorf("unsupported type: %#+v, %s", typ, typ)
return "TODO"
}
}
var objcNameReplacer = newNameSanitizer([]string{
"void", "char", "short", "int", "long", "float", "double", "signed",
"unsigned", "id", "const", "volatile", "in", "out", "inout", "bycopy",
"byref", "oneway", "self", "super", "init"})
const (
objcPreamble = `// Objective-C API for talking to %[1]s Go package.
// gobind %[2]s %[3]s
//
// File is generated by gobind. Do not edit.
`
)