bind: add const type support

Update golang/go#12475

Change-Id: I7fdc22462b5925c84ebbeb54517032c2fbd0545b
Reviewed-on: https://go-review.googlesource.com/15120
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/bind/gengo.go b/bind/gengo.go
index 93fdd51..258621d 100644
--- a/bind/gengo.go
+++ b/bind/gengo.go
@@ -385,7 +385,6 @@
 		}
 
 		switch obj := obj.(type) {
-		// TODO(crawshaw): case *types.Const:
 		// TODO(crawshaw): case *types.Var:
 		case *types.Func:
 			// TODO(crawshaw): functions that are not implementable from
@@ -402,7 +401,7 @@
 			case *types.Interface:
 				g.genInterface(obj)
 			}
-
+		case *types.Const:
 		default:
 			g.errorf("not yet supported, name for %v / %T", obj, obj)
 			continue
diff --git a/bind/genjava.go b/bind/genjava.go
index ee09ed0..d4bd18e 100644
--- a/bind/genjava.go
+++ b/bind/genjava.go
@@ -7,9 +7,11 @@
 import (
 	"bytes"
 	"fmt"
+	"go/constant"
 	"go/token"
 	"go/types"
 	"io"
+	"math"
 	"regexp"
 	"strings"
 )
@@ -315,7 +317,7 @@
 	switch T := T.(type) {
 	case *types.Basic:
 		switch T.Kind() {
-		case types.Bool:
+		case types.Bool, types.UntypedBool:
 			return "boolean"
 		case types.Int:
 			return "long"
@@ -323,23 +325,23 @@
 			return "byte"
 		case types.Int16:
 			return "short"
-		case types.Int32:
+		case types.Int32, types.UntypedRune: // types.Rune
 			return "int"
-		case types.Int64:
+		case types.Int64, types.UntypedInt:
 			return "long"
-		case types.Uint8:
+		case types.Uint8: // types.Byte
 			// TODO(crawshaw): Java bytes are signed, so this is
 			// questionable, but vital.
 			return "byte"
 		// TODO(crawshaw): case types.Uint, types.Uint16, types.Uint32, types.Uint64:
 		case types.Float32:
 			return "float"
-		case types.Float64:
+		case types.Float64, types.UntypedFloat:
 			return "double"
-		case types.String:
+		case types.String, types.UntypedString:
 			return "String"
 		default:
-			g.errorf("unsupported return type: %s", T)
+			g.errorf("unsupported basic type: %s", T)
 			return "TODO"
 		}
 	case *types.Slice:
@@ -597,6 +599,35 @@
 	return strings.Title(javaNameReplacer.Replace(g.pkg.Name()))
 }
 
+func (g *javaGen) genConst(o *types.Const) {
+	// TODO(hyangah): should const names use upper cases + "_"?
+	// TODO(hyangah): check invalid names.
+	jType := g.javaType(o.Type())
+	val := o.Val().String()
+	switch b := o.Type().(*types.Basic); b.Kind() {
+	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", val, o.Name(), jType)
+			return
+		}
+		val = fmt.Sprintf("%dL", i)
+
+	case types.Float32:
+		f, _ := constant.Float32Val(o.Val())
+		val = fmt.Sprintf("%gf", f)
+
+	case 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 %s", val, o.Name(), jType)
+			return
+		}
+		val = fmt.Sprintf("%g", f)
+	}
+	g.Printf("public static final %s %s = %s;\n", g.javaType(o.Type()), o.Name(), val)
+}
+
 func (g *javaGen) gen() error {
 	g.Printf(javaPreamble, g.javaPkg, g.className(), g.gobindOpts(), g.pkg.Path())
 
@@ -613,7 +644,6 @@
 		}
 
 		switch o := obj.(type) {
-		// TODO(crawshaw): case *types.Const:
 		// TODO(crawshaw): case *types.Var:
 		case *types.Func:
 			if isCallable(o) {
@@ -631,8 +661,10 @@
 				g.errorf("%s: cannot generate binding for %s: %T", g.fset.Position(o.Pos()), o.Name(), t)
 				continue
 			}
+		case *types.Const:
+			g.genConst(o)
 		default:
-			g.errorf("unsupported exported type: ", obj)
+			g.errorf("unsupported exported type: %T", obj)
 		}
 	}
 
diff --git a/bind/genobjc.go b/bind/genobjc.go
index fcc7350..181b17a 100644
--- a/bind/genobjc.go
+++ b/bind/genobjc.go
@@ -6,8 +6,10 @@
 
 import (
 	"fmt"
+	"go/constant"
 	"go/token"
 	"go/types"
+	"math"
 	"strings"
 )
 
@@ -26,6 +28,7 @@
 	namePrefix string
 	funcs      []*types.Func
 	names      []*types.TypeName
+	constants  []*types.Const
 }
 
 func (g *objcGen) init() {
@@ -47,7 +50,15 @@
 			}
 		case *types.TypeName:
 			g.names = append(g.names, obj)
-			// TODO(hyangah): *types.Const, *types.Var
+		case *types.Const:
+			if _, ok := obj.Type().(*types.Basic); !ok {
+				g.errorf("unsupported exported const for %s: %T", obj.Name(), obj)
+				continue
+			}
+			g.constants = append(g.constants, obj)
+		default:
+			g.errorf("unsupported exported type for %s: %T", obj.Name(), obj)
+			// TODO(hyangah): *types.Var
 		}
 	}
 }
@@ -95,6 +106,19 @@
 		}
 	}
 
+	// const
+	for _, obj := range g.constants {
+		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")
+	}
+
 	// static functions.
 	for _, obj := range g.funcs {
 		g.genFuncH(obj)
@@ -155,6 +179,13 @@
 		g.Printf("\n")
 	}
 
+	// const
+	for _, o := range g.constants {
+		g.genConstM(o)
+	}
+	if len(g.constants) > 0 {
+		g.Printf("\n")
+	}
 	// global functions.
 	for _, obj := range g.funcs {
 		g.genFuncM(obj)
@@ -179,6 +210,51 @@
 	return nil
 }
 
+func (g *objcGen) genConstM(o *types.Const) {
+	cName := fmt.Sprintf("%s%s", g.namePrefix, o.Name())
+	cType := 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, o.Val())
+
+	case types.Int, types.Int8, types.Int16, types.Int32:
+		g.Printf("const %s %s = %s;\n", cType, 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(), cType)
+			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", cType, cName, f)
+
+	default:
+		g.errorf("unsupported const type %s for %s", b, o.Name())
+	}
+}
+
 type funcSummary struct {
 	name              string
 	ret               string
@@ -754,7 +830,7 @@
 	switch typ := typ.(type) {
 	case *types.Basic:
 		switch typ.Kind() {
-		case types.Bool:
+		case types.Bool, types.UntypedBool:
 			return "BOOL"
 		case types.Int:
 			return "int"
@@ -762,9 +838,9 @@
 			return "int8_t"
 		case types.Int16:
 			return "int16_t"
-		case types.Int32:
+		case types.Int32, types.UntypedRune: // types.Rune
 			return "int32_t"
-		case types.Int64:
+		case types.Int64, types.UntypedInt:
 			return "int64_t"
 		case types.Uint8:
 			// byte is an alias of uint8, and the alias is lost.
@@ -777,9 +853,9 @@
 			return "uint64_t"
 		case types.Float32:
 			return "float"
-		case types.Float64:
+		case types.Float64, types.UntypedFloat:
 			return "double"
-		case types.String:
+		case types.String, types.UntypedString:
 			return "NSString*"
 		default:
 			g.errorf("unsupported type: %s", typ)
diff --git a/bind/java/SeqTest.java b/bind/java/SeqTest.java
index 81cb866..2f0927f 100644
--- a/bind/java/SeqTest.java
+++ b/bind/java/SeqTest.java
@@ -17,6 +17,22 @@
   public SeqTest() {
   }
 
+  public void testConst() {
+    assertEquals("const String", "a string", Testpkg.AString);
+    assertEquals("const Int", 7, Testpkg.AnInt);
+    assertEquals("const Bool", true, Testpkg.ABool);
+    assertEquals("const Float", 0.12345, Testpkg.AFloat, 0.0001);
+
+    assertEquals("const MinInt32", -1<<31, Testpkg.MinInt32);
+    assertEquals("const MaxInt32", (1<<31) - 1, Testpkg.MaxInt32);
+    assertEquals("const MinInt64", -1L<<63, Testpkg.MinInt64);
+    assertEquals("const MaxInt64", (1L<<63) - 1, Testpkg.MaxInt64);
+    assertEquals("const SmallestNonzeroFloat64", 4.940656458412465441765687928682213723651e-324, Testpkg.SmallestNonzeroFloat64, 1e-323);
+    assertEquals("const MaxFloat64", 1.797693134862315708145274237317043567981e+308, Testpkg.MaxFloat64, 0.0001);
+    assertEquals("const SmallestNonzeroFloat32", 1.401298464324817070923729583289916131280e-45, Testpkg.SmallestNonzeroFloat32, 1e-44);
+    assertEquals("const MaxFloat32", 3.40282346638528859811704183484516925440e+38, Testpkg.MaxFloat32, 0.0001);
+    assertEquals("const Log2E", 1/0.693147180559945309417232121458176568075500134360255254120680009, Testpkg.Log2E, 0.0001);
+  }
   public void testAssets() {
     String want = "Hello, Assets.\n";
     String got = Testpkg.ReadAsset();
diff --git a/bind/java/testpkg/testpkg.go b/bind/java/testpkg/testpkg.go
index 474bacc..1b3f498 100644
--- a/bind/java/testpkg/testpkg.go
+++ b/bind/java/testpkg/testpkg.go
@@ -15,12 +15,30 @@
 	"fmt"
 	"io/ioutil"
 	"log"
+	"math"
 	"runtime"
 	"time"
 
 	"golang.org/x/mobile/asset"
 )
 
+const (
+	AString = "a string"
+	AnInt   = 7
+	ABool   = true
+	AFloat  = 0.12345
+
+	MinInt32               int32   = math.MinInt32
+	MaxInt32               int32   = math.MaxInt32
+	MinInt64                       = math.MinInt64
+	MaxInt64                       = math.MaxInt64
+	SmallestNonzeroFloat64         = math.SmallestNonzeroFloat64
+	MaxFloat64                     = math.MaxFloat64
+	SmallestNonzeroFloat32 float32 = math.SmallestNonzeroFloat64
+	MaxFloat32             float32 = math.MaxFloat32
+	Log2E                          = math.Log2E
+)
+
 type I interface {
 	F()
 
diff --git a/bind/objc/SeqTest.m b/bind/objc/SeqTest.m
index 2607a67..5d1cbf8 100644
--- a/bind/objc/SeqTest.m
+++ b/bind/objc/SeqTest.m
@@ -15,6 +15,63 @@
 
 static int err = 0;
 
+void testConst() {
+  if (![GoTestpkgAString isEqualToString:@"a string"]) {
+    ERROR(@"GoTestpkgAString = %@, want 'a string'", GoTestpkgAString);
+  }
+  if (GoTestpkgAnInt != 7) {
+    ERROR(@"GoTestpkgAnInt = %lld, want 7", GoTestpkgAnInt);
+  }
+  if (ABS(GoTestpkgAFloat - 0.12345) > 0.0001) {
+    ERROR(@"GoTestpkgAFloat = %f, want 0.12345", GoTestpkgAFloat);
+  }
+  if (GoTestpkgABool != YES) {
+    ERROR(@"GoTestpkgABool = %@, want YES", GoTestpkgAFloat ? @"YES" : @"NO");
+  }
+
+  if (GoTestpkgMinInt32 != INT32_MIN) {
+    ERROR(@"GoTestpkgMinInt32 = %d, want %d", GoTestpkgMinInt32, INT32_MIN);
+  }
+  if (GoTestpkgMaxInt32 != INT32_MAX) {
+    ERROR(@"GoTestpkgMaxInt32 = %d, want %d", GoTestpkgMaxInt32, INT32_MAX);
+  }
+  if (GoTestpkgMinInt64 != INT64_MIN) {
+    ERROR(@"GoTestpkgMinInt64 = %lld, want %lld", GoTestpkgMinInt64, INT64_MIN);
+  }
+  if (GoTestpkgMaxInt64 != INT64_MAX) {
+    ERROR(@"GoTestpkgMaxInt64 = %lld, want %lld", GoTestpkgMaxInt64, INT64_MAX);
+  }
+  if (ABS(GoTestpkgSmallestNonzeroFloat64 -
+          4.940656458412465441765687928682213723651e-324) > 1e-323) {
+    ERROR(@"GoTestpkgSmallestNonzeroFloat64 = %f, want %f",
+          GoTestpkgSmallestNonzeroFloat64,
+          4.940656458412465441765687928682213723651e-324);
+  }
+  if (ABS(GoTestpkgMaxFloat64 -
+          1.797693134862315708145274237317043567981e+308) > 0.0001) {
+    ERROR(@"GoTestpkgMaxFloat64 = %f, want %f", GoTestpkgMaxFloat64,
+          1.797693134862315708145274237317043567981e+308);
+  }
+  if (ABS(GoTestpkgSmallestNonzeroFloat32 -
+          1.401298464324817070923729583289916131280e-45) > 1e-44) {
+    ERROR(@"GoTestpkgSmallestNonzeroFloat32 = %f, want %f",
+          GoTestpkgSmallestNonzeroFloat32,
+          1.401298464324817070923729583289916131280e-45);
+  }
+  if (ABS(GoTestpkgMaxFloat32 - 3.40282346638528859811704183484516925440e+38) >
+      0.0001) {
+    ERROR(@"GoTestpkgMaxFloat32 = %f, want %f", GoTestpkgMaxFloat32,
+          3.40282346638528859811704183484516925440e+38);
+  }
+  if (ABS(GoTestpkgLog2E -
+          1 / 0.693147180559945309417232121458176568075500134360255254120680009) >
+      0.0001) {
+    ERROR(
+        @"GoTestpkgLog2E = %f, want %f", GoTestpkgLog2E,
+        1 / 0.693147180559945309417232121458176568075500134360255254120680009);
+  }
+}
+
 void testHello(NSString *input) {
   NSString *got = GoTestpkgHello(input);
   NSString *want = [NSString stringWithFormat:@"Hello, %@!", input];
@@ -229,6 +286,8 @@
       ERROR(@"GoTestpkgSum(31, 21) = %lld, want 52\n", sum);
     }
 
+    testConst();
+
     testHello(@"세계"); // korean, utf-8, world.
 
     unichar t[] = {
diff --git a/bind/objc/testpkg/objc_testpkg/GoTestpkg.h b/bind/objc/testpkg/objc_testpkg/GoTestpkg.h
index 224a18d..8c33bc7 100644
--- a/bind/objc/testpkg/objc_testpkg/GoTestpkg.h
+++ b/bind/objc/testpkg/objc_testpkg/GoTestpkg.h
@@ -42,6 +42,20 @@
 - (NSString*)TryTwoStrings:(NSString*)first second:(NSString*)second;
 @end
 
+FOUNDATION_EXPORT const BOOL GoTestpkgABool;
+FOUNDATION_EXPORT const double GoTestpkgAFloat;
+FOUNDATION_EXPORT NSString* const GoTestpkgAString;
+FOUNDATION_EXPORT const int64_t GoTestpkgAnInt;
+FOUNDATION_EXPORT const double GoTestpkgLog2E;
+FOUNDATION_EXPORT const float GoTestpkgMaxFloat32;
+FOUNDATION_EXPORT const double GoTestpkgMaxFloat64;
+FOUNDATION_EXPORT const int32_t GoTestpkgMaxInt32;
+FOUNDATION_EXPORT const int64_t GoTestpkgMaxInt64;
+FOUNDATION_EXPORT const int32_t GoTestpkgMinInt32;
+FOUNDATION_EXPORT const int64_t GoTestpkgMinInt64;
+FOUNDATION_EXPORT const float GoTestpkgSmallestNonzeroFloat32;
+FOUNDATION_EXPORT const double GoTestpkgSmallestNonzeroFloat64;
+
 FOUNDATION_EXPORT NSData* GoTestpkgBytesAppend(NSData* a, NSData* b);
 
 FOUNDATION_EXPORT BOOL GoTestpkgCallIError(id<GoTestpkgI> i, BOOL triggerError, NSError** error);
diff --git a/bind/objc/testpkg/objc_testpkg/GoTestpkg.m b/bind/objc/testpkg/objc_testpkg/GoTestpkg.m
index 345ef21..4ef2d3c 100644
--- a/bind/objc/testpkg/objc_testpkg/GoTestpkg.m
+++ b/bind/objc/testpkg/objc_testpkg/GoTestpkg.m
@@ -298,6 +298,20 @@
 
 @end
 
+const BOOL GoTestpkgABool = YES;
+const double GoTestpkgAFloat = 0.12345;
+NSString* const GoTestpkgAString = @"a string";
+const int64_t GoTestpkgAnInt = 7LL;
+const double GoTestpkgLog2E = 1.4426950408889634;
+const float GoTestpkgMaxFloat32 = 3.4028234663852886e+38;
+const double GoTestpkgMaxFloat64 = 1.7976931348623157e+308;
+const int32_t GoTestpkgMaxInt32 = 2147483647;
+const int64_t GoTestpkgMaxInt64 = 9223372036854775807LL;
+const int32_t GoTestpkgMinInt32 = -2147483648;
+const int64_t GoTestpkgMinInt64 = -9223372036854775807LL-1;
+const float GoTestpkgSmallestNonzeroFloat32 = 0;
+const double GoTestpkgSmallestNonzeroFloat64 = 5e-324;
+
 NSData* GoTestpkgBytesAppend(NSData* a, NSData* b) {
 	GoSeq in_ = {};
 	GoSeq out_ = {};
diff --git a/bind/objc/testpkg/testpkg.go b/bind/objc/testpkg/testpkg.go
index d5c5218..fb6150c 100644
--- a/bind/objc/testpkg/testpkg.go
+++ b/bind/objc/testpkg/testpkg.go
@@ -10,10 +10,28 @@
 import (
 	"errors"
 	"fmt"
+	"math"
 	"runtime"
 	"time"
 )
 
+const (
+	AString = "a string"
+	AnInt   = 7
+	ABool   = true
+	AFloat  = 0.12345
+
+	MinInt32               int32   = math.MinInt32
+	MaxInt32               int32   = math.MaxInt32
+	MinInt64                       = math.MinInt64
+	MaxInt64                       = math.MaxInt64
+	SmallestNonzeroFloat64         = math.SmallestNonzeroFloat64
+	MaxFloat64                     = math.MaxFloat64
+	SmallestNonzeroFloat32 float32 = math.SmallestNonzeroFloat64
+	MaxFloat32             float32 = math.MaxFloat32
+	Log2E                          = math.Log2E
+)
+
 type I interface {
 	Times(v int32) int64
 	Error(triggerError bool) error
diff --git a/bind/testdata/basictypes.go b/bind/testdata/basictypes.go
index 579fbf7..6d9577b 100644
--- a/bind/testdata/basictypes.go
+++ b/bind/testdata/basictypes.go
@@ -4,6 +4,15 @@
 
 package basictypes
 
+const (
+	AString = "a string"
+	AnInt   = 7
+	AnInt2  = 1<<63 - 1
+	AFloat  = 0.2015
+	ARune   = rune(32)
+	ABool   = true
+)
+
 func Ints(x int8, y int16, z int32, t int64, u int) {}
 
 func Error() error { return nil }
diff --git a/bind/testdata/basictypes.java.golden b/bind/testdata/basictypes.java.golden
index f761a60..98aed25 100644
--- a/bind/testdata/basictypes.java.golden
+++ b/bind/testdata/basictypes.java.golden
@@ -9,6 +9,12 @@
 public abstract class Basictypes {
     private Basictypes() {} // uninstantiable
     
+    public static final boolean ABool = true;
+    public static final double AFloat = 0.2015;
+    public static final int ARune = 32;
+    public static final String AString = "a string";
+    public static final long AnInt = 7L;
+    public static final long AnInt2 = 9223372036854775807L;
     public static boolean Bool(boolean p0) {
         go.Seq _in = new go.Seq();
         go.Seq _out = new go.Seq();
diff --git a/bind/testdata/basictypes.objc.h.golden b/bind/testdata/basictypes.objc.h.golden
index 468647d..966b037 100644
--- a/bind/testdata/basictypes.objc.h.golden
+++ b/bind/testdata/basictypes.objc.h.golden
@@ -8,6 +8,13 @@
 
 #include <Foundation/Foundation.h>
 
+FOUNDATION_EXPORT const BOOL GoBasictypesABool;
+FOUNDATION_EXPORT const double GoBasictypesAFloat;
+FOUNDATION_EXPORT const int32_t GoBasictypesARune;
+FOUNDATION_EXPORT NSString* const GoBasictypesAString;
+FOUNDATION_EXPORT const int64_t GoBasictypesAnInt;
+FOUNDATION_EXPORT const int64_t GoBasictypesAnInt2;
+
 FOUNDATION_EXPORT BOOL GoBasictypesBool(BOOL p0);
 
 FOUNDATION_EXPORT NSData* GoBasictypesByteArrays(NSData* x);
diff --git a/bind/testdata/basictypes.objc.m.golden b/bind/testdata/basictypes.objc.m.golden
index dbec0bc..331a39a 100644
--- a/bind/testdata/basictypes.objc.m.golden
+++ b/bind/testdata/basictypes.objc.m.golden
@@ -21,6 +21,13 @@
 #define _CALL_ErrorPair_ 4
 #define _CALL_Ints_ 5
 
+const BOOL GoBasictypesABool = YES;
+const double GoBasictypesAFloat = 0.2015;
+const int32_t GoBasictypesARune = 32;
+NSString* const GoBasictypesAString = @"a string";
+const int64_t GoBasictypesAnInt = 7LL;
+const int64_t GoBasictypesAnInt2 = 9223372036854775807LL;
+
 BOOL GoBasictypesBool(BOOL p0) {
 	GoSeq in_ = {};
 	GoSeq out_ = {};