bind: generate ObjC initializers

For functions on the form

New<T>... (...) *T

or

New<T>... (...) (*T, error)

generate corresponding initializers. The name of an initializer is
the function name where "New<T>" is replaced by "init".

If no functions match for a type *T, generate a default (empty)
initializer that returns new(T). The default initializer mirrors
the default constructor in Java.

Fixes golang/go#20254.

Change-Id: I3c317418fa517d3f2de3f67f400867285b11ea4f
Reviewed-on: https://go-review.googlesource.com/52012
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/bind/genobjc.go b/bind/genobjc.go
index f306d5e..38d723e 100644
--- a/bind/genobjc.go
+++ b/bind/genobjc.go
@@ -33,6 +33,10 @@
 	// Structs that embeds Objc wrapper types.
 	ostructs map[*types.TypeName]*objcClassInfo
 	modules  []string
+	// Constructors is a map from Go struct types to a list
+	// of exported constructor functions for the type, on the form
+	// func New<Type>(...) *Type
+	constructors map[*types.TypeName][]*types.Func
 }
 
 type objcClassInfo struct {
@@ -47,6 +51,7 @@
 	g.Generator.Init()
 	g.namePrefix = g.namePrefixOf(g.Pkg)
 	g.wrapMap = make(map[string]*objc.Named)
+	g.constructors = make(map[*types.TypeName][]*types.Func)
 	modMap := make(map[string]struct{})
 	for _, w := range wrappers {
 		g.wrapMap[w.GoName] = w
@@ -84,6 +89,11 @@
 		}
 		g.ostructs[s.obj] = inf
 	}
+	for _, f := range g.funcs {
+		if t := g.constructorType(f); t != nil {
+			g.constructors[t] = append(g.constructors[t], f)
+		}
+	}
 }
 
 func (g *ObjcGen) namePrefixOf(pkg *types.Package) string {
@@ -407,6 +417,7 @@
 	sig               *types.Signature
 	params, retParams []paramInfo
 	hasself           bool
+	initName          string
 }
 
 type paramInfo struct {
@@ -462,6 +473,11 @@
 		}
 		s.params = append(s.params, v)
 	}
+	if obj != nil {
+		if pref := "New" + obj.Name(); strings.Index(f.Name(), pref) != -1 {
+			s.initName = "init" + f.Name()[len(pref):]
+		}
+	}
 	res := sig.Results()
 	switch res.Len() {
 	case 0:
@@ -532,6 +548,10 @@
 }
 
 func (s *funcSummary) asMethod(g *ObjcGen) string {
+	return fmt.Sprintf("(%s)%s%s", s.ret, objcNameReplacer(lowerFirst(s.name)), s.asSignature(g))
+}
+
+func (s *funcSummary) asSignature(g *ObjcGen) string {
 	var params []string
 	skip := 0
 	if s.hasself {
@@ -555,7 +575,19 @@
 		}
 		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, " "))
+	return strings.Join(params, " ")
+}
+
+func (s *funcSummary) asInitSignature(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))
+	}
+	return strings.Join(params, " ")
 }
 
 func (s *funcSummary) callMethod(g *ObjcGen) string {
@@ -1028,9 +1060,19 @@
 	g.Printf("}\n")
 	g.Printf("@property(strong, readonly) id _ref;\n")
 	g.Printf("\n")
-	g.Printf("- (id)initWithRef:(id)ref;\n")
-	if oinf != nil {
-		g.Printf("- (id)init;\n")
+	g.Printf("- (instancetype)initWithRef:(id)ref;\n")
+	cons := g.constructors[obj]
+	if oinf == nil {
+		for _, f := range cons {
+			if !g.isSigSupported(f.Type()) {
+				g.Printf("// skipped constructor %s.%s with unsupported parameter or return types\n\n", obj.Name(), f.Name())
+				continue
+			}
+			g.genInitH(obj, f)
+		}
+	}
+	if oinf != nil || len(cons) == 0 {
+		g.Printf("- (instancetype)init;\n")
 	}
 
 	// accessors to exported fields.
@@ -1064,15 +1106,21 @@
 	oinf := g.ostructs[obj]
 	g.Printf("@implementation %s%s {\n", g.namePrefix, obj.Name())
 	g.Printf("}\n\n")
-	g.Printf("- (id)initWithRef:(id)ref {\n")
+	g.Printf("- (instancetype)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")
-	if oinf != nil {
-		g.Printf("- (id)init {\n")
+	cons := g.constructors[obj]
+	if oinf == nil {
+		for _, f := range cons {
+			g.genInitM(obj, f)
+		}
+	}
+	if oinf != nil || len(cons) == 0 {
+		g.Printf("- (instancetype)init {\n")
 		g.Indent()
 		g.Printf("self = [super init];\n")
 		g.Printf("if (self) {\n")
@@ -1109,6 +1157,51 @@
 	g.Printf("@end\n\n")
 }
 
+func (g *ObjcGen) genInitH(obj *types.TypeName, f *types.Func) {
+	s := g.funcSummary(obj, f)
+	g.Printf("- (instancetype)%s%s;\n", s.initName, s.asInitSignature(g))
+}
+
+func (g *ObjcGen) genInitM(obj *types.TypeName, f *types.Func) {
+	s := g.funcSummary(obj, f)
+	g.Printf("- (instancetype)%s%s {\n", s.initName, s.asInitSignature(g))
+	g.Indent()
+	g.Printf("self = [super init];\n")
+	g.Printf("if (!self) return nil;\n")
+	for _, p := range s.params {
+		g.genWrite(p.name, p.typ, modeTransient)
+	}
+	// Constructors always return a mandatory *T and an optional error
+	if len(s.retParams) == 1 {
+		g.Printf("%s refnum = ", g.cgoType(s.retParams[0].typ))
+	} else {
+		g.Printf("struct proxy%s__%s_return res = ", g.pkgPrefix, s.goname)
+	}
+	g.Printf("proxy%s__%s(", g.pkgPrefix, s.goname)
+	for i, p := range s.params {
+		if i > 0 {
+			g.Printf(", ")
+		}
+		g.Printf("_%s", p.name)
+	}
+	g.Printf(");\n")
+	for _, p := range s.params {
+		g.genRelease(p.name, p.typ, modeTransient)
+	}
+	if len(s.retParams) == 2 {
+		g.Printf("int32_t refnum = res.r0;\n")
+		g.Printf("GoSeqRef *_err = go_seq_from_refnum(res.r1);\n")
+	}
+	g.Printf("__ref = go_seq_from_refnum(refnum);\n")
+	if len(s.retParams) == 2 {
+		g.Printf("if (_err != NULL)\n")
+		g.Printf("	return nil;\n")
+	}
+	g.Printf("return self;\n")
+	g.Outdent()
+	g.Printf("}\n\n")
+}
+
 func (g *ObjcGen) errorf(format string, args ...interface{}) {
 	g.err = append(g.err, fmt.Errorf(format, args...))
 }
diff --git a/bind/objc/SeqTest.m b/bind/objc/SeqTest.m
index 139a9f9..21c395c 100644
--- a/bind/objc/SeqTest.m
+++ b/bind/objc/SeqTest.m
@@ -453,4 +453,28 @@
 - (void)testTags {
 	XCTAssertEqual(42, TestpkgTaggedConst, @"Tagged const must exist");
 }
+
+- (void)testConstructors {
+	id<TestpkgInterface> i = [[TestpkgConcrete alloc] init];
+	[i f];
+
+	TestpkgS2 *s = [[TestpkgS2 alloc] init:1 y:2];
+	XCTAssertEqual(3.0, [s sum]);
+	XCTAssertEqualObjects(@"gostring", [s tryTwoStrings:@"go" second:@"string"]);
+
+	TestpkgS3 *s3 __attribute__((unused)) = [[TestpkgS3 alloc] init];
+
+	TestpkgS4 *s4 = [[TestpkgS4 alloc] initWithInt:123];
+	XCTAssertEqual(123, s4.i);
+
+	s4 = [[TestpkgS4 alloc] initWithFloat: 123.456];
+	XCTAssertEqual(123, s4.i);
+
+	s4 = [[TestpkgS4 alloc] initWithBoolAndError: false];
+	XCTAssertEqual(0, s4.i);
+
+	s4 = [[TestpkgS4 alloc] initWithBoolAndError: true];
+	XCTAssertEqual(s4, NULL);
+}
+
 @end
diff --git a/bind/testdata/ignore.objc.h.golden b/bind/testdata/ignore.objc.h.golden
index 29e3296..055dde5 100644
--- a/bind/testdata/ignore.objc.h.golden
+++ b/bind/testdata/ignore.objc.h.golden
@@ -18,7 +18,8 @@
 }
 @property(strong, readonly) id _ref;
 
-- (id)initWithRef:(id)ref;
+- (instancetype)initWithRef:(id)ref;
+- (instancetype)init;
 // skipped field S.F with unsupported type: *types.Interface
 
 // skipped method S.Argument with unsupported parameter or return types
diff --git a/bind/testdata/ignore.objc.m.golden b/bind/testdata/ignore.objc.m.golden
index ed5b7c2..2e536c7 100644
--- a/bind/testdata/ignore.objc.m.golden
+++ b/bind/testdata/ignore.objc.m.golden
@@ -12,12 +12,20 @@
 @implementation IgnoreS {
 }
 
-- (id)initWithRef:(id)ref {
+- (instancetype)initWithRef:(id)ref {
 	self = [super init];
 	if (self) { __ref = ref; }
 	return self;
 }
 
+- (instancetype)init {
+	self = [super init];
+	if (self) {
+		__ref = go_seq_from_refnum(new_ignore_S());
+	}
+	return self;
+}
+
 // skipped unsupported field F with type *types.Var
 
 // skipped method S.Argument with unsupported parameter or return types
diff --git a/bind/testdata/issue10788.objc.h.golden b/bind/testdata/issue10788.objc.h.golden
index 083208b..ee469df 100644
--- a/bind/testdata/issue10788.objc.h.golden
+++ b/bind/testdata/issue10788.objc.h.golden
@@ -18,7 +18,8 @@
 }
 @property(strong, readonly) id _ref;
 
-- (id)initWithRef:(id)ref;
+- (instancetype)initWithRef:(id)ref;
+- (instancetype)init;
 - (NSString*)value;
 - (void)setValue:(NSString*)v;
 @end
diff --git a/bind/testdata/issue10788.objc.m.golden b/bind/testdata/issue10788.objc.m.golden
index aa752c7..e8f6ba3 100644
--- a/bind/testdata/issue10788.objc.m.golden
+++ b/bind/testdata/issue10788.objc.m.golden
@@ -12,12 +12,20 @@
 @implementation Issue10788TestStruct {
 }
 
-- (id)initWithRef:(id)ref {
+- (instancetype)initWithRef:(id)ref {
 	self = [super init];
 	if (self) { __ref = ref; }
 	return self;
 }
 
+- (instancetype)init {
+	self = [super init];
+	if (self) {
+		__ref = go_seq_from_refnum(new_issue10788_TestStruct());
+	}
+	return self;
+}
+
 - (NSString*)value {
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
 	nstring r0 = proxyissue10788_TestStruct_Value_Get(refnum);
diff --git a/bind/testdata/issue12328.objc.h.golden b/bind/testdata/issue12328.objc.h.golden
index 0cf0d70..7499c14 100644
--- a/bind/testdata/issue12328.objc.h.golden
+++ b/bind/testdata/issue12328.objc.h.golden
@@ -16,7 +16,8 @@
 }
 @property(strong, readonly) id _ref;
 
-- (id)initWithRef:(id)ref;
+- (instancetype)initWithRef:(id)ref;
+- (instancetype)init;
 - (NSError*)err;
 - (void)setErr:(NSError*)v;
 @end
diff --git a/bind/testdata/issue12328.objc.m.golden b/bind/testdata/issue12328.objc.m.golden
index 5487b26..e46f6ac 100644
--- a/bind/testdata/issue12328.objc.m.golden
+++ b/bind/testdata/issue12328.objc.m.golden
@@ -12,12 +12,20 @@
 @implementation Issue12328T {
 }
 
-- (id)initWithRef:(id)ref {
+- (instancetype)initWithRef:(id)ref {
 	self = [super init];
 	if (self) { __ref = ref; }
 	return self;
 }
 
+- (instancetype)init {
+	self = [super init];
+	if (self) {
+		__ref = go_seq_from_refnum(new_issue12328_T());
+	}
+	return self;
+}
+
 - (NSError*)err {
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
 	int32_t r0 = proxyissue12328_T_Err_Get(refnum);
diff --git a/bind/testdata/structs.objc.h.golden b/bind/testdata/structs.objc.h.golden
index 424d2b1..00ad30c 100644
--- a/bind/testdata/structs.objc.h.golden
+++ b/bind/testdata/structs.objc.h.golden
@@ -19,7 +19,8 @@
 }
 @property(strong, readonly) id _ref;
 
-- (id)initWithRef:(id)ref;
+- (instancetype)initWithRef:(id)ref;
+- (instancetype)init;
 - (double)x;
 - (void)setX:(double)v;
 - (double)y;
@@ -32,7 +33,8 @@
 }
 @property(strong, readonly) id _ref;
 
-- (id)initWithRef:(id)ref;
+- (instancetype)initWithRef:(id)ref;
+- (instancetype)init;
 - (void)m;
 - (NSString*)string;
 @end
diff --git a/bind/testdata/structs.objc.m.golden b/bind/testdata/structs.objc.m.golden
index f4557ce..3d28302 100644
--- a/bind/testdata/structs.objc.m.golden
+++ b/bind/testdata/structs.objc.m.golden
@@ -12,12 +12,20 @@
 @implementation StructsS {
 }
 
-- (id)initWithRef:(id)ref {
+- (instancetype)initWithRef:(id)ref {
 	self = [super init];
 	if (self) { __ref = ref; }
 	return self;
 }
 
+- (instancetype)init {
+	self = [super init];
+	if (self) {
+		__ref = go_seq_from_refnum(new_structs_S());
+	}
+	return self;
+}
+
 - (double)x {
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
 	double r0 = proxystructs_S_X_Get(refnum);
@@ -86,12 +94,20 @@
 @implementation StructsS2 {
 }
 
-- (id)initWithRef:(id)ref {
+- (instancetype)initWithRef:(id)ref {
 	self = [super init];
 	if (self) { __ref = ref; }
 	return self;
 }
 
+- (instancetype)init {
+	self = [super init];
+	if (self) {
+		__ref = go_seq_from_refnum(new_structs_S2());
+	}
+	return self;
+}
+
 - (void)m {
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
 	proxystructs_S2_M(refnum);
diff --git a/bind/testdata/vars.objc.h.golden b/bind/testdata/vars.objc.h.golden
index eb39edc..9453f6f 100644
--- a/bind/testdata/vars.objc.h.golden
+++ b/bind/testdata/vars.objc.h.golden
@@ -18,7 +18,8 @@
 }
 @property(strong, readonly) id _ref;
 
-- (id)initWithRef:(id)ref;
+- (instancetype)initWithRef:(id)ref;
+- (instancetype)init;
 @end
 
 @protocol VarsI <NSObject>
diff --git a/bind/testdata/vars.objc.m.golden b/bind/testdata/vars.objc.m.golden
index 2aee07b..6e5cb3c 100644
--- a/bind/testdata/vars.objc.m.golden
+++ b/bind/testdata/vars.objc.m.golden
@@ -12,12 +12,20 @@
 @implementation VarsS {
 }
 
-- (id)initWithRef:(id)ref {
+- (instancetype)initWithRef:(id)ref {
 	self = [super init];
 	if (self) { __ref = ref; }
 	return self;
 }
 
+- (instancetype)init {
+	self = [super init];
+	if (self) {
+		__ref = go_seq_from_refnum(new_vars_S());
+	}
+	return self;
+}
+
 @end