Disallow @JsType in local classes and native @JsType in proper inner classes.

Change-Id: I1478898d0f2ddddefdcbb895e0d336685ee9cbc8
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index f8c997a..8222bbb 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -1458,8 +1458,7 @@
           continue;
         }
 
-        if (type.isJsType() && !type.getClassDisposition().isLocalType()) {
-          // only types with explicit source names in Java may have an exported prototype
+        if (type.isJsType()) {
           exportedMembersByExportName.put(type.getQualifiedJsName(), type);
         }
 
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 328936e..8717ee0 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
@@ -25,6 +25,7 @@
 import com.google.gwt.dev.jjs.ast.JConstructor;
 import com.google.gwt.dev.jjs.ast.JDeclarationStatement;
 import com.google.gwt.dev.jjs.ast.JDeclaredType;
+import com.google.gwt.dev.jjs.ast.JDeclaredType.NestedClassDisposition;
 import com.google.gwt.dev.jjs.ast.JExpression;
 import com.google.gwt.dev.jjs.ast.JExpressionStatement;
 import com.google.gwt.dev.jjs.ast.JField;
@@ -413,12 +414,33 @@
     }.accept(jprogram);
   }
 
-  private void checkNativeJsType(JDeclaredType type) {
+  private boolean checkJsType(JDeclaredType type) {
+    // Java (at least up to Java 8) does not allow to annotate anonymous classes or lambdas; if
+    // it ever becomes possible we should emit an error.
+    assert type.getClassDisposition() != NestedClassDisposition.ANONYMOUS
+        && type.getClassDisposition() != NestedClassDisposition.LAMBDA;
+
+    if  (type.getClassDisposition() == NestedClassDisposition.LOCAL) {
+      logError("Local class '%s' cannot be a JsType.", type);
+      return false;
+    }
+
+    return true;
+  }
+
+  private boolean checkNativeJsType(JDeclaredType type) {
     // TODO(rluble): add inheritance restrictions.
+
     if (type.isEnumOrSubclass() != null) {
       logError("Enum '%s' cannot be a native JsType.", type);
-      return;
+      return false;
     }
+
+    if (type.getClassDisposition() == NestedClassDisposition.INNER) {
+      logError("Non static inner class '%s' cannot be a native JsType.", type);
+      return false;
+    }
+
     if (!isClinitEmpty(type)) {
       logError("Native JsType '%s' cannot have static initializer.", type);
     }
@@ -429,6 +451,7 @@
             getMemberDescription(constructor));
       }
     }
+    return true;
   }
 
   private void checkJsFunction(JDeclaredType type) {
@@ -489,8 +512,15 @@
   private void checkType(JDeclaredType type) {
     minimalRebuildCache.removeExportedNames(type.getName());
 
+    if (type.isJsType()) {
+      if (!checkJsType(type)) {
+        return;
+      }
+    }
     if (type.isJsNative()) {
-      checkNativeJsType(type);
+      if (!checkNativeJsType(type)) {
+        return;
+      }
     }
 
     if (type.isJsFunction()) {
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 259daaa..c17ca62 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
@@ -1156,6 +1156,32 @@
         "Line 4: Enum 'EntryPoint.Buggy' cannot be a native JsType.");
   }
 
+  public void testInnerNativeJsTypeFails() {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetClassDecl(
+        "@JsType(isNative=true) public class Buggy { }");
+
+    assertBuggyFails(
+        "Line 4: Non static inner class 'EntryPoint.Buggy' cannot be a native JsType.");
+  }
+
+  public void testInnerJsTypeSucceeds() throws Exception {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetClassDecl(
+        "@SuppressWarnings(\"unusable-by-js\") @JsType public class Buggy { }");
+
+    assertBuggySucceeds();
+  }
+
+  public void testLocalJsTypeFails() {
+    addSnippetImport("jsinterop.annotations.JsType");
+    addSnippetClassDecl(
+        "public class Buggy { void m() { @JsType class Local {} } }");
+
+    assertBuggyFails(
+        "Line 4: Local class 'EntryPoint.Buggy.1Local' cannot be a JsType.");
+  }
+
   public void testNativeJsTypeInterfaceCompileTimeConstantSucceeds() throws Exception {
     addSnippetImport("jsinterop.annotations.JsType");
     addSnippetClassDecl(
diff --git a/user/test/com/google/gwt/dev/jjs/test/CompilerMiscRegressionTest.java b/user/test/com/google/gwt/dev/jjs/test/CompilerMiscRegressionTest.java
index 311abbd..0fdbbc3 100644
--- a/user/test/com/google/gwt/dev/jjs/test/CompilerMiscRegressionTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/CompilerMiscRegressionTest.java
@@ -314,27 +314,28 @@
    * <p>
    * Typetightener used to incorrectly tighten method calls marked with STATIC_DISPATCH_ONLY.
    */
+
   public void testIncorrectDispatch() {
-    final int[] state = new int[1];
-
-    @JsType
-    abstract class A {
-      public void m() {
-        state[0] = 1;
-      }
-    }
-
-    @JsType
-    class B extends A {
-      public void m() {
-        super.m();
-      }
-    }
-
+    state = new int[1];
     new B().m();
     assertEquals(1, state[0]);
   }
 
+  static int[] state;
+  @JsType
+  abstract static class A {
+    public void m() {
+      state[0] = 1;
+    }
+  }
+
+  @JsType
+  static class B extends A {
+    public void m() {
+      super.m();
+    }
+  }
+
   private static void assertEqualContents(float[] expected, float[] actual) {
 
     assertEquals("Array length mismatch", expected.length, actual.length);