bind: avoid ObjC reserved names

The new tests in CL 28494 exposed a bug: the ObjC generator does
not avoid reserved names and names with special meaning ("init").
Generalize the name sanitizer from the Java generator and use that.

Also, move the lowerFirst function to gen.go since it is now used
by both generators.

Change-Id: I25b7af2594b2ea136f05d2bab1cfdc66ba169859
Reviewed-on: https://go-review.googlesource.com/28592
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/bind/gen.go b/bind/gen.go
index dcf3a9a..8d482d7 100644
--- a/bind/gen.go
+++ b/bind/gen.go
@@ -11,6 +11,9 @@
 	"go/types"
 	"io"
 	"regexp"
+	"strings"
+	"unicode"
+	"unicode/utf8"
 )
 
 type (
@@ -340,3 +343,43 @@
 	// TODO: warning?
 	return v.String()
 }
+
+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)
+	}
+}
diff --git a/bind/genjava.go b/bind/genjava.go
index bf9f77d..a36333d 100644
--- a/bind/genjava.go
+++ b/bind/genjava.go
@@ -375,7 +375,7 @@
 	} else {
 		g.Printf(g.className())
 	}
-	oName := javaNameReplacer.Replace(lowerFirst(o.Name()))
+	oName := javaNameReplacer(lowerFirst(o.Name()))
 	if strings.HasSuffix(oName, "_") {
 		oName += "1" // JNI doesn't like methods ending with underscore, needs the _1 suffixing
 	}
@@ -435,7 +435,7 @@
 	if !header {
 		g.Printf("native ")
 	}
-	g.Printf("%s %s(", ret, javaNameReplacer.Replace(lowerFirst(o.Name())))
+	g.Printf("%s %s(", ret, javaNameReplacer(lowerFirst(o.Name())))
 	params := sig.Params()
 	for i := 0; i < params.Len(); i++ {
 		if i > 0 {
@@ -564,51 +564,20 @@
 	return strings.Join(opts, " ")
 }
 
-var javaKeywordsAndReserves = []string{
+var javaNameReplacer = newNameSanitizer([]string{
 	"abstract", "assert", "boolean", "break", "byte", "case", "catch", "char",
 	"class", "const", "continue", "default", "do", "double", "else", "enum",
 	"extends", "final", "finally", "float", "for", "goto", "if", "implements",
 	"import", "instanceof", "int", "interface", "long", "native", "new", "package",
 	"private", "protected", "public", "return", "short", "static", "strictfp",
 	"super", "switch", "synchronized", "this", "throw", "throws", "transient",
-	"try", "void", "volatile", "while", "false", "null", "true"}
-
-// javaNameSanitizer replaces all dashes and dots with underscores, as well as
-// avoiding all keywords and reserved words by suffixing such identifier with
-// an underscore.
-type javaNameSanitizer struct {
-	symbols  *strings.Replacer
-	reserved map[string]bool
-}
-
-func newJavaNameSanitizer() *javaNameSanitizer {
-	reserved := make(map[string]bool)
-	for _, word := range javaKeywordsAndReserves {
-		reserved[word] = true
-	}
-	return &javaNameSanitizer{
-		symbols: strings.NewReplacer(
-			"-", "_",
-			".", "_",
-		),
-		reserved: reserved,
-	}
-}
-
-func (r *javaNameSanitizer) Replace(s string) string {
-	if r.reserved[s] {
-		return s + "_"
-	}
-	return r.symbols.Replace(s)
-}
-
-var javaNameReplacer = newJavaNameSanitizer()
+	"try", "void", "volatile", "while", "false", "null", "true"})
 
 func (g *JavaGen) javaPkgName(pkg *types.Package) string {
 	if pkg == nil {
 		return "go"
 	}
-	s := javaNameReplacer.Replace(pkg.Name())
+	s := javaNameReplacer(pkg.Name())
 	if g.JavaPkg != "" {
 		return g.JavaPkg + "." + s
 	} else {
@@ -624,7 +593,7 @@
 	if pkg == nil {
 		return "Universe"
 	}
-	return javaNameReplacer.Replace(strings.Title(pkg.Name()))
+	return javaNameReplacer(strings.Title(pkg.Name()))
 }
 
 func (g *JavaGen) genConst(o *types.Const) {
@@ -1048,7 +1017,7 @@
 				jniParams += g.jniSigType(params.At(i).Type())
 			}
 			g.Printf("mid_%s_%s = (*env)->GetMethodID(env, clazz, %q, \"(%s)%s\");\n",
-				iface.obj.Name(), m.Name(), javaNameReplacer.Replace(lowerFirst(m.Name())), jniParams, retSig)
+				iface.obj.Name(), m.Name(), javaNameReplacer(lowerFirst(m.Name())), jniParams, retSig)
 		}
 		g.Printf("\n")
 	}
diff --git a/bind/genobjc.go b/bind/genobjc.go
index f067c03..4a936bf 100644
--- a/bind/genobjc.go
+++ b/bind/genobjc.go
@@ -10,8 +10,6 @@
 	"go/types"
 	"math"
 	"strings"
-	"unicode"
-	"unicode/utf8"
 )
 
 // TODO(hyangah): handle method name conflicts.
@@ -162,7 +160,7 @@
 				continue
 			}
 			objcType := g.objcType(obj.Type())
-			g.Printf("+ (%s) %s;\n", objcType, lowerFirst(obj.Name()))
+			g.Printf("+ (%s) %s;\n", objcType, objcNameReplacer(lowerFirst(obj.Name())))
 			g.Printf("+ (void) set%s:(%s)v;\n", obj.Name(), objcType)
 			g.Printf("\n")
 		}
@@ -305,7 +303,7 @@
 	g.Printf("}\n\n")
 
 	// getter
-	g.Printf("+ (%s) %s {\n", objcType, lowerFirst(o.Name()))
+	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())
@@ -470,7 +468,7 @@
 			params = append(params, fmt.Sprintf("%s:(%s)%s", key, g.objcType(p.typ)+"*", p.name))
 		}
 	}
-	return fmt.Sprintf("(%s)%s%s", s.ret, lowerFirst(s.name), strings.Join(params, " "))
+	return fmt.Sprintf("(%s)%s%s", s.ret, objcNameReplacer(lowerFirst(s.name)), strings.Join(params, " "))
 }
 
 func (s *funcSummary) callMethod(g *objcGen) string {
@@ -491,7 +489,7 @@
 			params = append(params, fmt.Sprintf("%s:&%s", key, p.name))
 		}
 	}
-	return fmt.Sprintf("%s%s", lowerFirst(s.name), strings.Join(params, " "))
+	return fmt.Sprintf("%s%s", objcNameReplacer(lowerFirst(s.name)), strings.Join(params, " "))
 }
 
 func (s *funcSummary) returnsVal() bool {
@@ -522,7 +520,7 @@
 
 func (g *objcGen) genGetter(oName string, f *types.Var) {
 	t := f.Type()
-	g.Printf("- (%s)%s {\n", g.objcType(t), lowerFirst(f.Name()))
+	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()))
@@ -885,7 +883,7 @@
 			continue
 		}
 		name, typ := f.Name(), g.objcFieldType(f.Type())
-		g.Printf("- (%s)%s;\n", typ, lowerFirst(name))
+		g.Printf("- (%s)%s;\n", typ, objcNameReplacer(lowerFirst(name)))
 		g.Printf("- (void)set%s:(%s)v;\n", name, typ)
 	}
 
@@ -896,7 +894,7 @@
 			continue
 		}
 		s := g.funcSummary(m)
-		g.Printf("- %s;\n", lowerFirst(s.asMethod(g)))
+		g.Printf("- %s;\n", objcNameReplacer(lowerFirst(s.asMethod(g))))
 	}
 	g.Printf("@end\n")
 }
@@ -1050,25 +1048,10 @@
 	}
 }
 
-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)
-}
+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.
diff --git a/bind/testdata/keywords.objc.h.golden b/bind/testdata/keywords.objc.h.golden
index 1672b7e..df4c914 100644
--- a/bind/testdata/keywords.objc.h.golden
+++ b/bind/testdata/keywords.objc.h.golden
@@ -20,29 +20,29 @@
 - (void)byte;
 - (void)case;
 - (void)catch;
-- (void)char;
+- (void)char_;
 - (void)class;
-- (void)const;
+- (void)const_;
 - (void)continue;
 - (void)default;
 - (void)do;
-- (void)double;
+- (void)double_;
 - (void)else;
 - (void)enum;
 - (void)extends;
 - (void)false;
 - (void)final;
 - (void)finally;
-- (void)float;
+- (void)float_;
 - (void)for;
 - (void)goto;
 - (void)if;
 - (void)implements;
 - (void)import;
 - (void)instanceof;
-- (void)int;
+- (void)int_;
 - (void)interface;
-- (void)long;
+- (void)long_;
 - (void)native;
 - (void)new;
 - (void)null;
@@ -51,10 +51,10 @@
 - (void)protected;
 - (void)public;
 - (void)return;
-- (void)short;
+- (void)short_;
 - (void)static;
 - (void)strictfp;
-- (void)super;
+- (void)super_;
 - (void)switch;
 - (void)synchronized;
 - (void)this;
@@ -63,8 +63,8 @@
 - (void)transient;
 - (void)true;
 - (void)try;
-- (void)void;
-- (void)volatile;
+- (void)void_;
+- (void)volatile_;
 - (void)while;
 @end
 
@@ -82,29 +82,29 @@
 - (void)byte;
 - (void)case;
 - (void)catch;
-- (void)char;
+- (void)char_;
 - (void)class;
-- (void)const;
+- (void)const_;
 - (void)continue;
 - (void)default;
 - (void)do;
-- (void)double;
+- (void)double_;
 - (void)else;
 - (void)enum;
 - (void)extends;
 - (void)false;
 - (void)final;
 - (void)finally;
-- (void)float;
+- (void)float_;
 - (void)for;
 - (void)goto;
 - (void)if;
 - (void)implements;
 - (void)import;
 - (void)instanceof;
-- (void)int;
+- (void)int_;
 - (void)interface;
-- (void)long;
+- (void)long_;
 - (void)native;
 - (void)new;
 - (void)null;
@@ -113,10 +113,10 @@
 - (void)protected;
 - (void)public;
 - (void)return;
-- (void)short;
+- (void)short_;
 - (void)static;
 - (void)strictfp;
-- (void)super;
+- (void)super_;
 - (void)switch;
 - (void)synchronized;
 - (void)this;
@@ -125,8 +125,8 @@
 - (void)transient;
 - (void)true;
 - (void)try;
-- (void)void;
-- (void)volatile;
+- (void)void_;
+- (void)volatile_;
 - (void)while;
 @end
 
diff --git a/bind/testdata/keywords.objc.m.golden b/bind/testdata/keywords.objc.m.golden
index c115f8c..d4f9d73 100644
--- a/bind/testdata/keywords.objc.m.golden
+++ b/bind/testdata/keywords.objc.m.golden
@@ -54,7 +54,7 @@
 	proxykeywords_KeywordCaller_Catch(refnum);
 }
 
-- (void)char {
+- (void)char_ {
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
 	proxykeywords_KeywordCaller_Char(refnum);
 }
@@ -64,7 +64,7 @@
 	proxykeywords_KeywordCaller_Class(refnum);
 }
 
-- (void)const {
+- (void)const_ {
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
 	proxykeywords_KeywordCaller_Const(refnum);
 }
@@ -84,7 +84,7 @@
 	proxykeywords_KeywordCaller_Do(refnum);
 }
 
-- (void)double {
+- (void)double_ {
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
 	proxykeywords_KeywordCaller_Double(refnum);
 }
@@ -119,7 +119,7 @@
 	proxykeywords_KeywordCaller_Finally(refnum);
 }
 
-- (void)float {
+- (void)float_ {
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
 	proxykeywords_KeywordCaller_Float(refnum);
 }
@@ -154,7 +154,7 @@
 	proxykeywords_KeywordCaller_Instanceof(refnum);
 }
 
-- (void)int {
+- (void)int_ {
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
 	proxykeywords_KeywordCaller_Int(refnum);
 }
@@ -164,7 +164,7 @@
 	proxykeywords_KeywordCaller_Interface(refnum);
 }
 
-- (void)long {
+- (void)long_ {
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
 	proxykeywords_KeywordCaller_Long(refnum);
 }
@@ -209,7 +209,7 @@
 	proxykeywords_KeywordCaller_Return(refnum);
 }
 
-- (void)short {
+- (void)short_ {
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
 	proxykeywords_KeywordCaller_Short(refnum);
 }
@@ -224,7 +224,7 @@
 	proxykeywords_KeywordCaller_Strictfp(refnum);
 }
 
-- (void)super {
+- (void)super_ {
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
 	proxykeywords_KeywordCaller_Super(refnum);
 }
@@ -269,12 +269,12 @@
 	proxykeywords_KeywordCaller_Try(refnum);
 }
 
-- (void)void {
+- (void)void_ {
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
 	proxykeywords_KeywordCaller_Void(refnum);
 }
 
-- (void)volatile {
+- (void)volatile_ {
 	int32_t refnum = go_seq_go_to_refnum(self._ref);
 	proxykeywords_KeywordCaller_Volatile(refnum);
 }
@@ -340,7 +340,7 @@
 void cproxykeywords_KeywordCaller_Char(int32_t refnum) {
 	@autoreleasepool {
 		GoKeywordsKeywordCaller* o = go_seq_objc_from_refnum(refnum);
-		[o char];
+		[o char_];
 	}
 }
 
@@ -354,7 +354,7 @@
 void cproxykeywords_KeywordCaller_Const(int32_t refnum) {
 	@autoreleasepool {
 		GoKeywordsKeywordCaller* o = go_seq_objc_from_refnum(refnum);
-		[o const];
+		[o const_];
 	}
 }
 
@@ -382,7 +382,7 @@
 void cproxykeywords_KeywordCaller_Double(int32_t refnum) {
 	@autoreleasepool {
 		GoKeywordsKeywordCaller* o = go_seq_objc_from_refnum(refnum);
-		[o double];
+		[o double_];
 	}
 }
 
@@ -431,7 +431,7 @@
 void cproxykeywords_KeywordCaller_Float(int32_t refnum) {
 	@autoreleasepool {
 		GoKeywordsKeywordCaller* o = go_seq_objc_from_refnum(refnum);
-		[o float];
+		[o float_];
 	}
 }
 
@@ -480,7 +480,7 @@
 void cproxykeywords_KeywordCaller_Int(int32_t refnum) {
 	@autoreleasepool {
 		GoKeywordsKeywordCaller* o = go_seq_objc_from_refnum(refnum);
-		[o int];
+		[o int_];
 	}
 }
 
@@ -494,7 +494,7 @@
 void cproxykeywords_KeywordCaller_Long(int32_t refnum) {
 	@autoreleasepool {
 		GoKeywordsKeywordCaller* o = go_seq_objc_from_refnum(refnum);
-		[o long];
+		[o long_];
 	}
 }
 
@@ -557,7 +557,7 @@
 void cproxykeywords_KeywordCaller_Short(int32_t refnum) {
 	@autoreleasepool {
 		GoKeywordsKeywordCaller* o = go_seq_objc_from_refnum(refnum);
-		[o short];
+		[o short_];
 	}
 }
 
@@ -578,7 +578,7 @@
 void cproxykeywords_KeywordCaller_Super(int32_t refnum) {
 	@autoreleasepool {
 		GoKeywordsKeywordCaller* o = go_seq_objc_from_refnum(refnum);
-		[o super];
+		[o super_];
 	}
 }
 
@@ -641,14 +641,14 @@
 void cproxykeywords_KeywordCaller_Void(int32_t refnum) {
 	@autoreleasepool {
 		GoKeywordsKeywordCaller* o = go_seq_objc_from_refnum(refnum);
-		[o void];
+		[o void_];
 	}
 }
 
 void cproxykeywords_KeywordCaller_Volatile(int32_t refnum) {
 	@autoreleasepool {
 		GoKeywordsKeywordCaller* o = go_seq_objc_from_refnum(refnum);
-		[o volatile];
+		[o volatile_];
 	}
 }