Implement UncheckedCast annotation.

Change-Id: I7d3ac459a924082480d687353c4def33267977a4
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JTransformer.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JTransformer.java
index 6478f28..0614d99 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JTransformer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JTransformer.java
@@ -220,7 +220,8 @@
   }
 
   public T missing(JNode x) {
-    throw new InternalCompilerException("Missing transform for " + x);
+    throw new InternalCompilerException(
+        "Missing transform for " + x + " (" + x.getClass().getSimpleName() + ")");
   }
 
   public T transformNullLiteral(JNullLiteral x) {
@@ -339,6 +340,10 @@
     return transformExpression(x);
   }
 
+  public T transformUnsafeTypeCoercion(JUnsafeTypeCoercion x) {
+    return transformExpression(x);
+  }
+
   public T transformWhileStatement(JWhileStatement x) {
     return transformStatement(x);
   }
@@ -1023,6 +1028,12 @@
       return false;
     }
 
+    public final boolean visit(JUnsafeTypeCoercion x, Context ctx) {
+      assert result == null;
+      result = transformUnsafeTypeCoercion(x);
+      return false;
+    }
+
     public final boolean visit(JValueLiteral x, Context ctx) {
       assert result == null;
       result = transformValueLiteral(x);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JUnsafeTypeCoercion.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JUnsafeTypeCoercion.java
new file mode 100644
index 0000000..6832647
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JUnsafeTypeCoercion.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016 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.dev.jjs.ast;
+
+import com.google.gwt.dev.jjs.SourceInfo;
+
+/**
+ * An unsafe type coercion (mostly resulting form the UncheckedCast annotation).
+ */
+public class JUnsafeTypeCoercion extends JExpression {
+
+  private JType coercionType;
+  private JExpression expression;
+
+  public JUnsafeTypeCoercion(SourceInfo info, JType coercionType, JExpression expression) {
+    super(info);
+    this.coercionType = coercionType;
+    this.expression = expression;
+  }
+
+  public JType getCoercionType() {
+    return coercionType;
+  }
+
+  public JExpression getExpression() {
+    return expression;
+  }
+
+  @Override
+  public JType getType() {
+    if (!expression.getType().canBeNull() && !coercionType.isNullType()) {
+      // Strengthen type to non null unless it has been determined that the type is not instantiable
+      // (and that is reflected by replacing the coercion type by the null type).
+      return coercionType.strengthenToNonNull();
+    }
+    return coercionType;
+  }
+
+  @Override
+  public boolean hasSideEffects() {
+    return expression.hasSideEffects();
+  }
+
+  /**
+   * Resolve an external reference during AST stitching.
+   */
+  public void resolve(JType coercionType) {
+    assert coercionType.replaces(this.coercionType);
+    this.coercionType = coercionType;
+  }
+
+  @Override
+  public void traverse(JVisitor visitor, Context ctx) {
+    if (visitor.visit(this, ctx)) {
+      expression = visitor.accept(expression);
+    }
+    visitor.endVisit(this, ctx);
+  }
+
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java
index c83d046..a74785c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java
@@ -477,6 +477,10 @@
     endVisit((JExpression) x, ctx);
   }
 
+  public void endVisit(JUnsafeTypeCoercion x, Context ctx) {
+    endVisit((JExpression) x, ctx);
+  }
+
   public void endVisit(JValueLiteral x, Context ctx) {
     endVisit((JLiteral) x, ctx);
   }
@@ -793,6 +797,10 @@
     return visit((JExpression) x, ctx);
   }
 
+  public boolean visit(JUnsafeTypeCoercion x, Context ctx) {
+    return visit((JExpression) x, ctx);
+  }
+
   public boolean visit(JValueLiteral x, Context ctx) {
     return visit((JLiteral) x, ctx);
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java
index a453256..9dfd509 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CloneExpressionVisitor.java
@@ -47,6 +47,7 @@
 import com.google.gwt.dev.jjs.ast.JRunAsync;
 import com.google.gwt.dev.jjs.ast.JStringLiteral;
 import com.google.gwt.dev.jjs.ast.JThisRef;
+import com.google.gwt.dev.jjs.ast.JUnsafeTypeCoercion;
 import com.google.gwt.dev.jjs.ast.JVisitor;
 import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
 import com.google.gwt.dev.jjs.ast.js.JsniFieldRef;
@@ -299,4 +300,11 @@
     expression = new JThisRef(x.getSourceInfo(), x.getClassType());
     return false;
   }
+
+  @Override
+  public boolean visit(JUnsafeTypeCoercion x, Context ctx) {
+    expression = new JUnsafeTypeCoercion(
+        x.getSourceInfo(), x.getCoercionType(), cloneExpression(x.getExpression()));
+    return false;
+  }
 }
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
index 1299734..cbc44f4 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
@@ -46,6 +46,7 @@
 import com.google.gwt.dev.jjs.ast.JRunAsync;
 import com.google.gwt.dev.jjs.ast.JStringLiteral;
 import com.google.gwt.dev.jjs.ast.JType;
+import com.google.gwt.dev.jjs.ast.JUnsafeTypeCoercion;
 import com.google.gwt.dev.jjs.ast.JVariable;
 import com.google.gwt.dev.jjs.ast.JVariableRef;
 import com.google.gwt.dev.jjs.ast.JVisitor;
@@ -198,30 +199,7 @@
 
     @Override
     public boolean visit(JCastOperation x, Context ctx) {
-      // Rescue any JavaScriptObject type that is the target of a cast.
-      JType targetType = x.getCastType();
-
-      if (!canBeInstantiatedInJavaScript(targetType)) {
-        return true;
-      }
-      rescue((JReferenceType) targetType, true);
-      JType exprType = x.getExpr().getType();
-      if (program.typeOracle.isSingleJsoImpl(targetType)) {
-        /*
-         * It's a JSO interface, check if the source expr can be a live JSO:
-         * 1) source is java.lang.Object (JSO could have been assigned to it)
-         * 2) source is JSO
-         * 3) source is SingleJSO interface whose implementor is live
-         */
-        if (program.getTypeJavaLangObject() == exprType
-            || program.typeOracle.canBeJavaScriptObject(exprType)) {
-          // source is JSO or SingleJso interface whose implementor is live
-          JClassType jsoImplementor =
-              program.typeOracle.getSingleJsoImpl((JReferenceType) targetType);
-          rescue(jsoImplementor, true);
-        }
-      }
-
+      rescueByTypeCoercion(x.getCastType(), x.getExpr().getType());
       return true;
     }
 
@@ -509,6 +487,35 @@
       return true;
     }
 
+    @Override
+    public boolean visit(JUnsafeTypeCoercion x, Context ctx) {
+      rescueByTypeCoercion(x.getCoercionType(), x.getExpression().getType());
+      return true;
+    }
+
+    private void rescueByTypeCoercion(JType targetType, JType expressionType) {
+      // Rescue any JavaScriptObject type that is the target of a cast.
+      if (!canBeInstantiatedInJavaScript(targetType)) {
+        return;
+      }
+      rescue((JReferenceType) targetType, true);
+      if (program.typeOracle.isSingleJsoImpl(targetType)) {
+        /*
+         * It's a JSO interface, check if the source expr can be a live JSO:
+         * 1) source is java.lang.Object (JSO could have been assigned to it)
+         * 2) source is JSO
+         * 3) source is SingleJSO interface whose implementor is live
+         */
+        if (program.getTypeJavaLangObject() == expressionType
+            || program.typeOracle.canBeJavaScriptObject(expressionType)) {
+          // source is JSO or SingleJso interface whose implementor is live
+          JClassType jsoImplementor =
+              program.typeOracle.getSingleJsoImpl((JReferenceType) targetType);
+          rescue(jsoImplementor, true);
+        }
+      }
+    }
+
     private boolean canBeInstantiatedInJavaScript(JType type) {
       // Technically, JsType/JsFunction are also instantiatable in JavaScript but we don't track
       // them using similar to JSO as if we do that then after cast normalization, they got pruned.
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 f78edbb..f96c3d8 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
@@ -92,6 +92,7 @@
 import com.google.gwt.dev.jjs.ast.JTryStatement;
 import com.google.gwt.dev.jjs.ast.JType;
 import com.google.gwt.dev.jjs.ast.JUnaryOperator;
+import com.google.gwt.dev.jjs.ast.JUnsafeTypeCoercion;
 import com.google.gwt.dev.jjs.ast.JVariable;
 import com.google.gwt.dev.jjs.ast.JVisitor;
 import com.google.gwt.dev.jjs.ast.JWhileStatement;
@@ -1153,6 +1154,11 @@
     }
 
     @Override
+    public JsNode transformUnsafeTypeCoercion(JUnsafeTypeCoercion unsafeTypeCoercion) {
+      return transform(unsafeTypeCoercion.getExpression());
+    }
+
+    @Override
     public JsNode transformWhileStatement(JWhileStatement whileStatement) {
       SourceInfo info = whileStatement.getSourceInfo();
       JsWhile stmt = new JsWhile(info);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
index 15d44e7..5930ad1 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
@@ -91,6 +91,7 @@
 import com.google.gwt.dev.jjs.ast.JTryStatement;
 import com.google.gwt.dev.jjs.ast.JType;
 import com.google.gwt.dev.jjs.ast.JUnaryOperator;
+import com.google.gwt.dev.jjs.ast.JUnsafeTypeCoercion;
 import com.google.gwt.dev.jjs.ast.JVariable;
 import com.google.gwt.dev.jjs.ast.JWhileStatement;
 import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
@@ -1541,24 +1542,26 @@
           }
         }
 
-        JMethodCall call = new JMethodCall(info, receiver, method);
+        JMethodCall methodCall = new JMethodCall(info, receiver, method);
 
         // On a super ref, don't allow polymorphic dispatch. Oddly enough,
         // QualifiedSuperReference not derived from SuperReference!
         boolean isSuperRef =
             x.receiver instanceof SuperReference || x.receiver instanceof QualifiedSuperReference;
         if (isSuperRef) {
-          call.setStaticDispatchOnly();
+          methodCall.setStaticDispatchOnly();
         }
 
-        // The arguments come first...
-        call.addArgs(arguments);
+        // The arguments come first.
+        methodCall.addArgs(arguments);
 
         if (x.valueCast != null) {
-          JType castType = typeMap.get(x.valueCast);
-          push(maybeCast(castType, call));
+          JType targetType = typeMap.get(x.valueCast);
+          push(isUncheckedGenericMethodCall(x)
+              ? maybeInsertUnsafeTypeCoersion(targetType, methodCall)
+              : maybeCast(targetType, methodCall));
         } else {
-          push(call);
+          push(methodCall);
         }
       } catch (Throwable e) {
         throw translateException(x, e);
@@ -1568,7 +1571,6 @@
     @Override
     public void endVisit(MethodDeclaration x, ClassScope scope) {
       try {
-
         if (x.isNative()) {
           processNativeMethod(x);
         } else {
@@ -3068,12 +3070,20 @@
 
     private JExpression maybeCast(JType expected, JExpression expression) {
       if (expected != expression.getType()) {
-        // Must be a generic; insert a cast operation.
-        JReferenceType toType = (JReferenceType) expected;
-        return new JCastOperation(expression.getSourceInfo(), toType, expression);
-      } else {
-        return expression;
+        // Must be a generic cast; insert a cast operation.
+        return new JCastOperation(expression.getSourceInfo(), expected, expression);
       }
+
+      return expression;
+    }
+
+      private JExpression maybeInsertUnsafeTypeCoersion(JType expected, JExpression expression) {
+      if (expected != expression.getType()) {
+        // A generic call marked as @UncheckedCast.
+        return new JUnsafeTypeCoercion(expression.getSourceInfo(), expected, expression);
+      }
+
+      return expression;
     }
 
     private JNode pop() {
@@ -4152,7 +4162,15 @@
     x.setSuppressedWarnings(JdtUtil.getSuppressedWarnings(annotations));
   }
 
-  private void maybeSetInliningMode(AbstractMethodDeclaration x, JMethod method) {
+  private static boolean isUncheckedGenericMethodCall(MessageSend messageSend) {
+    if (messageSend.binding.genericMethod() != null) {
+      return JdtUtil.getAnnotation(messageSend.binding.genericMethod(),
+          "javaemul.internal.annotations.UncheckedCast") != null;
+    }
+    return false;
+  }
+
+  private static void maybeSetInliningMode(AbstractMethodDeclaration x, JMethod method) {
     MethodBinding bind = x.binding;
     if (JdtUtil.getAnnotation(bind, "javaemul.internal.annotations.DoNotInline") != null) {
       method.setInliningMode(InliningMode.DO_NOT_INLINE);
@@ -4161,8 +4179,7 @@
     }
   }
 
-  private void maybeSetHasNoSideEffects(AbstractMethodDeclaration x,
-      JMethod method) {
+  private static void maybeSetHasNoSideEffects(AbstractMethodDeclaration x, JMethod method) {
     if (JdtUtil.getAnnotation(x.binding,
         "javaemul.internal.annotations.HasNoSideEffects") != null) {
       method.setHasSideEffects(false);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
index ae9af27..feaf680 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
@@ -67,6 +67,7 @@
 import com.google.gwt.dev.jjs.ast.JThisRef;
 import com.google.gwt.dev.jjs.ast.JTryStatement;
 import com.google.gwt.dev.jjs.ast.JType;
+import com.google.gwt.dev.jjs.ast.JUnsafeTypeCoercion;
 import com.google.gwt.dev.jjs.ast.JVariable;
 import com.google.gwt.dev.jjs.ast.RuntimeConstants;
 import com.google.gwt.dev.jjs.ast.js.JDebuggerStatement;
@@ -361,6 +362,11 @@
     }
 
     @Override
+    public void endVisit(JUnsafeTypeCoercion x, Context ctx) {
+      x.resolve(translate(x.getCoercionType()));
+    }
+
+    @Override
     public void endVisit(JVariable x, Context ctx) {
       x.setType(translate(x.getType()));
     }
diff --git a/user/super/com/google/gwt/emul/javaemul/internal/annotations/UncheckedCast.java b/user/super/com/google/gwt/emul/javaemul/internal/annotations/UncheckedCast.java
new file mode 100644
index 0000000..82c5597
--- /dev/null
+++ b/user/super/com/google/gwt/emul/javaemul/internal/annotations/UncheckedCast.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 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 javaemul.internal.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation to mark a method as unchecked to prevent the compiler from inserting casts on
+ * returns call sites due to erasure.
+ */
+@Target(ElementType.METHOD)
+@CompilerHint
+public @interface UncheckedCast {
+}
diff --git a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
index 3c5fc29..55f0681 100644
--- a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
+++ b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
@@ -39,6 +39,7 @@
 import com.google.gwt.dev.jjs.test.NativeLongTest;
 import com.google.gwt.dev.jjs.test.ObjectIdentityTest;
 import com.google.gwt.dev.jjs.test.SingleJsoImplTest;
+import com.google.gwt.dev.jjs.test.UncheckedCastTest;
 import com.google.gwt.dev.jjs.test.VarargsTest;
 import com.google.gwt.dev.jjs.test.singlejso.TypeHierarchyTest;
 import com.google.gwt.junit.tools.GWTTestSuite;
@@ -81,6 +82,7 @@
     suite.addTestSuite(ObjectIdentityTest.class);
     suite.addTestSuite(SingleJsoImplTest.class);
     suite.addTestSuite(TypeHierarchyTest.class);
+    suite.addTestSuite(UncheckedCastTest.class);
     suite.addTestSuite(VarargsTest.class);
     // $JUnit-END$
 
diff --git a/user/test/com/google/gwt/dev/jjs/OptimizedOnlyCompilerSuite.java b/user/test/com/google/gwt/dev/jjs/OptimizedOnlyCompilerSuite.java
index b95797c..bb5c85a 100644
--- a/user/test/com/google/gwt/dev/jjs/OptimizedOnlyCompilerSuite.java
+++ b/user/test/com/google/gwt/dev/jjs/OptimizedOnlyCompilerSuite.java
@@ -21,6 +21,7 @@
 import com.google.gwt.dev.jjs.optimized.JsOverlayMethodOptimizationTest;
 import com.google.gwt.dev.jjs.optimized.SpecializationTest;
 import com.google.gwt.dev.jjs.optimized.StringOptimizationTest;
+import com.google.gwt.dev.jjs.optimized.UncheckedCastOptimizationTest;
 import com.google.gwt.dev.jjs.test.HasNoSideEffectsTest;
 import com.google.gwt.dev.jjs.test.RunAsyncContentTest;
 import com.google.gwt.junit.tools.GWTTestSuite;
@@ -43,6 +44,7 @@
     suite.addTestSuite(JsOverlayMethodOptimizationTest.class);
     suite.addTestSuite(SpecializationTest.class);
     suite.addTestSuite(HasNoSideEffectsTest.class);
+    suite.addTestSuite(UncheckedCastOptimizationTest.class);
     // RunAsyncContentTest relies in string interning for its assertions which is now always off
     // in non optimzied compiles.
     suite.addTestSuite(RunAsyncContentTest.class);
diff --git a/user/test/com/google/gwt/dev/jjs/optimized/UncheckedCastOptimizationTest.java b/user/test/com/google/gwt/dev/jjs/optimized/UncheckedCastOptimizationTest.java
new file mode 100644
index 0000000..021beed
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/optimized/UncheckedCastOptimizationTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016 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.dev.jjs.optimized;
+
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.Platform;
+
+import javaemul.internal.annotations.UncheckedCast;
+
+import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsType;
+
+/**
+ * Tests that unchecked casts allow other optimizations to happen.
+ */
+@DoNotRunWith(Platform.Devel)
+public class UncheckedCastOptimizationTest extends OptimizationTestBase {
+
+  @JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Array")
+  private static class JsArray {
+    public int length;
+  }
+
+  @JsType(isNative = true)
+  private interface NativeObject {
+  }
+
+  @UncheckedCast
+  public static <T> T uncheckedCast(Object o) {
+    return (T) o;
+  }
+  public static int unckeckedCastOperation() {
+    JsArray array = uncheckedCast(new NativeObject[] {null, null});
+    return array.length;
+  }
+
+  private static native String getGeneratedUncheckedCastFunctionDefinition() /*-{
+    return function() {
+      @com.google.gwt.dev.jjs.optimized.UncheckedCastOptimizationTest::unckeckedCastOperation()();
+    }.toString();
+  }-*/;
+
+  public void testUncheckedCastAllowsOptimizaionsC() throws Exception {
+    String functionDef = getGeneratedUncheckedCastFunctionDefinition();
+    assertFunctionMatches(functionDef, "var<obf>;<obf>=[null,null]");
+  }
+}
diff --git a/user/test/com/google/gwt/dev/jjs/test/UncheckedCastTest.java b/user/test/com/google/gwt/dev/jjs/test/UncheckedCastTest.java
new file mode 100644
index 0000000..bfe873b
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/test/UncheckedCastTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014 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.dev.jjs.test;
+
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.Platform;
+import com.google.gwt.junit.client.GWTTestCase;
+
+import javaemul.internal.annotations.UncheckedCast;
+
+/**
+ * Tests for {@link UncheckedCast}.
+ */
+@DoNotRunWith(Platform.Devel)
+public class UncheckedCastTest extends GWTTestCase {
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.dev.jjs.CompilerSuite";
+  }
+
+  @UncheckedCast
+  private static <T> T uncheckedCast(Object object) {
+    return (T) object;
+  }
+
+  public void testUnsafeCast() {
+    Integer boxedInt = uncheckedCast(new Object());
+    assertNotNull(boxedInt);
+    Double d = uncheckedCast(new Object());
+    assertNotNull(d);
+    boolean unboxedBoolean = uncheckedCast(new Double(12));
+    assertTrue(unboxedBoolean);
+  }
+}