Enforce JsOverlay for compile time constants.

Also stops enforcing visibility rules for native type members.
Existing implementation was already broken and putting visibility
constraints doesn't seem like adding any value.

Change-Id: Ib21c47ad54deef5ff144b56ce038c13f6e14d049
diff --git a/dev/core/src/com/google/gwt/dev/javac/JsInteropUtil.java b/dev/core/src/com/google/gwt/dev/javac/JsInteropUtil.java
index b3ba633..fddb14e 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JsInteropUtil.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JsInteropUtil.java
@@ -89,10 +89,6 @@
   }
 
   public static void maybeSetJsInteropPropertiesNew(JMethod method, Annotation... annotations) {
-    if (getInteropAnnotation(annotations, "JsOverlay") != null) {
-      method.setJsOverlay();
-    }
-
     AnnotationBinding annotation = getInteropAnnotation(annotations, "JsMethod");
     if (annotation == null) {
       annotation = getInteropAnnotation(annotations, "JsConstructor");
@@ -106,6 +102,9 @@
   }
 
   public static void maybeSetJsInteropProperties(JField field, Annotation... annotations) {
+    if (field.getEnclosingType().isJsNative() && field.isCompileTimeConstant()) {
+      field.setJsOverlay();
+    }
     setJsInteropProperties(field, annotations, false);
   }
 
@@ -128,7 +127,7 @@
     /* Apply class wide JsInterop annotations */
 
     boolean ignore = JdtUtil.getAnnotation(annotations, JSNOEXPORT_CLASS) != null;
-    if (ignore || (!member.isPublic() && !isNativeConstructor(member)) || hasExport) {
+    if (ignore || (!member.isPublic() && !member.getEnclosingType().isJsNative())) {
       return;
     }
 
@@ -143,18 +142,19 @@
     }
   }
 
-  private static boolean isNativeConstructor(JMember member) {
-    return member instanceof JConstructor && member.getEnclosingType().isJsNative();
-  }
-
   private static void setJsInteropPropertiesNew(JMember member, Annotation[] annotations,
       AnnotationBinding memberAnnotation, boolean isAccessor) {
+    if (getInteropAnnotation(annotations, "JsOverlay") != null) {
+      member.setJsOverlay();
+    }
+
     if (getInteropAnnotation(annotations, "JsIgnore") != null) {
       return;
     }
 
     boolean isPublicMemberForJsType = member.getEnclosingType().isJsType() && member.isPublic();
-    if (!isPublicMemberForJsType && !isNativeConstructor(member) && memberAnnotation == null) {
+    boolean memberForNativeType = member.getEnclosingType().isJsNative();
+    if (!isPublicMemberForJsType && !memberForNativeType && memberAnnotation == null) {
       return;
     }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsInfo.java b/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsInfo.java
index 75f6b6a..4d66e8a 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsInfo.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsInfo.java
@@ -118,6 +118,8 @@
 
   void setJsMemberInfo(JsMemberType type, String namespace, String name, boolean exported);
 
+  void setJsOverlay();
+
   JsMemberType getJsMemberType();
 
   boolean isJsNative();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JConstructor.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JConstructor.java
index 97f6564..b207a63 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JConstructor.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JConstructor.java
@@ -93,11 +93,6 @@
     return true;
   }
 
-  @Override
-  public boolean isJsNative() {
-    return getEnclosingType().isJsNative() && getJsName() != null;
-  }
-
   /**
    * Returns <code>true</code> if this constructor does no real work.
    *
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JField.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JField.java
index 27edfb0..2be07c4 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JField.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JField.java
@@ -84,6 +84,7 @@
   private String jsName;
   private String jsNamespace;
   private boolean exported;
+  private boolean isJsOverlay = false;
   private final JDeclaredType enclosingType;
   private final boolean isCompileTimeConstant;
   private final boolean isStatic;
@@ -141,6 +142,11 @@
   }
 
   @Override
+  public void setJsOverlay() {
+    isJsOverlay = true;
+  }
+
+  @Override
   public JsMemberType getJsMemberType() {
     return jsMembertype;
   }
@@ -173,12 +179,12 @@
 
   @Override
   public boolean isJsNative() {
-    return enclosingType.isJsNative() && jsName != null;
+    return !isJsOverlay() && enclosingType.isJsNative();
   }
 
   @Override
   public boolean isJsOverlay() {
-    return false;
+    return isJsOverlay;
   }
 
   @Override
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
index 98a574f..6d8cf25 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
@@ -48,10 +48,11 @@
     }
   };
 
-  private String jsName;
-  private boolean exported;
-  private String jsNamespace;
   private JsMemberType jsMemberType = JsMemberType.NONE;
+  private String jsName;
+  private String jsNamespace;
+  private boolean exported;
+  private boolean isJsOverlay = false;
   private Specialization specialization;
   private InliningMode inliningMode = InliningMode.NORMAL;
   private boolean preventDevirtualization = false;
@@ -59,7 +60,6 @@
   private boolean defaultMethod = false;
   private boolean syntheticAccidentalOverride = false;
   private Set<String> suppressedWarnings;
-  private boolean isJsOverlay = false;
 
   @Override
   public void setJsMemberInfo(JsMemberType type, String namespace, String name, boolean exported) {
@@ -194,7 +194,7 @@
 
   @Override
   public boolean isJsNative() {
-    return (isAbstract || body == null) && jsName != null;
+    return body == null || (!isJsOverlay() && getEnclosingType().isJsNative());
   }
 
   @Override
@@ -247,6 +247,7 @@
     this.defaultMethod = true;
   }
 
+  @Override
   public void setJsOverlay() {
     this.isJsOverlay = true;
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java
index d3c649a..f6bd3fd 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java
@@ -84,8 +84,6 @@
   private final JProgram jprogram;
   private final MinimalRebuildCache minimalRebuildCache;
 
-  // TODO review any use of word export
-
   private JsInteropRestrictionChecker(JProgram jprogram,
       MinimalRebuildCache minimalRebuildCache) {
     this.jprogram = jprogram;
@@ -93,29 +91,23 @@
   }
 
   /**
-   * Returns true if the constructor method is locally empty (allows calls to empty init and super
+   * Returns true if the constructor method is locally empty (allows calls to init and super
    * constructor).
    */
   private static boolean isConstructorEmpty(final JConstructor constructor) {
-    List<JStatement> statements = FluentIterable
-        .from(constructor.getBody().getStatements())
-        .filter(new Predicate<JStatement>() {
-          @Override
-          public boolean apply(JStatement statement) {
-            JClassType type = constructor.getEnclosingType();
-            if (isImplicitSuperCall(statement, type.getSuperClass())) {
-              return false;
-            }
-            if (isEmptyInitCall(statement, type)) {
-              return false;
-            }
-            if (statement instanceof JDeclarationStatement) {
-              return ((JDeclarationStatement) statement).getInitializer() != null;
-            }
-            return true;
-          }
-        }).toList();
-    return statements.isEmpty();
+    return Iterables.all(constructor.getBody().getStatements(), new Predicate<JStatement>() {
+      @Override
+      public boolean apply(JStatement statement) {
+        JClassType type = constructor.getEnclosingType();
+        if (isImplicitSuperCall(statement, type.getSuperClass())) {
+          return true;
+        }
+        if (isInitCall(statement, type)) {
+          return true;
+        }
+        return false;
+      }
+    });
   }
 
   private static JMethodCall isMethodCall(JStatement statement) {
@@ -127,12 +119,11 @@
     return expression instanceof JMethodCall ? (JMethodCall) expression : null;
   }
 
-  private static boolean isEmptyInitCall(JStatement statement, JDeclaredType type) {
+  private static boolean isInitCall(JStatement statement, JDeclaredType type) {
     JMethodCall methodCall = isMethodCall(statement);
 
     return methodCall != null
-        && methodCall.getTarget() == type.getInitMethod()
-        && ((JMethodBody) methodCall.getTarget().getBody()).getStatements().isEmpty();
+        && methodCall.getTarget() == type.getInitMethod();
   }
 
   private static boolean isImplicitSuperCall(JStatement statement, JDeclaredType superType) {
@@ -144,11 +135,16 @@
         && methodCall.getTarget().getEnclosingType() == superType;
   }
 
+  private static boolean isInitEmpty(JDeclaredType type) {
+    return type.getInitMethod() == null
+        || ((JMethodBody) type.getInitMethod().getBody()).getStatements().isEmpty();
+  }
+
   /**
    * Returns true if the clinit for a type is locally empty (except for the call to its super
    * clinit).
    */
-  private static boolean isClinitEmpty(JDeclaredType type) {
+  private static boolean isClinitEmpty(JDeclaredType type, final boolean skipDeclaration) {
     JMethod clinit = type.getClinitMethod();
     List<JStatement> statements = FluentIterable
         .from(((JMethodBody) clinit.getBody()).getStatements())
@@ -158,6 +154,9 @@
             if (!(statement instanceof JDeclarationStatement)) {
               return true;
             }
+            if (skipDeclaration) {
+              return false;
+            }
             JDeclarationStatement declarationStatement = (JDeclarationStatement) statement;
             JField field = (JField) declarationStatement.getVariableRef().getTarget();
             return !field.isCompileTimeConstant();
@@ -235,7 +234,7 @@
     }
 
     if (member.isJsOverlay()) {
-      checkJsOverlay((JMethod) member);
+      checkJsOverlay(member);
       return;
     }
 
@@ -265,26 +264,35 @@
     }
   }
 
-  private void checkJsOverlay(JMethod method) {
-    if (method.getEnclosingType().isJsoType()) {
+  private void checkJsOverlay(JMember member) {
+    if (member.getEnclosingType().isJsoType()) {
       return;
     }
 
-    String methodDescription = JjsUtils.getReadableDescription(method);
+    String methodDescription = JjsUtils.getReadableDescription(member);
 
-    if (!method.getEnclosingType().isJsNative()) {
-      logError(method,
-          "Method '%s' in non-native type cannot be @JsOverlay.", methodDescription);
+    if (!member.getEnclosingType().isJsNative()) {
+      logError(member, "JsOverlay '%s' can only be declared in a native type.", methodDescription);
     }
 
+    if (member instanceof JField) {
+      JField field = (JField) member;
+      if (!field.isCompileTimeConstant()) {
+        logError(
+            member, "JsOverlay field '%s' can only be a compile time constant.", methodDescription);
+      }
+      return;
+    }
+
+    JMethod method = (JMethod) member;
     if (!method.getOverriddenMethods().isEmpty()) {
-      logError(method,
+      logError(member,
           "JsOverlay method '%s' cannot override a supertype method.", methodDescription);
       return;
     }
 
-    if (method.isJsNative() || (!method.isFinal() && !method.isStatic())) {
-      logError(method,
+    if (method.getBody() == null || (!method.isFinal() && !method.isStatic())) {
+      logError(member,
           "JsOverlay method '%s' cannot be non-final nor native.", methodDescription);
     }
   }
@@ -296,16 +304,39 @@
       return;
     }
 
-    if (member.isSynthetic() || member.isJsNative() || member.isJsOverlay()) {
+    if (member.isSynthetic() || member.isJsOverlay()) {
       return;
     }
 
-    if (member.getJsName() == null) {
-      logError(member, "Native JsType member %s is not public or has @JsIgnore.",
-          getMemberDescription(member));
-    } else {
-      logError(member, "Native JsType method %s should be native or abstract.",
-          getMemberDescription(member));
+    JsMemberType jsMemberType = member.getJsMemberType();
+    switch (jsMemberType) {
+      case CONSTRUCTOR:
+        if (!isConstructorEmpty((JConstructor) member)) {
+          logError(member, "Native JsType constructor %s cannot have non-empty method body.",
+              getMemberDescription(member));
+        }
+        break;
+      case METHOD:
+      case GETTER:
+      case SETTER:
+      case UNDEFINED_ACCESSOR:
+        JMethod method = (JMethod) member;
+        if (!method.isAbstract() && method.getBody() != null) {
+          logError(member, "Native JsType method %s should be native or abstract.",
+              getMemberDescription(member));
+        }
+        break;
+      case PROPERTY:
+        JField field = (JField) member;
+        if (field.hasInitializer()) {
+          logError(member, "Native JsType field %s cannot have initializer.",
+              getMemberDescription(member));
+        }
+        break;
+      case NONE:
+        logError(member, "Native JsType member %s cannot have @JsIgnore.",
+            getMemberDescription(member));
+        break;
     }
   }
 
@@ -501,21 +532,19 @@
       }
     }
 
-    if (!isClinitEmpty(type)) {
+    if (!isInitEmpty(type)) {
+      logError("Native JsType '%s' cannot have initializer.", type);
+    }
+
+    if (!isClinitEmpty(type, true)) {
       logError("Native JsType '%s' cannot have static initializer.", type);
     }
 
-    for (JConstructor constructor : type.getConstructors()) {
-      if (!isConstructorEmpty(constructor)) {
-        logError(constructor, "Native JsType constructor %s cannot have non-empty method body.",
-            getMemberDescription(constructor));
-      }
-    }
     return true;
   }
 
   private void checkJsFunction(JDeclaredType type) {
-    if (!isClinitEmpty(type)) {
+    if (!isClinitEmpty(type, false)) {
       logError("JsFunction '%s' cannot have static initializer.", type);
     }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
index 77ee482..12b9b1c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
@@ -173,6 +173,9 @@
       newMethod.setHasSideEffects(x.hasSideEffects());
       newMethod.setSynthetic();
       newMethod.addThrownExceptions(x.getThrownExceptions());
+      if (x.isJsOverlay()) {
+        newMethod.setJsOverlay();
+      }
 
       JType thisParameterType = enclosingType.strengthenToNonNull();
       // Setup parameters; map from the old params to the new params
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java
index 6bed6e9..c047de3 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java
@@ -1123,12 +1123,30 @@
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) public static class Buggy {",
-        "  public static String s = \"hello\";",
-        "  static {  s += \"hello\"; }",
+        "  static {  int x = 1; }",
+        "}",
+        "@JsType(isNative=true) public static class Buggy2 {",
+        "  static {  Object.class.getName(); }",
         "}");
 
     assertBuggyFails(
-        "Line 4: Native JsType 'EntryPoint.Buggy' cannot have static initializer.");
+        "Line 4: Native JsType 'EntryPoint.Buggy' cannot have static initializer.",
+        "Line 7: Native JsType 'EntryPoint.Buggy2' cannot have static initializer.");
+  }
+
+  public void testNativeJsTypeInstanceInitializerFails() {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetClassDecl(
+        "@JsType(isNative=true) public static class Buggy {",
+        "  { Object.class.getName(); }",
+        "}",
+        "@JsType(isNative=true) public static class Buggy2 {",
+        "  { int x = 1; }",
+        "}");
+
+    assertBuggyFails(
+        "Line 4: Native JsType 'EntryPoint.Buggy' cannot have initializer.",
+        "Line 7: Native JsType 'EntryPoint.Buggy2' cannot have initializer.");
   }
 
   public void testNativeJsTypeNonEmptyConstructorFails() {
@@ -1145,20 +1163,6 @@
             + "non-empty method body.");
   }
 
-  public void testNativeJsTypeInstanceInitializerFails() {
-    addSnippetImport("jsinterop.annotations.JsType");
-    addSnippetClassDecl(
-        "@JsType(isNative=true) public static class Buggy {",
-        "  public int x = 1;",
-        "  public Buggy(int n) {",
-        "  }",
-        "}");
-
-    assertBuggyFails(
-        "Line 6: Native JsType constructor 'EntryPoint.Buggy.EntryPoint$Buggy(int)' "
-            + "cannot have non-empty method body.");
-  }
-
   public void testNativeJsTypeImplicitSuperSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
@@ -1206,38 +1210,6 @@
     assertBuggySucceeds();
   }
 
-  public void testNativeJsTypeInlineStaticInitializerFails() {
-    addSnippetImport("jsinterop.annotations.JsType");
-    addSnippetClassDecl(
-        "@JsType(isNative=true) public static class Buggy {",
-        "  public static final String s = new String(\"hello\");",
-        "}");
-
-    assertBuggyFails(
-        "Line 4: Native JsType 'EntryPoint.Buggy' cannot have static initializer.");
-  }
-
-  public void testNativeJsTypeInterfaceInlineInitializerFails() {
-    addSnippetImport("jsinterop.annotations.JsType");
-    addSnippetClassDecl(
-        "@JsType(isNative=true) public interface Buggy {",
-        "  static final String s = new String(\"hello\");",
-        "}");
-
-    assertBuggyFails(
-        "Line 4: Native JsType 'EntryPoint.Buggy' cannot have static initializer.");
-  }
-
-  public void testNativeJsTypeCompileTimeConstantSucceeds() throws Exception {
-    addSnippetImport("jsinterop.annotations.JsType");
-    addSnippetClassDecl(
-        "@JsType(isNative=true) public static class Buggy {",
-        "  public static final String s = \"hello\";",
-        "}");
-
-    assertBuggySucceeds();
-  }
-
   public void testJsTypeInterfaceInInstanceofFails() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
@@ -1285,16 +1257,6 @@
         "Line 4: Local class 'EntryPoint.Buggy.1Local' cannot be a JsType.");
   }
 
-  public void testNativeJsTypeInterfaceCompileTimeConstantSucceeds() throws Exception {
-    addSnippetImport("jsinterop.annotations.JsType");
-    addSnippetClassDecl(
-        "@JsType(isNative=true) public interface Buggy {",
-        "  static final String s = \"hello\";",
-        "}");
-
-    assertBuggySucceeds();
-  }
-
   public void testNativeJsTypeExtendsNativeJsTypeSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
@@ -1393,24 +1355,15 @@
     addSnippetImport("jsinterop.annotations.JsOverlay");
     addSnippetClassDecl(
         "@JsType(isNative=true) public static class Buggy {",
-        "  @JsOverlay public final void m() { }",
-        "  @JsOverlay public final void m(int x) { }",
-        "  @JsOverlay private final void n() { }",
-        "  @JsOverlay final void o() { }",
-        "  @JsOverlay protected final void p() { }",
-        "}");
-
-    assertBuggySucceeds();
-  }
-
-  public void testJsOverlayOnStaticSucceds() throws Exception {
-    addSnippetImport("jsinterop.annotations.JsType");
-    addSnippetImport("jsinterop.annotations.JsOverlay");
-    addSnippetClassDecl(
-        "@JsType(isNative=true) public static class Buggy {",
+        "  @JsOverlay public static final int f = 2;",
         "  @JsOverlay public static void m() { }",
         "  @JsOverlay public static void m(int x) { }",
         "  @JsOverlay private static void m(boolean x) { }",
+        "  @JsOverlay public final void n() { }",
+        "  @JsOverlay public final void n(int x) { }",
+        "  @JsOverlay private final void n(boolean x) { }",
+        "  @JsOverlay final void o() { }",
+        "  @JsOverlay protected final void p() { }",
         "}");
 
     assertBuggySucceeds();
@@ -1446,16 +1399,21 @@
         "Line 9: JsOverlay method 'void EntryPoint.Buggy.m()' cannot override a supertype method.");
   }
 
-  public void testJsOverlayOnNonFinalFails() {
+  public void testJsOverlayOnNonFinalMethodAndNonCompileTimeConstantFieldFails() {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetImport("jsinterop.annotations.JsOverlay");
     addSnippetClassDecl(
         "@JsType(isNative=true) public static class Buggy {",
+        "  @JsOverlay public static int f1 = 2;",
+        "  @JsOverlay public final int f2 = 2;",
         "  @JsOverlay public void m() { }",
         "}");
 
     assertBuggyFails(
-        "Line 6: JsOverlay method 'void EntryPoint.Buggy.m()' cannot be non-final nor native.");
+        "Line 5: Native JsType 'EntryPoint.Buggy' cannot have initializer.",
+        "Line 6: JsOverlay field 'int EntryPoint.Buggy.f1' can only be a compile time constant.",
+        "Line 7: JsOverlay field 'int EntryPoint.Buggy.f2' can only be a compile time constant.",
+        "Line 8: JsOverlay method 'void EntryPoint.Buggy.m()' cannot be non-final nor native.");
   }
 
   public void testJsOverlayOnNativeMethodFails() {
@@ -1505,22 +1463,13 @@
     addSnippetImport("jsinterop.annotations.JsOverlay");
     addSnippetClassDecl(
         "@JsType public static class Buggy {",
+        "  @JsOverlay public static final int f = 2;",
         "  @JsOverlay public final void m() { };",
         "}");
 
     assertBuggyFails(
-        "Line 6: Method 'void EntryPoint.Buggy.m()' in non-native type cannot be @JsOverlay.");
-  }
-
-  public void testJsOverlayOnNonJsTypeFails() {
-    addSnippetImport("jsinterop.annotations.JsOverlay");
-    addSnippetClassDecl(
-        "public static class Buggy {",
-        "  @JsOverlay public final void m() { };",
-        "}");
-
-    assertBuggyFails(
-        "Line 5: Method 'void EntryPoint.Buggy.m()' in non-native type cannot be @JsOverlay.");
+        "Line 6: JsOverlay 'int EntryPoint.Buggy.f' can only be declared in a native type.",
+        "Line 7: JsOverlay 'void EntryPoint.Buggy.m()' can only be declared in a native type.");
   }
 
   public void testJsTypeExtendsNativeJsTypeSucceeds() throws Exception {
@@ -1601,27 +1550,27 @@
     assertBuggySucceeds();
   }
 
-  public void testNativeJsTypeMembersFails() {
+  public void testNativeJsTypeBadMembersFails() {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetImport("jsinterop.annotations.JsIgnore");
     addSnippetClassDecl(
         "@JsType(isNative=true) static class Buggy {",
+        "  public static final int s = 42;",
+        "  public int f = 42;",
         "  @JsIgnore public Buggy() { }",
         "  @JsIgnore public int x;",
-        "  native void m();",
         "  @JsIgnore public native void n();",
-        "  int f;",
         "  public void o() {}",
         "  public native void p() /*-{}-*/;",
         "}");
 
     assertBuggyFails(
-        "Line 6: Native JsType member 'EntryPoint.Buggy.EntryPoint$Buggy()' is not public or "
-            + "has @JsIgnore.",
-        "Line 7: Native JsType member 'int EntryPoint.Buggy.x' is not public or has @JsIgnore.",
-        "Line 8: Native JsType member 'void EntryPoint.Buggy.m()' is not public or has @JsIgnore.",
-        "Line 9: Native JsType member 'void EntryPoint.Buggy.n()' is not public or has @JsIgnore.",
-        "Line 10: Native JsType member 'int EntryPoint.Buggy.f' is not public or has @JsIgnore.",
+        "Line 5: Native JsType 'EntryPoint.Buggy' cannot have initializer.",
+        "Line 6: Native JsType field 'int EntryPoint.Buggy.s' cannot have initializer.",
+        "Line 7: Native JsType field 'int EntryPoint.Buggy.f' cannot have initializer.",
+        "Line 8: Native JsType member 'EntryPoint.Buggy.EntryPoint$Buggy()' cannot have @JsIgnore.",
+        "Line 9: Native JsType member 'int EntryPoint.Buggy.x' cannot have @JsIgnore.",
+        "Line 10: Native JsType member 'void EntryPoint.Buggy.n()' cannot have @JsIgnore.",
         "Line 11: Native JsType method 'void EntryPoint.Buggy.o()' should be native or abstract.",
         "Line 12: JSNI method 'void EntryPoint.Buggy.p()' is not allowed in a native JsType.");
   }
@@ -1637,39 +1586,37 @@
     assertBuggySucceeds();
   }
 
-  public void testNativeJsTypeOverloadsSucceeds() throws Exception {
+  public void testNativeJsTypeSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
-        "@JsType(isNative=true) static class Buggy {",
+        "@JsType(isNative=true) abstract static class Buggy {",
         "  public static native void m();",
-        "  public static native void m(Object o);",
-        "  public static native void m(String o);",
+        "  protected static native void m(Object o);",
+        "  private static native void m(String o);",
         "  public Buggy() { }",
-        "  public Buggy(Object o) { }",
-        "  public Buggy(String o) { }",
+        "  protected Buggy(Object o) { }",
+        "  private Buggy(String o) { }",
         "  public native void n();",
-        "  public native void n(Object o);",
-        "  public native void n(String o);",
+        "  protected native void n(Object o);",
+        "  private native void n(String o);",
+        "  public abstract void o();",
+        "  protected abstract void o(Object o);",
+        "  abstract void o(String o);",
         "}");
 
     assertBuggySucceeds();
   }
 
-  public void testNativeJsTypeAbstractMethodSucceeds() throws Exception {
-    addSnippetImport("jsinterop.annotations.JsType");
-    addSnippetClassDecl(
-        "@JsType(isNative=true) static abstract class Buggy {",
-        "  public abstract void m(Object o);",
-        "}");
-
-    assertBuggySucceeds();
-  }
-
-  public void testNativeJsTypeNonPublicConstructorSucceeds() throws Exception {
+  public void testNativeJsTypeFieldsSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
         "@JsType(isNative=true) static class Buggy {",
-        "  Buggy() { }",
+        "  public static int f1;",
+        "  protected static int f2;",
+        "  private static int f3;",
+        "  public int f4;",
+        "  protected int f5;",
+        "  private int f6;",
         "}");
 
     assertBuggySucceeds();
diff --git a/user/src/jsinterop/annotations/JsOverlay.java b/user/src/jsinterop/annotations/JsOverlay.java
index 2ba6244..d3b054e 100644
--- a/user/src/jsinterop/annotations/JsOverlay.java
+++ b/user/src/jsinterop/annotations/JsOverlay.java
@@ -23,14 +23,15 @@
 
 /**
  * JsOverlay is used to add new helper APIs to existing JavaScript types. This is achieved by adding
- * the new method to @JsType(isNative=true) and marking it with this annotation.
+ * the new method/field to a @JsType(isNative=true) and marking it with this annotation.
  * <p>
  * Note that the JsOverlay methods cannot be called from JavaScript, cannot override any existing
  * methods and needs to be marked as final. This is because underneath, the original type is not
- * modified and the method is simply turned into a static dispatch.
+ * modified and the method is simply turned into a static dispatch. Similarly, JsOverlay fields can
+ * only be compile time constants.
  */
 @Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.METHOD)
+@Target({ElementType.METHOD, ElementType.FIELD})
 @Documented
 public @interface JsOverlay {
 }
diff --git a/user/test/com/google/gwt/core/client/interop/JsTypeTest.java b/user/test/com/google/gwt/core/client/interop/JsTypeTest.java
index 08238ac..ad9a5f2 100644
--- a/user/test/com/google/gwt/core/client/interop/JsTypeTest.java
+++ b/user/test/com/google/gwt/core/client/interop/JsTypeTest.java
@@ -485,6 +485,9 @@
   @JsType(isNative = true, namespace = GLOBAL, name = "Object")
   static class NativeJsTypeWithOverlay {
 
+    @JsOverlay
+    public static final int x = 2;
+
     public static native String[] keys(Object o);
 
     @JsOverlay @DoNotInline
@@ -508,5 +511,6 @@
     NativeJsTypeWithOverlay object = createNativeJsTypeWithOverlay();
     assertTrue(object.hasM());
     assertTrue(NativeJsTypeWithOverlay.hasM(object));
+    assertEquals(2, NativeJsTypeWithOverlay.x);
   }
 }