Changes long warnings to errors.  A new annotation is now required to compile without errors.
- LongFromJSNIChecker is now implemented totally in terms of JDT.
- The trick was to do lookup via Scope.getType(), not scope.environment.askForType(). The former has all kinds of smarts for resolving graphs of type references in a dependency-driven manner.  The latter bypasses all the smarts and just asks the source oracle, which won't know.

Patch by: spoon
Review by: me


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2339 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jdt/AstCompiler.java b/dev/core/src/com/google/gwt/dev/jdt/AstCompiler.java
index f7fe488..8def4ac 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/AstCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/AstCompiler.java
@@ -122,6 +122,7 @@
   protected void doCompilationUnitDeclarationValidation(
       CompilationUnitDeclaration cud, TreeLogger logger) {
     JSORestrictionsChecker.check(cud);
+    LongFromJSNIChecker.check(cud);
     BinaryTypeReferenceRestrictionsChecker.check(cud);
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/jdt/LongFromJSNIChecker.java b/dev/core/src/com/google/gwt/dev/jdt/LongFromJSNIChecker.java
index edffb59..ff53b63 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/LongFromJSNIChecker.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/LongFromJSNIChecker.java
@@ -15,35 +15,27 @@
  */
 package com.google.gwt.dev.jdt;
 
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.typeinfo.JClassType;
-import com.google.gwt.core.ext.typeinfo.JField;
-import com.google.gwt.core.ext.typeinfo.JMethod;
-import com.google.gwt.core.ext.typeinfo.JParameter;
-import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
-import com.google.gwt.core.ext.typeinfo.JType;
-import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.dev.jjs.InternalCompilerException;
-import com.google.gwt.dev.util.Jsni;
+import com.google.gwt.core.client.UnsafeNativeLong;
 import com.google.gwt.dev.util.JsniRef;
 
+import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.ASTVisitor;
-import org.eclipse.jdt.internal.compiler.CompilationResult;
 import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
 import org.eclipse.jdt.internal.compiler.ast.Argument;
 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.TypeReference;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
-import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
 import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
 import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
 import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
+import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
+import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
 import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
 import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
-import org.eclipse.jdt.internal.compiler.util.Util;
 
-import java.lang.reflect.Field;
 import java.util.Set;
 
 /**
@@ -60,7 +52,7 @@
       ClassFileConstants {
     @Override
     public void endVisit(MethodDeclaration meth, ClassScope scope) {
-      if (meth.isNative() && !suppressingWarnings(meth, scope)) {
+      if (meth.isNative() && !hasUnsafeLongsAnnotation(meth, scope)) {
         checkDecl(meth, scope);
         checkRefs(meth, scope);
       }
@@ -69,15 +61,16 @@
     private void checkDecl(MethodDeclaration meth, ClassScope scope) {
       TypeReference returnType = meth.returnType;
       if (containsLong(returnType, scope)) {
-        warn(meth, "Return value of type '" + returnType
-            + "' is an opaque, non-numeric value in JS code");
+        longAccessError(meth, "Type '" + typeString(returnType)
+            + "' may not be returned from a JSNI method");
       }
 
       if (meth.arguments != null) {
         for (Argument arg : meth.arguments) {
           if (containsLong(arg.type, scope)) {
-            warn(arg, "Parameter '" + String.valueOf(arg.name) + "': '"
-                + arg.type + "' is an opaque, non-numeric value in JS code");
+            longAccessError(arg, "Parameter '" + String.valueOf(arg.name)
+                + "': type '" + typeString(arg.type)
+                + "' is not safe to access in JSNI code");
           }
         }
       }
@@ -85,38 +78,41 @@
 
     private void checkFieldRef(MethodDeclaration meth, JsniRef jsniRef) {
       assert jsniRef.isField();
-      JField target = getField(jsniRef);
+      FieldBinding target = getField(jsniRef);
       if (target == null) {
         return;
       }
-      if (containsLong(target.getType())) {
-        warn(meth, "Referencing field '"
-            + target.getEnclosingType().getSimpleSourceName() + "."
-            + target.getName() + "': '" + target.getType()
-            + "' is an opaque, non-numeric value in JS code");
+      if (containsLong(target.type)) {
+        longAccessError(meth, "Referencing field '" + jsniRef.className() + "."
+            + jsniRef.memberName() + "': type '" + typeString(target.type)
+            + "' is not safe to access in JSNI code");
       }
     }
 
     private void checkMethodRef(MethodDeclaration meth, JsniRef jsniRef) {
       assert jsniRef.isMethod();
-      JMethod target = getMethod(jsniRef);
+      MethodBinding target = getMethod(jsniRef);
       if (target == null) {
         return;
       }
-      if (containsLong(target.getReturnType())) {
-        warn(meth, "Referencing method '"
-            + target.getEnclosingType().getSimpleSourceName() + "."
-            + target.getName() + "': return type '" + target.getReturnType()
-            + "' is an opaque, non-numeric value in JS code");
+      if (containsLong(target.returnType)) {
+        longAccessError(meth, "Referencing method '" + jsniRef.className()
+            + "." + jsniRef.memberName() + "': return type '"
+            + typeString(target.returnType)
+            + "' is not safe to access in JSNI code");
       }
 
-      for (JParameter param : target.getParameters()) {
-        if (containsLong(param.getType())) {
-          warn(meth, "Referencing method '"
-              + target.getEnclosingType().getSimpleSourceName() + "."
-              + target.getName() + "': parameter '" + param.getName() + "': '"
-              + param.getType()
-              + "' is an opaque, non-numeric value in JS code");
+      if (target.parameters != null) {
+        int i = 0;
+        for (TypeBinding paramType : target.parameters) {
+          ++i;
+          if (containsLong(paramType)) {
+            // It would be nice to print the parameter name, but how to find it?
+            longAccessError(meth, "Parameter " + i + " of method '"
+                + jsniRef.className() + "." + jsniRef.memberName()
+                + "': type '" + typeString(paramType)
+                + "' may not be passed out of JSNI code");
+          }
         }
       }
     }
@@ -138,13 +134,6 @@
       }
     }
 
-    private boolean containsLong(JType type) {
-      if (type != null && type.isArray() != null) {
-        return containsLong(type.isArray().getLeafType());
-      }
-      return type == JPrimitiveType.LONG;
-    }
-
     /**
      * Check whether the argument type is long or an array of (arrays of...)
      * long. If the argument is <code>null</code>, returns <code>false</code>.
@@ -172,46 +161,33 @@
       return returnType != null && containsLong(returnType.resolveType(scope));
     }
 
-    private JClassType findType(JsniRef jsniRef) {
-      // Use source name.
-      String className = jsniRef.className();
-      className = className.replace('$', '.');
-      JClassType type = typeOracle.findType(className);
-      return type;
-    }
-
-    /**
-     * Returns either the type returned if this reference is "read". For a
-     * field, returns the field's type. For a method, returns the method's
-     * return type. If the reference cannot be resolved, returns null.
-     */
-    private JField getField(JsniRef jsniRef) {
-      assert jsniRef.isField();
-      JClassType type = findType(jsniRef);
-      if (type == null) {
-        return null;
-      }
-
-      JField field = type.findField(jsniRef.memberName());
-      if (field != null) {
-        return field;
+    private ReferenceBinding findClass(JsniRef jsniRef) {
+      String className = jsniRef.className().replace('$', '.');
+      char[][] compoundName = CharOperation.splitOn('.',
+          className.toCharArray());
+      TypeBinding binding = cud.scope.getType(compoundName, compoundName.length);
+      if (binding instanceof ReferenceBinding) {
+        return (ReferenceBinding) binding;
       }
       return null;
     }
 
-    /**
-     * Returns either the type returned if this reference is "read". For a
-     * field, returns the field's type. For a method, returns the method's
-     * return type. If the reference cannot be resolved, returns null.
-     */
-    private JMethod getMethod(JsniRef jsniRef) {
-      assert jsniRef.isMethod();
-      JClassType type = findType(jsniRef);
+    private FieldBinding getField(JsniRef jsniRef) {
+      assert jsniRef.isField();
+      ReferenceBinding type = findClass(jsniRef);
       if (type == null) {
         return null;
       }
+      return type.getField(jsniRef.memberName().toCharArray(), false);
+    }
 
-      for (JMethod method : type.getMethods()) {
+    private MethodBinding getMethod(JsniRef jsniRef) {
+      assert jsniRef.isMethod();
+      ReferenceBinding type = findClass(jsniRef);
+      if (type == null) {
+        return null;
+      }
+      for (MethodBinding method : type.getMethods(jsniRef.memberName().toCharArray())) {
         if (paramTypesMatch(method, jsniRef)) {
           return method;
         }
@@ -219,86 +195,74 @@
       return null;
     }
 
-    private boolean paramTypesMatch(JMethod method, JsniRef jsniRef) {
-      String methodSig = Jsni.getMemberSignature(method);
-      return methodSig.equals(jsniRef.memberSignature());
-    }
-
-    private boolean suppressingWarnings(MethodDeclaration meth, ClassScope scope) {
-      CompilationResult result = scope.referenceCompilationUnit().compilationResult;
-      long[] suppressWarningIrritants;
-      long[] suppressWarningScopePositions; // (start << 32) + end
-      int suppressWarningsCount;
-
-      try {
-        {
-          Field field = CompilationResult.class.getDeclaredField("suppressWarningIrritants");
-          field.setAccessible(true);
-          suppressWarningIrritants = (long[]) field.get(result);
-        }
-        {
-          Field field = CompilationResult.class.getDeclaredField("suppressWarningScopePositions");
-          field.setAccessible(true);
-          suppressWarningScopePositions = (long[]) field.get(result);
-        }
-        {
-          Field field = CompilationResult.class.getDeclaredField("suppressWarningsCount");
-          field.setAccessible(true);
-          suppressWarningsCount = (Integer) field.get(result);
-        }
-      } catch (NoSuchFieldException e) {
-        throw new InternalCompilerException(
-            "Failed to read suppress warnings data from JDT", e);
-      } catch (IllegalAccessException e) {
-        throw new InternalCompilerException(
-            "Failed to read suppress warnings data from JDT", e);
-      }
-
-      for (int i = 0; i < suppressWarningsCount; i++) {
-        if ((suppressWarningIrritants[i] & CompilerOptions.DiscouragedReference) != 0) {
-          long start = suppressWarningScopePositions[i] >> 32;
-          long end = suppressWarningScopePositions[i] & 0xFFFFFFFF;
-          if (meth.bodyStart >= start && meth.bodyStart <= end) {
+    private boolean hasUnsafeLongsAnnotation(MethodDeclaration meth,
+        ClassScope scope) {
+      if (meth.annotations != null) {
+        for (Annotation annot : meth.annotations) {
+          if (isUnsafeLongAnnotation(annot, scope)) {
             return true;
           }
         }
       }
-
       return false;
     }
 
-    private void warn(ASTNode node, String message) {
-      CompilationResult compResult = cud.compilationResult();
-      int[] lineEnds = compResult.getLineSeparatorPositions();
-      int startLine = Util.getLineNumber(node.sourceStart(), lineEnds, 0,
-          lineEnds.length - 1);
-      String fileName = String.valueOf(cud.getFileName());
-      logger.log(TreeLogger.WARN, fileName + "(" + startLine + "): " + message,
-          null);
+    private boolean isUnsafeLongAnnotation(Annotation annot, ClassScope scope) {
+      if (annot.type != null) {
+        TypeBinding resolved = annot.type.resolveType(scope);
+        if (resolved != null) {
+          if (resolved instanceof ReferenceBinding) {
+            ReferenceBinding rb = (ReferenceBinding) resolved;
+            if (CharOperation.equals(rb.compoundName,
+                UNSAFE_LONG_ANNOTATION_CHARS)) {
+              return true;
+            }
+          }
+        }
+      }
+      return false;
+    }
+
+    private void longAccessError(ASTNode node, String message) {
+      GWTProblem.recordInCud(node, cud, message);
+    }
+
+    private boolean paramTypesMatch(MethodBinding method, JsniRef jsniRef) {
+      StringBuilder methodSig = new StringBuilder();
+      if (method.parameters != null) {
+        for (TypeBinding binding : method.parameters) {
+          methodSig.append(binding.signature());
+        }
+      }
+      return methodSig.toString().equals(jsniRef.paramTypesString());
+    }
+
+    private String typeString(TypeBinding type) {
+      return String.valueOf(type.shortReadableName());
+    }
+
+    private String typeString(TypeReference type) {
+      return type.toString();
     }
   }
 
+  private static final char[][] UNSAFE_LONG_ANNOTATION_CHARS = CharOperation.splitOn(
+      '.', UnsafeNativeLong.class.getName().toCharArray());
+
   /**
    * Checks an entire
    * {@link org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration}.
    * 
    */
-  public static void check(TypeOracle typeOracle,
-      CompilationUnitDeclaration cud, TreeLogger logger) {
-    LongFromJSNIChecker checker = new LongFromJSNIChecker(typeOracle, cud,
-        logger);
+  public static void check(CompilationUnitDeclaration cud) {
+    LongFromJSNIChecker checker = new LongFromJSNIChecker(cud);
     checker.check();
   }
 
   private final CompilationUnitDeclaration cud;
-  private final TreeLogger logger;
-  private final TypeOracle typeOracle;
 
-  private LongFromJSNIChecker(TypeOracle typeOracle,
-      CompilationUnitDeclaration cud, TreeLogger logger) {
-    this.typeOracle = typeOracle;
+  private LongFromJSNIChecker(CompilationUnitDeclaration cud) {
     this.cud = cud;
-    this.logger = logger;
   }
 
   private void check() {
diff --git a/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java b/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java
index 59d63c9..01b2a21 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java
@@ -617,10 +617,6 @@
     Util.invokeInaccessableMethod(TypeOracle.class, "refresh",
         new Class[] {TreeLogger.class}, oracle, new Object[] {logger});
 
-    for (CompilationUnitDeclaration cud : cudsByFileName.values()) {
-      LongFromJSNIChecker.check(oracle, cud, logger);
-    }
-
     PerfLogger.end();
 
     return oracle;
diff --git a/dev/core/super/com/google/gwt/core/client/UnsafeNativeLong.java b/dev/core/super/com/google/gwt/core/client/UnsafeNativeLong.java
new file mode 100644
index 0000000..ceaa8de
--- /dev/null
+++ b/dev/core/super/com/google/gwt/core/client/UnsafeNativeLong.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.client;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation can be placed on a native method to allow it to directly
+ * access Java <code>long</code> values. Without this annotation present,
+ * accessing a <code>long</code> in any way from a JSNI method is an error.
+ * This includes declaring a parameter or return type of <code>long</code>,
+ * calling a method that takes or returns a <code>long</code>, or accessing a
+ * <code>long</code> field.
+ * 
+ * <p>
+ * The reason for the restriction is that Java long values are not represented
+ * as numeric values in compiled code, but as opaque Objects. Attempting to
+ * perform math operations on them would produce undesirable results.
+ * </p>
+ * <p>
+ * Use this annotation with care; the only safe thing to do with
+ * <code>long</code> values in JSNI code is to pass them back into Java
+ * unaltered.
+ * </p>
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.SOURCE)
+public @interface UnsafeNativeLong {
+}
diff --git a/dev/core/super/com/google/gwt/lang/LongLib.java b/dev/core/super/com/google/gwt/lang/LongLib.java
index ec03903..ddfacc0 100644
--- a/dev/core/super/com/google/gwt/lang/LongLib.java
+++ b/dev/core/super/com/google/gwt/lang/LongLib.java
@@ -24,6 +24,8 @@
 import static com.google.gwt.lang.LongLib.Const.TWO_PWR_24;
 import static com.google.gwt.lang.LongLib.Const.ZERO;
 
+import com.google.gwt.core.client.UnsafeNativeLong;
+
 /**
  * Implements a Java <code>long</code> in a way that can be translated to
  * JavaScript.
@@ -670,7 +672,7 @@
   /**
    * Web mode implementation; the long is already the right object.
    */
-  @SuppressWarnings("restriction")
+  @UnsafeNativeLong
   private static native double[] typeChange0(long value) /*-{
     return value;
   }-*/;
diff --git a/dev/core/test/com/google/gwt/dev/jdt/LongFromJSNITest.java b/dev/core/test/com/google/gwt/dev/jdt/LongFromJSNITest.java
index 8e41ad7..fb1c5b2 100644
--- a/dev/core/test/com/google/gwt/dev/jdt/LongFromJSNITest.java
+++ b/dev/core/test/com/google/gwt/dev/jdt/LongFromJSNITest.java
@@ -17,7 +17,7 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.util.UnitTestTreeLogger;
 
 import junit.framework.TestCase;
@@ -34,10 +34,8 @@
     code.append("  $wnd.alert(\"x is: \"+this.@Buggy::x); }-*/;\n");
     code.append("}\n");
 
-    shouldGenerateWarning(
-        code,
-        3,
-        "Referencing field 'Buggy.x': 'long' is an opaque, non-numeric value in JS code");
+    shouldGenerateError(code, 3,
+        "Referencing field 'Buggy.x': type 'long' is not safe to access in JSNI code");
   }
 
   public void testLongArray() throws UnableToCompleteException {
@@ -48,10 +46,10 @@
     code.append("    $wnd.alert(this.@Buggy::m()()); }-*/;\n");
     code.append("}\n");
 
-    shouldGenerateWarning(
+    shouldGenerateError(
         code,
         3,
-        "Referencing method \'Buggy.m\': return type \'long[]\' is an opaque, non-numeric value in JS code");
+        "Referencing method \'Buggy.m\': return type 'long[]' is not safe to access in JSNI code");
   }
 
   public void testLongParameter() throws UnableToCompleteException {
@@ -60,8 +58,8 @@
     code.append("  native void jsniMeth(long x) /*-{ return; }-*/;\n");
     code.append("}\n");
 
-    shouldGenerateWarning(code, 2,
-        "Parameter 'x': 'long' is an opaque, non-numeric value in JS code");
+    shouldGenerateError(code, 2,
+        "Parameter 'x': type 'long' is not safe to access in JSNI code");
   }
 
   public void testLongReturn() throws UnableToCompleteException {
@@ -70,8 +68,8 @@
     code.append("  native long jsniMeth() /*-{ return 0; }-*/;\n");
     code.append("}\n");
 
-    shouldGenerateWarning(code, 2,
-        "Return value of type 'long' is an opaque, non-numeric value in JS code");
+    shouldGenerateError(code, 2,
+        "Type 'long' may not be returned from a JSNI method");
   }
 
   public void testMethodArgument() throws UnableToCompleteException {
@@ -81,10 +79,10 @@
     code.append("  native void jsniMeth() /*-{ this.@Buggy::print(J)(0); }-*/;\n");
     code.append("}\n");
 
-    shouldGenerateWarning(
+    shouldGenerateError(
         code,
         3,
-        "Referencing method \'Buggy.print\': parameter \'x\': \'long\' is an opaque, non-numeric value in JS code");
+        "Parameter 1 of method \'Buggy.print\': type 'long' may not be passed out of JSNI code");
   }
 
   public void testMethodReturn() throws UnableToCompleteException {
@@ -95,10 +93,10 @@
     code.append("    $wnd.alert(this.@Buggy::m()()); }-*/;\n");
     code.append("}\n");
 
-    shouldGenerateWarning(
+    shouldGenerateError(
         code,
         3,
-        "Referencing method 'Buggy.m': return type 'long' is an opaque, non-numeric value in JS code");
+        "Referencing method 'Buggy.m': return type 'long' is not safe to access in JSNI code");
   }
 
   public void testOverloadedMethodWithNoWarning()
@@ -111,7 +109,7 @@
     code.append("    $wnd.alert(this.@Buggy::m(Ljava/lang/String;)(\"hello\")); }-*/;\n");
     code.append("}\n");
 
-    shouldGenerateNoWarning(code);
+    shouldGenerateNoError(code);
   }
 
   public void testOverloadedMethodWithWarning()
@@ -124,53 +122,70 @@
     code.append("    $wnd.alert(this.@Buggy::m(I)(10)); }-*/;\n");
     code.append("}\n");
 
-    shouldGenerateWarning(
+    shouldGenerateError(
         code,
         4,
-        "Referencing method 'Buggy.m': return type 'long' is an opaque, non-numeric value in JS code");
+        "Referencing method 'Buggy.m': return type 'long' is not safe to access in JSNI code");
   }
 
-  public void testSuppressWarnings() throws UnableToCompleteException {
+  public void testUnsafeAnnotation() throws UnableToCompleteException {
     {
       StringBuffer code = new StringBuffer();
+      code.append("import com.google.gwt.core.client.UnsafeNativeLong;");
       code.append("class Buggy {\n");
       code.append("  void print(long x) { }\n");
-      code.append("  @SuppressWarnings(\"restriction\")\n");
+      code.append("  @UnsafeNativeLong\n");
       code.append("  native void jsniMeth() /*-{ this.@Buggy::print(J)(0); }-*/;\n");
       code.append("}\n");
 
-      shouldGenerateNoWarning(code);
+      shouldGenerateNoError(code);
     }
+  }
 
+  private void addLongCheckingCups(TypeOracleBuilder builder)
+      throws UnableToCompleteException {
     {
-      StringBuffer code = new StringBuffer();
-      code.append("@SuppressWarnings(\"restriction\")\n");
-      code.append("class Buggy {\n");
-      code.append("  void print(long x) { }\n");
-      code.append("  native void jsniMeth() /*-{ this.@Buggy::print(J)(0); }-*/;\n");
+      StringBuilder code = new StringBuilder();
+      code.append("package com.google.gwt.core.client;\n");
+      code.append("public @interface UnsafeNativeLong {\n");
       code.append("}\n");
 
-      shouldGenerateNoWarning(code);
+      TypeOracleTestingUtils.addCup(builder,
+          "com.google.gwt.core.client.UnsafeNativeLong", code);
     }
   }
 
-  private void shouldGenerateNoWarning(StringBuffer code)
-      throws UnableToCompleteException {
-    shouldGenerateWarning(code, -1, null);
+  private TypeOracle buildOracleWithCode(CharSequence code,
+      UnitTestTreeLogger logger) throws UnableToCompleteException {
+    TypeOracleBuilder builder = new TypeOracleBuilder();
+    TypeOracleTestingUtils.addStandardCups(builder);
+    addLongCheckingCups(builder);
+    TypeOracleTestingUtils.addCup(builder, "Buggy", code);
+    return builder.build(logger);
   }
 
-  private void shouldGenerateWarning(CharSequence code, int line, String message)
+  private void shouldGenerateError(CharSequence code, int line, String message)
       throws UnableToCompleteException {
-    Type logType = TreeLogger.WARN;
     UnitTestTreeLogger.Builder b = new UnitTestTreeLogger.Builder();
-    b.setLowestLogLevel(logType);
+    b.setLowestLogLevel(TreeLogger.ERROR);
     if (message != null) {
-      final String fullMessage = "transient source for Buggy(" + line + "): "
-          + message;
-      b.expect(logType, fullMessage, null);
+      b.expect(TreeLogger.ERROR, "Errors in 'transient source for Buggy'", null);
+      final String fullMessage = "Line " + line + ":  " + message;
+      b.expect(TreeLogger.ERROR, fullMessage, null);
+      b.expect(TreeLogger.ERROR,
+          "Compilation problem due to 'transient source for Buggy'", null);
     }
     UnitTestTreeLogger logger = b.createLogger();
-    TypeOracleTestingUtils.buildTypeOracleForCode("Buggy", code, logger);
+    TypeOracle oracle = buildOracleWithCode(code, logger);
     logger.assertCorrectLogEntries();
+    if (message != null) {
+      assertEquals("Buggy compilation unit not removed from type oracle", null,
+          oracle.findType("Buggy"));
+    }
+  }
+
+  private void shouldGenerateNoError(StringBuffer code)
+      throws UnableToCompleteException {
+    shouldGenerateError(code, -1, null);
   }
 }
diff --git a/user/src/com/google/gwt/user/rebind/rpc/FieldSerializerCreator.java b/user/src/com/google/gwt/user/rebind/rpc/FieldSerializerCreator.java
index b87a6b5..82567eb 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/FieldSerializerCreator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/FieldSerializerCreator.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.user.rebind.rpc;
 
+import com.google.gwt.core.client.UnsafeNativeLong;
 import com.google.gwt.core.ext.GeneratorContext;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.typeinfo.JArrayType;
@@ -23,6 +24,7 @@
 import com.google.gwt.core.ext.typeinfo.JField;
 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.dev.jdt.LongFromJSNIChecker;
 import com.google.gwt.user.client.rpc.SerializationException;
 import com.google.gwt.user.client.rpc.SerializationStreamReader;
 import com.google.gwt.user.client.rpc.SerializationStreamWriter;
@@ -136,11 +138,11 @@
   private void maybeSuppressLongWarnings(JType fieldType) {
     if (fieldType == JPrimitiveType.LONG) {
       /**
-       * Accessing long from JSNI causes a warning, but field serializers need
+       * Accessing long from JSNI causes a error, but field serializers need
        * to be able to do just that in order to bypass java accessibility
        * restrictions.
        */
-      sourceWriter.println("@SuppressWarnings(\"restriction\")");
+      sourceWriter.println("@" + UnsafeNativeLong.class.getName());
     }
   }
 
diff --git a/user/test/com/google/gwt/dev/jjs/test/HostedTest.java b/user/test/com/google/gwt/dev/jjs/test/HostedTest.java
index efe1e2c..56af8ca 100644
--- a/user/test/com/google/gwt/dev/jjs/test/HostedTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/HostedTest.java
@@ -22,6 +22,7 @@
 
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.UnsafeNativeLong;
 import com.google.gwt.junit.client.GWTTestCase;
 
 import java.util.AbstractList;
@@ -160,7 +161,7 @@
     return val;
   }-*/;
 
-  @SuppressWarnings("restriction")
+  @UnsafeNativeLong
   private static native long passThroughLong(long val) /*-{
     return val;
   }-*/;