Make sure native fields are not pruned.

Change-Id: Ia9f6e12933ff5bccbdd51bb9c8f8ad5fbad4f623
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 4d66e8a..262b81f 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
@@ -128,5 +128,7 @@
 
   boolean canBeReferencedExternally();
 
+  boolean canBeImplementedExternally();
+
   boolean isJsInteropEntryPoint();
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java
index ac7fe51..3aa02d0 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JClassType.java
@@ -38,6 +38,9 @@
     }
   }
 
+  public static JClassType NULL_CLASS =
+      new JClassType(SourceOrigin.UNKNOWN, "NullClass", true, true);
+
   private final boolean isAbstract;
   private final boolean isFinal;
   private boolean isJso;
@@ -148,8 +151,18 @@
   protected Object writeReplace() {
     if (isExternal()) {
       return new ExternalSerializedForm(this);
+    } else if (this == NULL_CLASS) {
+      return ExternalSerializedNullClass.INSTANCE;
     } else {
       return this;
     }
   }
+
+  private static class ExternalSerializedNullClass implements Serializable {
+    public static final ExternalSerializedNullClass INSTANCE = new ExternalSerializedNullClass();
+
+    private Object readResolve() {
+      return NULL_CLASS;
+    }
+  }
 }
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 2be07c4..c9ec78c 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
@@ -77,8 +77,8 @@
     }
   }
 
-  public static final JField NULL_FIELD = new JField(SourceOrigin.UNKNOWN, "nullField", null,
-      JReferenceType.NULL_TYPE, false, Disposition.FINAL);
+  public static final JField NULL_FIELD = new JField(SourceOrigin.UNKNOWN, "nullField",
+      JClassType.NULL_CLASS, JReferenceType.NULL_TYPE, false, Disposition.FINAL);
 
   private JsMemberType jsMembertype = JsMemberType.NONE;
   private String jsName;
@@ -162,6 +162,11 @@
   }
 
   @Override
+  public boolean canBeImplementedExternally() {
+    return isJsNative();
+  }
+
+  @Override
   public String getJsNamespace() {
     return jsNamespace == null ? enclosingType.getQualifiedJsName() : jsNamespace;
   }
@@ -208,7 +213,7 @@
   }
 
   public boolean isExternal() {
-    return getEnclosingType() != null && getEnclosingType().isExternal();
+    return getEnclosingType().isExternal();
   }
 
   @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 6d8cf25..cff496e 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
@@ -87,6 +87,7 @@
     return false;
   }
 
+  @Override
   public boolean canBeImplementedExternally() {
     return isJsNative() || isJsFunctionMethod() || isJsInterfaceMethod();
   }
@@ -177,7 +178,7 @@
   }
 
   private boolean isJsFunctionMethod() {
-    return enclosingType != null && enclosingType.isJsFunction();
+    return enclosingType.isJsFunction();
   }
 
   public boolean isOrOverridesJsFunctionMethod() {
@@ -199,7 +200,7 @@
 
   @Override
   public boolean isJsOverlay() {
-    return isJsOverlay || (getEnclosingType() != null && getEnclosingType().isJsoType());
+    return isJsOverlay || getEnclosingType().isJsoType();
   }
 
   public void setSyntheticAccidentalOverride() {
@@ -325,8 +326,9 @@
     }
   }
 
-  public static final JMethod NULL_METHOD = new JMethod(SourceOrigin.UNKNOWN, "nullMethod", null,
-      JReferenceType.NULL_TYPE, false, false, true, AccessModifier.PUBLIC);
+  public static final JMethod NULL_METHOD = new JMethod(SourceOrigin.UNKNOWN, "nullMethod",
+      JClassType.NULL_CLASS, JReferenceType.NULL_TYPE, false, false, true,
+      AccessModifier.PUBLIC);
 
   static {
     NULL_METHOD.setSynthetic();
@@ -585,7 +587,7 @@
   }
 
   public boolean isExternal() {
-    return getEnclosingType() != null && getEnclosingType().isExternal();
+    return getEnclosingType().isExternal();
   }
 
   @Override
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
index 9285ba1..f9fe8fe 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
@@ -260,7 +260,8 @@
   public static boolean isClinit(JMethod method) {
     JDeclaredType enclosingType = method.getEnclosingType();
 
-    boolean isClinit = enclosingType != null && method == enclosingType.getClinitMethod();
+    boolean isClinit = enclosingType != JClassType.NULL_CLASS &&
+        method == enclosingType.getClinitMethod();
     assert !isClinit || method.getName().equals(GwtAstBuilder.CLINIT_METHOD_NAME);
     return isClinit;
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
index f42ba3b..e203666 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
@@ -459,8 +459,9 @@
 
     @Override
     public void exit(JField x, Context ctx) {
-      if (program.codeGenTypes.contains(x.getEnclosingType()) || x.canBeReferencedExternally()) {
-        // We cannot tighten this field as we don't see all references.
+      if (program.codeGenTypes.contains(x.getEnclosingType())
+          || x.canBeReferencedExternally() || x.canBeImplementedExternally()) {
+        // We cannot tighten this field as we don't see all references or the initial value.
         return;
       }
       if (!x.isVolatile()) {
diff --git a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
index a17e052..22dd9a0 100644
--- a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
+++ b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
@@ -20,6 +20,7 @@
 import com.google.gwt.dev.jjs.test.AnnotationsTest;
 import com.google.gwt.dev.jjs.test.ArrayTest;
 import com.google.gwt.dev.jjs.test.AutoboxTest;
+import com.google.gwt.dev.jjs.test.BasicJsInteropTest;
 import com.google.gwt.dev.jjs.test.BlankInterfaceTest;
 import com.google.gwt.dev.jjs.test.ClassCastTest;
 import com.google.gwt.dev.jjs.test.ClassLiteralsTest;
@@ -73,6 +74,7 @@
     suite.addTestSuite(AnnotationsTest.class);
     suite.addTestSuite(ArrayTest.class);
     suite.addTestSuite(AutoboxTest.class);
+    suite.addTestSuite(BasicJsInteropTest.class);
     suite.addTestSuite(BlankInterfaceTest.class);
     suite.addTestSuite(ClassCastTest.class);
     suite.addTestSuite(ClassLiteralsTest.class);
diff --git a/user/test/com/google/gwt/dev/jjs/test/BasicJsInteropTest.java b/user/test/com/google/gwt/dev/jjs/test/BasicJsInteropTest.java
new file mode 100644
index 0000000..22b05a9
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/test/BasicJsInteropTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2015 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.client.GWTTestCase;
+
+import jsinterop.annotations.JsType;
+
+/**
+ * Tests for JsInterop that should work with -nogenerateJsInteropExports.
+ */
+public class BasicJsInteropTest extends GWTTestCase {
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.dev.jjs.CompilerSuite";
+  }
+
+  /**
+   * Tests that a native field that is not written to in Java code (but has some initial value in
+   * the native implementation) is not assumed to be null.
+   */
+  @JsType(isNative = true)
+  static class A {
+    public String field;
+  }
+
+  private native A createA() /*-{
+    return { field: "AA" };
+  }-*/;
+
+  public void testANotPruned() {
+    A a = createA();
+    assertEquals("aa", a.field.toLowerCase());
+  }
+}