Tracks nullness within the compiler by adding a JNonNull type. Review by: scottb git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7279 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/SizeMapRecorder.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/SizeMapRecorder.java index 19ea629..d1543ef 100644 --- a/dev/core/src/com/google/gwt/core/ext/soyc/impl/SizeMapRecorder.java +++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/SizeMapRecorder.java
@@ -16,8 +16,8 @@ package com.google.gwt.core.ext.soyc.impl; import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.dev.jjs.ast.JClassType; import com.google.gwt.dev.jjs.ast.JMethod; -import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JType; import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap; import com.google.gwt.dev.js.SizeBreakdown; @@ -182,7 +182,7 @@ return new TypedProgramReference("method", desc); } - JReferenceType type = jjsmap.nameToType(name); + JClassType type = jjsmap.nameToType(name); if (type != null) { return new TypedProgramReference("type", type.getName()); }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/CorrelationFactory.java b/dev/core/src/com/google/gwt/dev/jjs/CorrelationFactory.java index 4dd00aa..be0fbfb 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/CorrelationFactory.java +++ b/dev/core/src/com/google/gwt/dev/jjs/CorrelationFactory.java
@@ -17,9 +17,9 @@ import com.google.gwt.dev.jjs.Correlation.Axis; import com.google.gwt.dev.jjs.Correlation.Literal; +import com.google.gwt.dev.jjs.ast.JDeclaredType; import com.google.gwt.dev.jjs.ast.JField; import com.google.gwt.dev.jjs.ast.JMethod; -import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JType; import com.google.gwt.dev.js.ast.JsFunction; import com.google.gwt.dev.js.ast.JsName; @@ -51,7 +51,7 @@ } @Override - public Correlation by(JReferenceType type) { + public Correlation by(JDeclaredType type) { return null; } @@ -155,7 +155,7 @@ } @Override - public Correlation by(JReferenceType type) { + public Correlation by(JDeclaredType type) { Correlation toReturn = canonicalMap.get(type); if (toReturn == null) { toReturn = new Correlation(Axis.CLASS, type.getName(), type); @@ -225,7 +225,7 @@ public abstract Correlation by(JMethod method); - public abstract Correlation by(JReferenceType type); + public abstract Correlation by(JDeclaredType type); public abstract Correlation by(JsFunction function);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java index aef1490..d4fbd85 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -724,7 +724,7 @@ throw new UnableToCompleteException(); } - JMethod entryMethod = findMainMethodRecurse(reboundEntryType); + JMethod entryMethod = findMainMethodRecurse(entryClass); if (entryMethod == null) { logger.log(TreeLogger.ERROR, "Could not find entry method 'onModuleLoad()' method in entry point class '" @@ -738,7 +738,7 @@ + originalMainClassName + "' must not be abstract", null); throw new UnableToCompleteException(); } - SourceInfo sourceInfo = reboundEntryType.getSourceInfo().makeChild( + SourceInfo sourceInfo = entryClass.getSourceInfo().makeChild( JavaToJavaScriptCompiler.class, "Rebound entry point"); JExpression qualifier = null;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/HasSettableType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/HasSettableType.java deleted file mode 100644 index c56737f..0000000 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/HasSettableType.java +++ /dev/null
@@ -1,24 +0,0 @@ -/* - * Copyright 2007 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; - -/** - * Characteristic interface to be overlaid on AST constructs that have a type - * that can be explicitly set. - */ -public interface HasSettableType extends HasType { - void setType(JType newType); -}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JArrayRef.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JArrayRef.java index 8d8e469..e63722f 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JArrayRef.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JArrayRef.java
@@ -22,8 +22,8 @@ */ public class JArrayRef extends JExpression { - private JExpression instance; private JExpression indexExpr; + private JExpression instance; public JArrayRef(SourceInfo info, JExpression instance, JExpression indexExpr) { super(info); @@ -31,6 +31,14 @@ this.indexExpr = indexExpr; } + public JArrayType getArrayType() { + JType type = instance.getType(); + if (type instanceof JNullType) { + return null; + } + return (JArrayType) ((JReferenceType) type).getUnderlyingType(); + } + public JExpression getIndexExpr() { return indexExpr; } @@ -40,12 +48,9 @@ } public JType getType() { - JType type = instance.getType(); - if (type instanceof JNullType) { - return JNullType.INSTANCE; - } - JArrayType arrayType = (JArrayType) type; - return arrayType.getElementType(); + JArrayType arrayType = getArrayType(); + return (arrayType == null) ? JNullType.INSTANCE + : arrayType.getElementType(); } public boolean hasSideEffects() {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JBinaryOperation.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JBinaryOperation.java index 54db51d..61af84f 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JBinaryOperation.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JBinaryOperation.java
@@ -20,7 +20,7 @@ /** * Binary operator expression. */ -public class JBinaryOperation extends JExpression implements HasSettableType { +public class JBinaryOperation extends JExpression { private JExpression lhs; private final JBinaryOperator op;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JConditional.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JConditional.java index f474f25..b8f4dff 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JConditional.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JConditional.java
@@ -20,7 +20,7 @@ /** * Conditional expression. */ -public class JConditional extends JExpression implements HasSettableType { +public class JConditional extends JExpression { private JExpression elseExpr; private JExpression ifTest;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JGwtCreate.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JGwtCreate.java index cb46c35..69ebac2 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JGwtCreate.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JGwtCreate.java
@@ -25,7 +25,7 @@ * finalized. Replaced with the entry call for the appropriate rebind result in * that permutation. */ -public class JGwtCreate extends JExpression implements HasSettableType { +public class JGwtCreate extends JExpression { public static JExpression createInstantiationExpression(SourceInfo info, JClassType classType) { @@ -46,7 +46,8 @@ return null; } // Call it, using a new expression as a qualifier - JNewInstance newInstance = new JNewInstance(info, classType); + JNewInstance newInstance = new JNewInstance(info, + (JNonNullType) noArgCtor.getType()); return new JMethodCall(info, newInstance, noArgCtor); }
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 0a4a833..e2cb5de 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
@@ -31,7 +31,7 @@ * A Java method implementation. */ public final class JMethod extends JNode implements HasEnclosingType, HasName, - HasSettableType, CanBeAbstract, CanBeSetFinal, CanBeNative, CanBeStatic { + HasType, CanBeAbstract, CanBeSetFinal, CanBeNative, CanBeStatic { private static final String TRACE_METHOD_WILDCARD = "*";
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JNewArray.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JNewArray.java index 24de34a..5df5e87 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JNewArray.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JNewArray.java
@@ -23,7 +23,7 @@ /** * New array expression. */ -public class JNewArray extends JExpression implements HasSettableType { +public class JNewArray extends JExpression { public static JNewArray createDims(JProgram program, SourceInfo info, JArrayType arrayType, List<JExpression> dims) { @@ -45,39 +45,40 @@ classLiterals.add(classLit); cur = ((JArrayType) cur).getElementType(); } - return new JNewArray(info, arrayType, dims, null, classLiterals); + return new JNewArray(info, program.getNonNullType(arrayType), dims, null, + classLiterals); } public static JNewArray createInitializers(JProgram program, SourceInfo info, JArrayType arrayType, List<JExpression> initializers) { List<JClassLiteral> classLiterals = new ArrayList<JClassLiteral>(); classLiterals.add(program.getLiteralClass(arrayType)); - return new JNewArray(info, arrayType, null, initializers, classLiterals); + return new JNewArray(info, program.getNonNullType(arrayType), null, + initializers, classLiterals); } public final List<JExpression> dims; public final List<JExpression> initializers; - private JArrayType arrayType; + private JNonNullType type; /** * The list of class literals that will be needed to support this expression. */ private final List<JClassLiteral> classLiterals; - public JNewArray(SourceInfo info, JArrayType arrayType, - List<JExpression> dims, List<JExpression> initializers, - List<JClassLiteral> classLits) { + public JNewArray(SourceInfo info, JNonNullType type, List<JExpression> dims, + List<JExpression> initializers, List<JClassLiteral> classLits) { super(info); - this.arrayType = arrayType; + setType(type); this.dims = dims; this.initializers = initializers; this.classLiterals = classLits; } public JArrayType getArrayType() { - return arrayType; + return (JArrayType) type.getUnderlyingType(); } /** @@ -98,8 +99,8 @@ return classLiterals; } - public JType getType() { - return arrayType; + public JNonNullType getType() { + return type; } @Override @@ -122,8 +123,9 @@ return false; } - public void setType(JType arrayType) { - this.arrayType = (JArrayType) arrayType; + public void setType(JNonNullType type) { + assert type.getUnderlyingType() instanceof JArrayType; + this.type = type; } public void traverse(JVisitor visitor, Context ctx) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JNewInstance.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JNewInstance.java index f8a11a1..eeea366 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JNewInstance.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JNewInstance.java
@@ -24,21 +24,23 @@ */ public class JNewInstance extends JExpression { - private final JClassType classType; + private final JNonNullType type; - public JNewInstance(SourceInfo info, JClassType classType) { + public JNewInstance(SourceInfo info, JNonNullType type) { super(info); - this.classType = classType; + assert type.getUnderlyingType() instanceof JClassType; + this.type = type; } public JClassType getClassType() { - return classType; + return (JClassType) type.getUnderlyingType(); } - public JType getType() { - return classType; + public JNonNullType getType() { + return type; } + @Override public boolean hasSideEffects() { // The actual new operation itself has no side effects (see class comment). return false;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JNonNullType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JNonNullType.java new file mode 100644 index 0000000..1d19196 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JNonNullType.java
@@ -0,0 +1,70 @@ +/* + * 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.dev.jjs.ast; + +import com.google.gwt.dev.jjs.InternalCompilerException; + +/** + * A type including all the values in some other type except for + * <code>null</code>. + */ +public class JNonNullType extends JReferenceType { + + private final JReferenceType ref; + + JNonNullType(JReferenceType ref) { + super(ref.getSourceInfo(), ref.getName()); + assert ref.canBeNull(); + this.ref = ref; + } + + @Override + public boolean canBeNull() { + return false; + } + + @Override + public String getClassLiteralFactoryMethod() { + return ref.getClassLiteralFactoryMethod(); + } + + @Override + public JClassType getSuperClass() { + return ref.getSuperClass(); + } + + @Override + public JReferenceType getUnderlyingType() { + return ref; + } + + public boolean isAbstract() { + return ref.isAbstract(); + } + + public boolean isFinal() { + return ref.isFinal(); + } + + @Override + public void setSuperClass(JClassType superClass) { + throw new InternalCompilerException("should not be called"); + } + + public void traverse(JVisitor visitor, Context ctx) { + visitor.accept(ref); + } +}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JNullType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JNullType.java index 2a2c513..cac95fb 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JNullType.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JNullType.java
@@ -36,10 +36,12 @@ "Cannot get class literal for null type"); } + @Override public String getJavahSignatureName() { return "N"; } + @Override public String getJsniSignatureName() { return "N"; } @@ -52,6 +54,11 @@ return true; } + @Override + public void setSuperClass(JClassType superClass) { + throw new InternalCompilerException("should not be called"); + } + public void traverse(JVisitor visitor, Context ctx) { if (visitor.visit(this, ctx)) { }
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 c6bd9f6..df289af 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
@@ -283,6 +283,8 @@ private List<JsonObject> jsonTypeTable; + private Map<JReferenceType, JNonNullType> nonNullTypes = new IdentityHashMap<JReferenceType, JNonNullType>(); + private JField nullField; private JMethod nullMethod; @@ -317,6 +319,8 @@ private final Map<String, JDeclaredType> typeNameMap = new HashMap<String, JDeclaredType>(); + private JNonNullType typeNonNullString; + private JClassType typeSpecialClassLiteralHolder; private JClassType typeSpecialJavaScriptObject; @@ -397,6 +401,7 @@ typeJavaLangObject = x; } else if (sname.equals("java.lang.String")) { typeString = x; + typeNonNullString = getNonNullType(x); } else if (sname.equals("java.lang.Enum")) { typeJavaLangEnum = x; } else if (sname.equals("java.lang.Class")) { @@ -544,6 +549,10 @@ return createSourceInfo(0, caller.getName()).makeChild(caller, description); } + /** + * Return the least upper bound of a set of types. That is, the smallest type + * that is a supertype of all the input types. + */ public JReferenceType generalizeTypes( Collection<? extends JReferenceType> types) { assert (types != null); @@ -556,12 +565,31 @@ return curType; } + /** + * Return the least upper bound of two types. That is, the smallest type that + * is a supertype of both types. + */ public JReferenceType generalizeTypes(JReferenceType type1, JReferenceType type2) { if (type1 == type2) { return type1; } + if (type1 instanceof JNonNullType && type2 instanceof JNonNullType) { + // Neither can be null. + type1 = type1.getUnderlyingType(); + type2 = type2.getUnderlyingType(); + return getNonNullType(generalizeTypes(type1, type2)); + } else if (type1 instanceof JNonNullType) { + // type2 can be null, so the result can be null + type1 = type1.getUnderlyingType(); + } else if (type2 instanceof JNonNullType) { + // type1 can be null, so the result can be null + type2 = type2.getUnderlyingType(); + } + assert !(type1 instanceof JNonNullType); + assert !(type2 instanceof JNonNullType); + int classify1 = classifyType(type1); int classify2 = classifyType(type2); @@ -679,8 +707,8 @@ int lesser = Math.min(classify1, classify2); int greater = Math.max(classify1, classify2); - JReferenceType tLesser = classify1 > classify2 ? type1 : type2; - JReferenceType tGreater = classify1 < classify2 ? type1 : type2; + JReferenceType tLesser = classify1 < classify2 ? type1 : type2; + JReferenceType tGreater = classify1 > classify2 ? type1 : type2; if (lesser == IS_INTERFACE && greater == IS_CLASS) { @@ -730,7 +758,7 @@ } public JThisRef getExprThisRef(SourceInfo info, JClassType enclosingType) { - return new JThisRef(info, enclosingType); + return new JThisRef(info, getNonNullType(enclosingType)); } public int getFragmentCount() { @@ -884,13 +912,25 @@ JStringLiteral toReturn = stringLiteralMap.get(s); if (toReturn == null) { toReturn = new JStringLiteral(stringPoolSourceInfo.makeChild( - JProgram.class, "String literal: " + s), s, getTypeJavaLangString()); + JProgram.class, "String literal: " + s), s, typeNonNullString); stringLiteralMap.put(s, toReturn); } toReturn.getSourceInfo().merge(sourceInfo); return toReturn; } + public JNonNullType getNonNullType(JReferenceType type) { + if (type instanceof JNonNullType) { + return (JNonNullType) type; + } + JNonNullType nonNullType = nonNullTypes.get(type); + if (nonNullType == null) { + nonNullType = new JNonNullType(type); + nonNullTypes.put(type, nonNullType); + } + return nonNullType; + } + public JField getNullField() { if (nullField == null) { nullField = new JField(createSourceInfoSynthetic(JProgram.class, @@ -910,6 +950,7 @@ } public int getQueryId(JReferenceType elementType) { + assert (elementType == getRunTimeType(elementType)); Integer integer = queryIds.get(elementType); if (integer == null) { return 0; @@ -922,6 +963,25 @@ return runAsyncReplacements; } + /** + * A run-time type is a type at the granularity that GWT tests at run time. + * These include declared types, arrays of declared types, arrays of + * primitives, and null. This is also the granularity for the notion of + * instantiability recorded in {@link JTypeOracle}. This method returns the + * narrowest supertype of <code>type</code> that is a run-time type. + */ + public JReferenceType getRunTimeType(JReferenceType type) { + type = type.getUnderlyingType(); + if (type instanceof JArrayType) { + JArrayType typeArray = (JArrayType) type; + if (typeArray.getLeafType() instanceof JNonNullType) { + JNonNullType leafType = (JNonNullType) typeArray.getLeafType(); + type = getTypeArray(leafType.getUnderlyingType(), typeArray.getDims()); + } + } + return type; + } + public List<Integer> getSplitPointInitialSequence() { return splitPointInitialSequence; } @@ -931,6 +991,7 @@ } public JArrayType getTypeArray(JType leafType, int dimensions) { + assert (!(leafType instanceof JArrayType)); HashMap<JType, JArrayType> typeToArrayType; // Create typeToArrayType maps for index slots that don't exist yet. @@ -1014,6 +1075,7 @@ } public int getTypeId(JReferenceType referenceType) { + assert (referenceType == getRunTimeType(referenceType)); Integer integer = typeIdMap.get(referenceType); if (integer == null) { return 0; @@ -1086,9 +1148,13 @@ this.jsonTypeTable = jsonObjects; } + public boolean isJavaLangString(JType type) { + return type == typeString || type == typeNonNullString; + } + public boolean isJavaScriptObject(JType type) { - if (type instanceof JClassType && typeSpecialJavaScriptObject != null) { - return typeOracle.canTriviallyCast((JClassType) type, + if (type instanceof JReferenceType && typeSpecialJavaScriptObject != null) { + return typeOracle.canTriviallyCast((JReferenceType) type, typeSpecialJavaScriptObject); } return false; @@ -1145,11 +1211,20 @@ return staticToInstanceMap.get(method); } + /** + * Return the greatest lower bound of two types. That is, return the largest + * type that is a subtype of both inputs. + */ public JReferenceType strongerType(JReferenceType type1, JReferenceType type2) { if (type1 == type2) { return type1; } + if (type1 instanceof JNonNullType != type2 instanceof JNonNullType) { + // If either is non-nullable, the result should be non-nullable. + return strongerType(getNonNullType(type1), getNonNullType(type2)); + } + if (typeOracle.canTriviallyCast(type1, type2)) { return type1; } @@ -1171,6 +1246,7 @@ } private int classifyType(JReferenceType type) { + assert !(type instanceof JNonNullType); if (type instanceof JNullType) { return IS_NULL; } else if (type instanceof JInterfaceType) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JReboundEntryPoint.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JReboundEntryPoint.java index 41d6045..c93e3ec 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JReboundEntryPoint.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JReboundEntryPoint.java
@@ -28,9 +28,9 @@ private final List<JExpression> entryCalls; private final List<JClassType> resultTypes; - private final JReferenceType sourceType; + private final JDeclaredType sourceType; - public JReboundEntryPoint(SourceInfo info, JReferenceType sourceType, + public JReboundEntryPoint(SourceInfo info, JDeclaredType sourceType, List<JClassType> resultTypes, List<JExpression> entryCalls) { super(info); this.sourceType = sourceType; @@ -46,7 +46,7 @@ return resultTypes; } - public JReferenceType getSourceType() { + public JDeclaredType getSourceType() { return sourceType; }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JReferenceType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JReferenceType.java index e70f1d7..db07d97 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JReferenceType.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JReferenceType.java
@@ -31,6 +31,16 @@ super(info, name, JNullLiteral.INSTANCE); } + /** + * Returns <code>true</code> if it's possible for this type to be + * <code>null</code>. + * + * @see JNonNullType + */ + public boolean canBeNull() { + return true; + } + @Override public String getJavahSignatureName() { return "L" + name.replaceAll("_", "_1").replace('.', '_') + "_2"; @@ -55,6 +65,13 @@ } /** + * If this type is a non-null type, returns the underlying (original) type. + */ + public JReferenceType getUnderlyingType() { + return this; + } + + /** * Sets this type's super class. */ public void setSuperClass(JClassType superClass) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JStringLiteral.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JStringLiteral.java index 9197a91..e38cd41 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JStringLiteral.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JStringLiteral.java
@@ -22,13 +22,13 @@ */ public class JStringLiteral extends JValueLiteral { - private final JClassType stringType; + private final JNonNullType stringType; private final String value; /** * These are only supposed to be constructed by JProgram. */ - JStringLiteral(SourceInfo sourceInfo, String value, JClassType stringType) { + JStringLiteral(SourceInfo sourceInfo, String value, JNonNullType stringType) { super(sourceInfo); this.value = value; this.stringType = stringType;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java index d24101c..6f58f3b 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java
@@ -22,19 +22,20 @@ */ public class JThisRef extends JExpression { - private final JClassType classType; + private final JNonNullType type; - public JThisRef(SourceInfo info, JClassType classType) { + public JThisRef(SourceInfo info, JNonNullType type) { super(info); - this.classType = classType; + assert type.getUnderlyingType() instanceof JClassType; + this.type = type; } public JClassType getClassType() { - return classType; + return (JClassType) type.getUnderlyingType(); } public JType getType() { - return classType; + return type; } public boolean hasSideEffects() {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java index 1310630..ebd7a0b 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
@@ -177,35 +177,6 @@ } /** - * Determine whether a type is instantiated, given an assumed list of - * instantiated types. - * - * @param type any type - * @param instantiatedTypes a set of types assumed to be instantiated. If - * <code>null</code>, then there are no assumptions about which types - * are instantiated. - * @return whether the type is instantiated - */ - private static boolean isInstantiatedType(JReferenceType type, - Set<JReferenceType> instantiatedTypes) { - if (instantiatedTypes == null) { - return true; - } - - if (type instanceof JNullType) { - return true; - } - - if (type instanceof JArrayType) { - JArrayType arrayType = (JArrayType) type; - if (arrayType.getLeafType() instanceof JNullType) { - return true; - } - } - return instantiatedTypes.contains(type); - } - - /** * A map of all interfaces to the set of classes that could theoretically * implement them. */ @@ -229,6 +200,11 @@ */ private final Map<JClassType, Set<JInterfaceType>> implementsMap = new IdentityHashMap<JClassType, Set<JInterfaceType>>(); + /** + * The types in the program that are instantiable. All types in this set + * should be run-time types as defined at + * {@link JProgram#getRunTimeType(JReferenceType)}. + */ private Set<JReferenceType> instantiatedTypes = null; /** @@ -291,6 +267,15 @@ } public boolean canTheoreticallyCast(JReferenceType type, JReferenceType qType) { + if (!type.canBeNull() && qType == program.getTypeNull()) { + // Cannot cast non-nullable to null + return false; + } + + // Compare the underlying types. + type = type.getUnderlyingType(); + qType = qType.getUnderlyingType(); + JClassType jlo = program.getTypeJavaLangObject(); if (type == qType || type == jlo) { return true; @@ -346,6 +331,15 @@ } public boolean canTriviallyCast(JReferenceType type, JReferenceType qType) { + if (type.canBeNull() && !qType.canBeNull()) { + // Cannot reliably cast nullable to non-nullable + return false; + } + + // Compare the underlying types. + type = type.getUnderlyingType(); + qType = qType.getUnderlyingType(); + JClassType jlo = program.getTypeJavaLangObject(); if (type == qType || qType == jlo) { return true; @@ -419,8 +413,7 @@ jsoSingleImpls.clear(); dualImpls.clear(); - for (int i = 0; i < program.getDeclaredTypes().size(); ++i) { - JReferenceType type = program.getDeclaredTypes().get(i); + for (JDeclaredType type : program.getDeclaredTypes()) { if (type instanceof JClassType) { recordSuperSubInfo((JClassType) type); } else { @@ -451,20 +444,17 @@ } } - for (int i = 0; i < program.getDeclaredTypes().size(); ++i) { - JReferenceType type = program.getDeclaredTypes().get(i); + for (JDeclaredType type : program.getDeclaredTypes()) { if (type instanceof JClassType) { computeImplements((JClassType) type); } } - for (int i = 0; i < program.getDeclaredTypes().size(); ++i) { - JReferenceType type = program.getDeclaredTypes().get(i); + for (JDeclaredType type : program.getDeclaredTypes()) { if (type instanceof JClassType) { computeCouldImplement((JClassType) type); } } - for (int i = 0; i < program.getDeclaredTypes().size(); ++i) { - JReferenceType type = program.getDeclaredTypes().get(i); + for (JDeclaredType type : program.getDeclaredTypes()) { if (type instanceof JClassType) { computeVirtualUpRefs((JClassType) type); } @@ -538,12 +528,12 @@ return results; } - public Set<JInterfaceType> getInterfacesWithJavaAndJsoImpls() { - return Collections.unmodifiableSet(dualImpls); + public JClassType getSingleJsoImpl(JReferenceType maybeSingleJsoIntf) { + return jsoSingleImpls.get(maybeSingleJsoIntf.getUnderlyingType()); } - public Map<JInterfaceType, JClassType> getSingleJsoImpls() { - return Collections.unmodifiableMap(jsoSingleImpls); + public boolean isDualJsoInterface(JReferenceType maybeDualImpl) { + return dualImpls.contains(maybeDualImpl.getUnderlyingType()); } public boolean isInstantiatedType(JReferenceType type) { @@ -815,6 +805,37 @@ return get(implementsMap, type).contains(qType); } + /** + * Determine whether a type is instantiated, given an assumed list of + * instantiated types. + * + * @param type any type + * @param instantiatedTypes a set of types assumed to be instantiated. If + * <code>null</code>, then there are no assumptions about which types + * are instantiated. + * @return whether the type is instantiated + */ + private boolean isInstantiatedType(JReferenceType type, + Set<JReferenceType> instantiatedTypes) { + type = program.getRunTimeType(type); + + if (instantiatedTypes == null) { + return true; + } + + if (type instanceof JNullType) { + return true; + } + + if (type instanceof JArrayType) { + JArrayType arrayType = (JArrayType) type; + if (arrayType.getLeafType() instanceof JNullType) { + return true; + } + } + return instantiatedTypes.contains(type); + } + private boolean isSameOrSuper(JClassType type, JClassType qType) { return (type == qType || isSuperClass(type, qType)); }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JVariable.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JVariable.java index f9d46f2..8184366 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JVariable.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JVariable.java
@@ -21,7 +21,7 @@ * Base class for any storage location. */ public abstract class JVariable extends JNode implements CanBeSetFinal, - CanHaveInitializer, HasName, HasSettableType { + CanHaveInitializer, HasName, HasType { protected JDeclarationStatement declStmt = null; private boolean isFinal;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsniFieldRef.java b/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsniFieldRef.java index a6b46b8..0accf75 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsniFieldRef.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/js/JsniFieldRef.java
@@ -29,7 +29,7 @@ public class JsniFieldRef extends JFieldRef { private final String ident; - private boolean isLvalue; + private final boolean isLvalue; public JsniFieldRef(SourceInfo info, String ident, JField field, JDeclaredType enclosingType, boolean isLvalue) { @@ -47,6 +47,7 @@ return isLvalue; } + @Override public void traverse(JVisitor visitor, Context ctx) { if (visitor.visit(this, ctx)) { }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java index a1e2292..14f1f2a 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java
@@ -49,12 +49,11 @@ public void endVisit(JBinaryOperation x, Context ctx) { if (x.getOp() == JBinaryOperator.ASG && x.getLhs() instanceof JArrayRef) { JArrayRef arrayRef = (JArrayRef) x.getLhs(); - if (arrayRef.getType() instanceof JNullType) { + JType elementType = arrayRef.getType(); + if (elementType instanceof JNullType) { // will generate a null pointer exception instead return; } - JArrayType arrayType = (JArrayType) arrayRef.getInstance().getType(); - JType elementType = arrayType.getElementType(); /* * See if we need to do a checked store. Primitives and (effectively) @@ -62,7 +61,9 @@ */ if (elementType instanceof JReferenceType) { if (!((JReferenceType) elementType).isFinal() - || elementType != x.getRhs().getType()) { + || !program.typeOracle.canTriviallyCast( + (JReferenceType) x.getRhs().getType(), + (JReferenceType) elementType)) { // replace this assignment with a call to setCheck() JMethodCall call = new JMethodCall(x.getSourceInfo(), null, setCheckMethod); @@ -137,10 +138,14 @@ SourceInfo sourceInfo = x.getSourceInfo().makeChild(ArrayVisitor.class, "Creating dimensions"); JMethodCall call = new JMethodCall(sourceInfo, null, initDims, arrayType); - JsonArray classLitList = new JsonArray(sourceInfo, program.getJavaScriptObject()); - JsonArray typeIdList = new JsonArray(sourceInfo, program.getJavaScriptObject()); - JsonArray queryIdList = new JsonArray(sourceInfo, program.getJavaScriptObject()); - JsonArray dimList = new JsonArray(sourceInfo, program.getJavaScriptObject()); + JsonArray classLitList = new JsonArray(sourceInfo, + program.getJavaScriptObject()); + JsonArray typeIdList = new JsonArray(sourceInfo, + program.getJavaScriptObject()); + JsonArray queryIdList = new JsonArray(sourceInfo, + program.getJavaScriptObject()); + JsonArray dimList = new JsonArray(sourceInfo, + program.getJavaScriptObject()); JType cur = arrayType; for (int i = 0; i < dims; ++i) { // Walk down each type from most dims to least. @@ -168,11 +173,13 @@ // override the type of the called method with the array's type SourceInfo sourceInfo = x.getSourceInfo().makeChild(ArrayVisitor.class, "Array initializer"); - JMethodCall call = new JMethodCall(sourceInfo, null, initValues, arrayType); + JMethodCall call = new JMethodCall(sourceInfo, null, initValues, + arrayType); JLiteral classLit = x.getClassLiteral(); JLiteral typeIdLit = program.getLiteralInt(program.getTypeId(arrayType)); JLiteral queryIdLit = program.getLiteralInt(tryGetQueryId(arrayType)); - JsonArray initList = new JsonArray(sourceInfo, program.getJavaScriptObject()); + JsonArray initList = new JsonArray(sourceInfo, + program.getJavaScriptObject()); for (int i = 0; i < x.initializers.size(); ++i) { initList.exprs.add(x.initializers.get(i)); } @@ -184,7 +191,7 @@ JType elementType = type.getElementType(); int leafTypeId = -1; if (elementType instanceof JReferenceType) { - leafTypeId = program.getQueryId((JReferenceType) elementType); + leafTypeId = program.getQueryId(program.getRunTimeType((JReferenceType) elementType)); } return leafTypeId; }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java index 81b3b9c..55f54ea 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
@@ -29,6 +29,7 @@ import com.google.gwt.dev.jjs.ast.JMethodBody; import com.google.gwt.dev.jjs.ast.JMethodCall; import com.google.gwt.dev.jjs.ast.JNewInstance; +import com.google.gwt.dev.jjs.ast.JNonNullType; import com.google.gwt.dev.jjs.ast.JParameter; import com.google.gwt.dev.jjs.ast.JParameterRef; import com.google.gwt.dev.jjs.ast.JPrimitiveType; @@ -168,8 +169,8 @@ String name = enclosingType.getShortName(); SourceInfo info = makeSourceInfo(ctorDecl, enclosingType); JMethod newMethod = program.createMethod(info, name.toCharArray(), - enclosingType, enclosingType, false, false, true, b.isPrivate(), - false); + enclosingType, program.getNonNullType(enclosingType), false, false, + true, b.isPrivate(), false); // Enums have hidden arguments for name and value if (enclosingType.isEnumOrSubclass() != null) { @@ -400,12 +401,13 @@ // Define the method JMethod synthetic = program.createMethod(type.getSourceInfo().makeChild( BuildDeclMapVisitor.class, "Synthetic constructor"), - "new".toCharArray(), type, type, false, true, true, false, false); + "new".toCharArray(), type, program.getNonNullType(type), false, true, + true, false, false); // new Foo() : Create the instance JNewInstance newInstance = new JNewInstance( type.getSourceInfo().makeChild(BuildDeclMapVisitor.class, - "new instance"), type); + "new instance"), (JNonNullType) synthetic.getType()); // (new Foo()).Foo() : Invoke the constructor method on the instance JMethodCall call = new JMethodCall(type.getSourceInfo().makeChild(
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java index 4296607..dc0a041 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
@@ -71,7 +71,6 @@ * </p> */ public class CastNormalizer { - private class AssignTypeIdsVisitor extends JVisitor { Set<JReferenceType> alreadyRan = new HashSet<JReferenceType>(); @@ -147,17 +146,17 @@ */ @Override public void endVisit(JBinaryOperation x, Context ctx) { - if (x.getOp() == JBinaryOperator.ASG && x.getLhs() instanceof JArrayRef) { + if (x.getOp().isAssignment() && x.getLhs() instanceof JArrayRef) { // first, calculate the transitive closure of all possible runtime types // the lhs could be - JExpression instance = ((JArrayRef) x.getLhs()).getInstance(); - if (instance.getType() instanceof JNullType) { + JArrayRef lhsArrayRef = (JArrayRef) x.getLhs(); + JType elementType = lhsArrayRef.getType(); + if (elementType instanceof JNullType) { // will generate a null pointer exception instead return; } - JArrayType lhsArrayType = (JArrayType) instance.getType(); - JType elementType = lhsArrayType.getElementType(); + JArrayType lhsArrayType = lhsArrayRef.getArrayType(); // primitives are statically correct if (!(elementType instanceof JReferenceType)) { @@ -183,7 +182,7 @@ if (typeOracle.canTheoreticallyCast(arrayType, lhsArrayType)) { JType itElementType = arrayType.getElementType(); if (itElementType instanceof JReferenceType) { - recordCastInternal((JReferenceType) itElementType, refRhsType); + recordCast(itElementType, x.getRhs()); } } } @@ -214,6 +213,7 @@ if (type == null || alreadyRan.contains(type)) { return; } + assert (type == program.getRunTimeType(type)); alreadyRan.add(type); @@ -288,14 +288,13 @@ private void recordCast(JType targetType, JExpression rhs) { if (targetType instanceof JReferenceType) { + targetType = program.getRunTimeType((JReferenceType) targetType); // unconditional cast b/c it would've been a semantic error earlier - JReferenceType rhsType = (JReferenceType) rhs.getType(); + JReferenceType rhsType = program.getRunTimeType((JReferenceType) rhs.getType()); // don't record a type for trivial casts that won't generate code - if (rhsType instanceof JClassType) { - if (program.typeOracle.canTriviallyCast(rhsType, - (JReferenceType) targetType)) { - return; - } + if (program.typeOracle.canTriviallyCast(rhsType, + (JReferenceType) targetType)) { + return; } // If the target type is a JavaScriptObject, don't record an id. @@ -307,9 +306,10 @@ } } - private void recordCastInternal(JReferenceType targetType, + private void recordCastInternal(JReferenceType toType, JReferenceType rhsType) { - JReferenceType toType = targetType; + toType = program.getRunTimeType(toType); + rhsType = program.getRunTimeType(rhsType); Set<JReferenceType> querySet = queriedTypes.get(toType); if (querySet == null) { queryIds.put(toType, nextQueryId++); @@ -434,7 +434,7 @@ replaceExpr = call; } else if (toType instanceof JReferenceType) { JExpression curExpr = expr; - JReferenceType refType = (JReferenceType) toType; + JReferenceType refType = program.getRunTimeType((JReferenceType) toType); JReferenceType argType = (JReferenceType) expr.getType(); if (program.typeOracle.canTriviallyCast(argType, refType)) { // just remove the cast @@ -442,11 +442,11 @@ } else { JMethod method; - boolean isJsoCast = program.isJavaScriptObject(toType); + boolean isJsoCast = program.isJavaScriptObject(refType); if (isJsoCast) { // A cast to a concrete JSO subtype method = program.getIndexedMethod("Cast.dynamicCastJso"); - } else if (program.typeOracle.getSingleJsoImpls().containsKey(toType)) { + } else if (program.typeOracle.isDualJsoInterface(refType)) { // An interface that should succeed when the object is a JSO method = program.getIndexedMethod("Cast.dynamicCastAllowJso"); } else { @@ -549,6 +549,8 @@ public void endVisit(JInstanceOf x, Context ctx) { JReferenceType argType = (JReferenceType) x.getExpr().getType(); JReferenceType toType = x.getTestType(); + // Only tests on run-time types are supported + assert (toType == program.getRunTimeType(toType)); if (program.typeOracle.canTriviallyCast(argType, toType)) { // trivially true if non-null; replace with a null test JNullLiteral nullLit = program.getLiteralNull(); @@ -559,7 +561,7 @@ } else { JMethod method; boolean isJsoCast = false; - if (program.typeOracle.getSingleJsoImpls().containsKey(toType)) { + if (program.typeOracle.getSingleJsoImpl(toType) != null) { method = program.getIndexedMethod("Cast.instanceOfOrJso"); } else if (program.isJavaScriptObject(toType)) { isJsoCast = true;
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 5c7de38..6bcc205 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
@@ -242,7 +242,7 @@ @Override public boolean visit(JNewArray x, Context ctx) { - expression = new JNewArray(x.getSourceInfo(), x.getArrayType(), + expression = new JNewArray(x.getSourceInfo(), x.getType(), cloneExpressions(x.dims), cloneExpressions(x.initializers), x.getClassLiterals()); return false; @@ -250,7 +250,7 @@ @Override public boolean visit(JNewInstance x, Context ctx) { - expression = new JNewInstance(x.getSourceInfo(), x.getClassType()); + expression = new JNewInstance(x.getSourceInfo(), x.getType()); return false; }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java index 0c0d090..0095722 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
@@ -167,7 +167,7 @@ public Map<JField, Integer> fields = new HashMap<JField, Integer>(); public Map<JMethod, Integer> methods = new HashMap<JMethod, Integer>(); public Map<String, Integer> strings = new HashMap<String, Integer>(); - public Map<JReferenceType, Integer> types = new HashMap<JReferenceType, Integer>(); + public Map<JDeclaredType, Integer> types = new HashMap<JDeclaredType, Integer>(); } /** @@ -184,6 +184,10 @@ this.fragment = fragment; } + public boolean isLive(JDeclaredType type) { + return checkMap(fragmentMap.types, type); + } + public boolean isLive(JField field) { return checkMap(fragmentMap.fields, field); } @@ -192,10 +196,6 @@ return checkMap(fragmentMap.methods, method); } - public boolean isLive(JReferenceType type) { - return checkMap(fragmentMap.types, type); - } - public boolean isLive(String literal) { return checkMap(fragmentMap.strings, literal); } @@ -458,6 +458,19 @@ return cfa; } + /** + * Extract the types from a set that happen to be declared types. + */ + private static Set<JDeclaredType> declaredTypesIn(Set<JReferenceType> types) { + Set<JDeclaredType> result = new HashSet<JDeclaredType>(); + for (JReferenceType type : types) { + if (type instanceof JDeclaredType) { + result.add((JDeclaredType) type); + } + } + return result; + } + private static String fullNameString(JField field) { return field.getEnclosingType().getName() + "." + field.getName(); } @@ -487,8 +500,8 @@ private static void installInitialLoadSequenceField(JProgram program, LinkedHashSet<Integer> initialLoadSequence) { JMethodCall constructorCall = ReplaceRunAsyncs.getBrowserLoaderConstructor(program); - assert constructorCall.getArgs().get(1).getType() instanceof JArrayType; - assert ((JArrayType) constructorCall.getArgs().get(1).getType()).getElementType() == JPrimitiveType.INT; + assert ((JReferenceType) constructorCall.getArgs().get(1).getType()).getUnderlyingType() instanceof JArrayType; + assert ((JArrayType) ((JReferenceType) constructorCall.getArgs().get(1).getType()).getUnderlyingType()).getElementType() == JPrimitiveType.INT; SourceInfo info = program.createSourceInfoSynthetic(ReplaceRunAsyncs.class, "array with initial load sequence"); @@ -600,7 +613,6 @@ } private final MultipleDependencyGraphRecorder dependencyRecorder; - private final Map<JField, JClassLiteral> fieldToLiteralOfClass; private final FragmentExtractor fragmentExtractor; private final LinkedHashSet<Integer> initialLoadSequence; @@ -933,12 +945,12 @@ private void fixUpLoadOrderDependenciesForTypes(ExclusivityMap fragmentMap) { int numFixups = 0; - Queue<JReferenceType> typesToCheck = new ArrayBlockingQueue<JReferenceType>( + Queue<JDeclaredType> typesToCheck = new ArrayBlockingQueue<JDeclaredType>( jprogram.getDeclaredTypes().size()); typesToCheck.addAll(jprogram.getDeclaredTypes()); while (!typesToCheck.isEmpty()) { - JReferenceType type = typesToCheck.remove(); + JDeclaredType type = typesToCheck.remove(); if (type.getSuperClass() != null) { int typeFrag = getOrZero(fragmentMap.types, type); int supertypeFrag = getOrZero(fragmentMap.types, type.getSuperClass()); @@ -995,8 +1007,9 @@ allButOne.getLiveFieldsAndMethods(), allMethods); updateMap(entry, fragmentMap.strings, allButOne.getLiveStrings(), everything.getLiveStrings()); - updateMap(entry, fragmentMap.types, allButOne.getInstantiatedTypes(), - everything.getInstantiatedTypes()); + updateMap(entry, fragmentMap.types, + declaredTypesIn(allButOne.getInstantiatedTypes()), + declaredTypesIn(everything.getInstantiatedTypes())); } }
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 f62bba9..6abc9de 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
@@ -464,8 +464,7 @@ */ private void maybeRescueJavaScriptObjectPassingIntoJava(JType type) { boolean doIt = false; - if (program.isJavaScriptObject(type) - || type == program.getTypeJavaLangString()) { + if (program.isJavaScriptObject(type) || program.isJavaLangString(type)) { doIt = true; } else if (type instanceof JArrayType) { /* @@ -475,7 +474,7 @@ JArrayType arrayType = (JArrayType) type; JType elementType = arrayType.getElementType(); if (elementType instanceof JPrimitiveType - || elementType == program.getTypeJavaLangString() + || program.isJavaLangString(elementType) || program.isJavaScriptObject(elementType)) { doIt = true; } @@ -527,32 +526,39 @@ private void rescue(JReferenceType type, boolean isReferenced, boolean isInstantiated) { - if (type != null) { + if (type == null) { + return; + } - boolean doVisit = false; - if (isInstantiated && !instantiatedTypes.contains(type)) { - instantiatedTypes.add(type); - doVisit = true; - } + /* + * Track references and instantiability at the granularity of run-time + * types. For example, ignore nullness. + */ + type = program.getRunTimeType(type); - if (isReferenced && !referencedTypes.contains(type)) { - referencedTypes.add(type); - doVisit = true; - } + boolean doVisit = false; + if (isInstantiated && !instantiatedTypes.contains(type)) { + instantiatedTypes.add(type); + doVisit = true; + } - if (doVisit) { - accept(type); + if (isReferenced && !referencedTypes.contains(type)) { + referencedTypes.add(type); + doVisit = true; + } - if (type instanceof JDeclaredType) { - for (JNode artificial : ((JDeclaredType) type).getArtificialRescues()) { - if (artificial instanceof JReferenceType) { - rescue((JReferenceType) artificial, true, true); - rescue(program.getLiteralClass((JReferenceType) artificial).getField()); - } else if (artificial instanceof JVariable) { - rescue((JVariable) artificial); - } else if (artificial instanceof JMethod) { - rescue((JMethod) artificial); - } + if (doVisit) { + accept(type); + + if (type instanceof JDeclaredType) { + for (JNode artificial : ((JDeclaredType) type).getArtificialRescues()) { + if (artificial instanceof JReferenceType) { + rescue((JReferenceType) artificial, true, true); + rescue(program.getLiteralClass((JReferenceType) artificial).getField()); + } else if (artificial instanceof JVariable) { + rescue((JVariable) artificial); + } else if (artificial instanceof JMethod) { + rescue((JMethod) artificial); } } } @@ -602,10 +608,11 @@ * Handle special rescues needed implicitly to support concat. */ private void rescueByConcat(JType type) { - JClassType stringType = program.getTypeJavaLangString(); JPrimitiveType charType = program.getTypePrimitiveChar(); - if (type instanceof JReferenceType && type != stringType - && type != program.getTypeNull()) { + JClassType stringType = program.getTypeJavaLangString(); + if (type instanceof JReferenceType + && !program.typeOracle.canTriviallyCast((JReferenceType) type, + stringType) && type != program.getTypeNull()) { /* * Any reference types (except String, which works by default) that take * part in a concat must rescue java.lang.Object.toString(). @@ -789,7 +796,7 @@ } } - public void traverseFromReferenceTo(JReferenceType type) { + public void traverseFromReferenceTo(JDeclaredType type) { rescuer.rescue(type, true, false); }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java index 90505e7..b39364c 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
@@ -1161,11 +1161,7 @@ } private boolean isTypeString(JExpression exp) { - return isTypeString(exp.getType()); - } - - private boolean isTypeString(JType type) { - return type == program.getTypeJavaLangString(); + return program.isJavaLangString(exp.getType()); } private boolean isUnconditionalBreak(JStatement statement) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/EqualityNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/EqualityNormalizer.java index 83578f5..fdff227 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/EqualityNormalizer.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/EqualityNormalizer.java
@@ -28,37 +28,36 @@ import com.google.gwt.dev.jjs.ast.JType; /** - * Handle all of the cases where Java reference identity causes problems in - * JavaScript due to the <code>null === undefined</code> problem. Reference - * identity checks will generally fall into one of two cases right now. - * * <p> - * If something that may be a String is compared to something that may not be a - * <code>String</code>, we must use the <code>===</code> to prevent - * JavaScript compare-as-strings behavior. However, this invites the - * <code>null === undefined</code> problem, so we must emit calls to mask off - * <code>undefined</code> as <code>null</code>. These cases include: + * Rewrite Java <code>==</code> so that it will execute correctly in JavaScript. + * After this pass, Java's <code>==</code> is considered equivalent to + * JavaScript's <code>===</code>. + *</p> + *<p> + * Whenever possible, a Java <code>==</code> is replaced by a JavaScript + * <code>==</code>. This is shorter than <code>===</code>, and it avoids any + * complication due to GWT treating both <code>null</code> and + * <code>undefined</code> as a valid translation of a Java <code>null</code>. + * </p> + * <p> + * However, whenever something that may be a String is compared to something + * that may not be a <code>String</code>, use <code>===</code>. A Java object + * compared to a string should always yield false, but that's not true when + * comparing in JavaScript using <code>==</code>. The cases where + * <code>===</code> must be used are: * </p> * <ul> * <li>One or both sides have unknown <code>String</code> status.</li> - * <li>One side is definitely <code>String</code> and one side is definitely !<code>String</code>. - * <br/>TODO: This case could be optimized as - * <code>(a == null) & (b == null)</code>. </li> + * <li>One side is definitely <code>String</code> and one side is definitely ! + * <code>String</code>. <br/> + * TODO: This case could be optimized as + * <code>(a == null) & (b == null)</code>.</li> * </ul> - * * <p> - * Otherwise, we can use the <code>==</code> operator to avoid having to mask - * of <code>undefined</code>. These cases include: + * Since <code>null !== undefined</code>, it is also necessary to normalize + * <code>null</code> vs. <code>undefined</code> if it's possible for one side to + * be <code>null</code> and the other to be <code>undefined</code>. * </p> - * <ul> - * <li>Comparing two things statically typed as <code>String</code>.</li> - * <li>Comparing two things statically typed as !<code>String</code>.</li> - * <li>Comparing anything to something that is definitely <code>null</code>.</li> - * </ul> - * - * TODO: There will be a third case when we can identity things that are - * definitely not <code>null</code>. The presence of a definitely not null - * operand allows us to use <code>===</code> with impunity. */ public class EqualityNormalizer { @@ -84,45 +83,60 @@ StringStatus lhsStatus = getStringStatus((JReferenceType) lhsType); StringStatus rhsStatus = getStringStatus((JReferenceType) rhsType); + int strat = COMPARISON_STRAT[lhsStatus.getIndex()][rhsStatus.getIndex()]; - if ((USE_TRIPLE_EQUALS[lhsStatus.getIndex()][rhsStatus.getIndex()] == 1)) { - // Mask each side to prevent null === undefined. - lhs = maskUndefined(lhs); - rhs = maskUndefined(rhs); - JBinaryOperation binOp = new JBinaryOperation(x.getSourceInfo(), - x.getType(), x.getOp(), lhs, rhs); - ctx.replaceMe(binOp); - } else { - boolean lhsNullLit = lhs == program.getLiteralNull(); - boolean rhsNullLit = rhs == program.getLiteralNull(); - if ((lhsNullLit && rhsStatus == StringStatus.NOTSTRING) - || (rhsNullLit && lhsStatus == StringStatus.NOTSTRING)) { - /* - * If either side is a null literal and the other is non-String, - * replace with a null-check. - */ - String methodName; - if (op == JBinaryOperator.EQ) { - methodName = "Cast.isNull"; - } else { - methodName = "Cast.isNotNull"; + switch (strat) { + case STRAT_TRIPLE: { + if (canBeNull(lhs) && canBeNull(rhs)) { + /* + * If it's possible for one side to be null and the other side + * undefined, then mask both sides. + */ + lhs = maskUndefined(lhs); + rhs = maskUndefined(rhs); } - JMethod isNullMethod = program.getIndexedMethod(methodName); - JMethodCall call = new JMethodCall(x.getSourceInfo(), null, isNullMethod); - call.addArg(lhsNullLit ? rhs : lhs); - ctx.replaceMe(call); - } else { - // Replace with a call to Cast.jsEquals, which does a == internally. - String methodName; - if (op == JBinaryOperator.EQ) { - methodName = "Cast.jsEquals"; + + JBinaryOperation binOp = new JBinaryOperation(x.getSourceInfo(), + x.getType(), x.getOp(), lhs, rhs); + ctx.replaceMe(binOp); + break; + } + + case STRAT_DOUBLE: { + boolean lhsNullLit = lhs == program.getLiteralNull(); + boolean rhsNullLit = rhs == program.getLiteralNull(); + if ((lhsNullLit && rhsStatus == StringStatus.NOTSTRING) + || (rhsNullLit && lhsStatus == StringStatus.NOTSTRING)) { + /* + * If either side is a null literal and the other is non-String, + * replace with a null-check. + */ + String methodName; + if (op == JBinaryOperator.EQ) { + methodName = "Cast.isNull"; + } else { + methodName = "Cast.isNotNull"; + } + JMethod isNullMethod = program.getIndexedMethod(methodName); + JMethodCall call = new JMethodCall(x.getSourceInfo(), null, + isNullMethod); + call.addArg(lhsNullLit ? rhs : lhs); + ctx.replaceMe(call); } else { - methodName = "Cast.jsNotEquals"; + // Replace with a call to Cast.jsEquals, which does a == internally. + String methodName; + if (op == JBinaryOperator.EQ) { + methodName = "Cast.jsEquals"; + } else { + methodName = "Cast.jsNotEquals"; + } + JMethod eqMethod = program.getIndexedMethod(methodName); + JMethodCall call = new JMethodCall(x.getSourceInfo(), null, + eqMethod); + call.addArgs(lhs, rhs); + ctx.replaceMe(call); } - JMethod eqMethod = program.getIndexedMethod(methodName); - JMethodCall call = new JMethodCall(x.getSourceInfo(), null, eqMethod); - call.addArgs(lhs, rhs); - ctx.replaceMe(call); + break; } } } @@ -141,9 +155,11 @@ } private JExpression maskUndefined(JExpression lhs) { + assert ((JReferenceType) lhs.getType()).canBeNull(); + JMethod maskMethod = program.getIndexedMethod("Cast.maskUndefined"); - JMethodCall lhsCall = new JMethodCall(lhs.getSourceInfo(), null, maskMethod, - lhs.getType()); + JMethodCall lhsCall = new JMethodCall(lhs.getSourceInfo(), null, + maskMethod, lhs.getType()); lhsCall.addArg(lhs); return lhsCall; } @@ -152,8 +168,6 @@ /** * Represents what we know about an operand type in terms of its type and * <code>null</code> status. - * - * TODO: represent definitely non-null things. */ private enum StringStatus { NOTSTRING(2), NULL(3), STRING(1), UNKNOWN(0); @@ -170,20 +184,34 @@ } /** - * A map of the combinations where <code>===</code> should be used. + * A map of the combinations where each comparison strategy should be used. */ - private static int[][] USE_TRIPLE_EQUALS = { + private static int[][] COMPARISON_STRAT = { // ..U..S.!S..N - {1, 1, 1, 0}, // UNKNOWN - {1, 0, 1, 0}, // STRING - {1, 1, 0, 0}, // NOTSTRING - {0, 0, 0, 0}, // NULL + {1, 1, 1, 0,}, // UNKNOWN + {1, 0, 1, 0,}, // STRING + {1, 1, 0, 0,}, // NOTSTRING + {0, 0, 0, 0,}, // NULL }; + /** + * The comparison strategy of using ==. + */ + private static final int STRAT_DOUBLE = 0; + + /** + * The comparison strategy of using ===. + */ + private static final int STRAT_TRIPLE = 1; + public static void exec(JProgram program) { new EqualityNormalizer(program).execImpl(); } + private static boolean canBeNull(JExpression x) { + return ((JReferenceType) x.getType()).canBeNull(); + } + private final JProgram program; private EqualityNormalizer(JProgram program) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java index 5ac8a58..fbe8748 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java
@@ -16,10 +16,11 @@ package com.google.gwt.dev.jjs.impl; import com.google.gwt.dev.jjs.SourceInfo; +import com.google.gwt.dev.jjs.ast.JClassType; +import com.google.gwt.dev.jjs.ast.JDeclaredType; import com.google.gwt.dev.jjs.ast.JField; import com.google.gwt.dev.jjs.ast.JMethod; import com.google.gwt.dev.jjs.ast.JProgram; -import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.js.ast.JsBinaryOperation; import com.google.gwt.dev.js.ast.JsBinaryOperator; import com.google.gwt.dev.js.ast.JsEmpty; @@ -65,7 +66,7 @@ return cfa.getLiveFieldsAndMethods().contains(method); } - public boolean isLive(JReferenceType type) { + public boolean isLive(JDeclaredType type) { return cfa.getInstantiatedTypes().contains(type); } @@ -107,7 +108,7 @@ boolean isLive(JMethod method); - boolean isLive(JReferenceType type); + boolean isLive(JDeclaredType type); boolean isLive(String literal); @@ -133,7 +134,7 @@ return false; } - public boolean isLive(JReferenceType type) { + public boolean isLive(JDeclaredType type) { return false; } @@ -258,7 +259,7 @@ /** * The type whose vtables can currently be installed. */ - JReferenceType currentVtableType = null; + JClassType currentVtableType = null; for (int frag = 0; frag < jsprogram.getFragmentCount(); frag++) { List<JsStatement> stats = jsprogram.getFragmentBlock(frag).getStatements(); @@ -284,7 +285,7 @@ if (vtableTypeAssigned(stat) != null) { currentVtableType = vtableTypeAssigned(stat); } - JReferenceType vtableType = vtableTypeNeeded(stat); + JClassType vtableType = vtableTypeNeeded(stat); if (vtableType != null && vtableType != currentVtableType) { extractedStats.add(vtableStatFor(vtableType)); currentVtableType = vtableType; @@ -379,7 +380,7 @@ } private boolean isLive(JsStatement stat, LivenessPredicate livenessPredicate) { - JReferenceType type = map.typeForStatement(stat); + JClassType type = map.typeForStatement(stat); if (type != null) { // This is part of the code only needed once a type is instantiable return livenessPredicate.isLive(type); @@ -478,7 +479,7 @@ * <code>_ = foo.prototype</code>, where <code>foo</code> is the constructor * function for <code>vtableType</code>. */ - private JsStatement vtableStatFor(JReferenceType vtableType) { + private JsStatement vtableStatFor(JClassType vtableType) { JsNameRef prototypeField = new JsNameRef( jsprogram.createSourceInfoSynthetic(FragmentExtractor.class, "prototype field"), "prototype"); @@ -506,10 +507,8 @@ /** * If <code>state</code> is of the form <code>_ = Foo.prototype = exp</code>, * then return <code>Foo</code>. Otherwise return <code>null</code>. - * - * TODO(spoon): get this information via source info on the statement */ - private JReferenceType vtableTypeAssigned(JsStatement stat) { + private JClassType vtableTypeAssigned(JsStatement stat) { if (!(stat instanceof JsExprStmt)) { return null; } @@ -552,11 +551,11 @@ return map.typeForStatement(stat); } - private JReferenceType vtableTypeNeeded(JsStatement stat) { + private JClassType vtableTypeNeeded(JsStatement stat) { JMethod meth = map.vtableInitToMethod(stat); if (meth != null) { if (!meth.isStatic()) { - return meth.getEnclosingType(); + return (JClassType) meth.getEnclosingType(); } } return null;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java index ace3d27..5e2add6 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
@@ -66,6 +66,7 @@ import com.google.gwt.dev.jjs.ast.JNewArray; import com.google.gwt.dev.jjs.ast.JNewInstance; import com.google.gwt.dev.jjs.ast.JNode; +import com.google.gwt.dev.jjs.ast.JNonNullType; import com.google.gwt.dev.jjs.ast.JParameter; import com.google.gwt.dev.jjs.ast.JParameterRef; import com.google.gwt.dev.jjs.ast.JPostfixOperation; @@ -790,7 +791,8 @@ } call = new JMethodCall(makeSourceInfo(x), null, targetMethod); } else { - JNewInstance newInstance = new JNewInstance(info, newType); + JNewInstance newInstance = new JNewInstance(info, + (JNonNullType) ctor.getType()); call = new JMethodCall(info, newInstance, ctor); } @@ -904,7 +906,7 @@ op = JBinaryOperator.SHRU; break; case BinaryExpression.PLUS: - if (typeMap.get(x.resolvedType) == program.getTypeJavaLangString()) { + if (program.isJavaLangString((JType) typeMap.get(x.resolvedType))) { op = JBinaryOperator.CONCAT; } else { op = JBinaryOperator.ADD; @@ -975,7 +977,7 @@ switch (x.operator) { case CompoundAssignment.PLUS: - if (typeMap.get(x.resolvedType) == program.getTypeJavaLangString()) { + if (program.isJavaLangString((JType) typeMap.get(x.resolvedType))) { op = JBinaryOperator.ASG_CONCAT; } else { op = JBinaryOperator.ASG_ADD; @@ -1214,8 +1216,8 @@ SourceInfo info = makeSourceInfo(x); MethodBinding b = x.binding; JMethod ctor = (JMethod) typeMap.get(b); - JClassType enclosingType = (JClassType) ctor.getEnclosingType(); - JNewInstance newInstance = new JNewInstance(info, enclosingType); + JNewInstance newInstance = new JNewInstance(info, + (JNonNullType) ctor.getType()); JMethodCall call = new JMethodCall(info, newInstance, ctor); JExpression qualifier = dispProcessExpression(x.enclosingInstance); List<JExpression> qualList = new ArrayList<JExpression>(); @@ -2155,7 +2157,7 @@ * world would we need to do this? It turns out that when making an * unqualified explicit super constructor call to something that needs a * synthetic outer this arg, the correct value to pass in can be one of - * several of the calling constructor's own synthetic ags. The catch is, + * several of the calling constructor's own synthetic args. The catch is, * it's possible none of the args are exactly the right type-- we have to * make one of them the right type by following each of their synthetic this * refs up an arbitrarily big tree of enclosing classes and @@ -2167,6 +2169,8 @@ * prefer the current expression as one of its supertypes over a synthetic * this ref rooted off the current expression that happens to be the correct * type. We have observed this to be consistent with how Java handles it. + * + * TODO(scottb): could we get this info directly from JDT? */ private JExpression createThisRef(JReferenceType qualType, List<JExpression> list) { @@ -2174,7 +2178,7 @@ workList.addAll(list); while (!workList.isEmpty()) { JExpression expr = workList.removeFirst(); - JClassType classType = (JClassType) expr.getType(); + JClassType classType = (JClassType) ((JReferenceType) expr.getType()).getUnderlyingType(); for (; classType != null; classType = classType.getSuperClass()) { // prefer myself or myself-as-supertype over any of my this$ fields // that may have already been added to the work list @@ -2226,7 +2230,7 @@ JClassType fieldEnclosingType = (JClassType) field.getEnclosingType(); instance = createThisRef(info, fieldEnclosingType); if (!program.typeOracle.canTriviallyCast( - (JClassType) instance.getType(), fieldEnclosingType)) { + (JReferenceType) instance.getType(), fieldEnclosingType)) { throw new InternalCompilerException( "FieldRef referencing field in a different type."); } @@ -2344,8 +2348,8 @@ * expression. Beware that when autoboxing, the type of the expression is * not necessarily the same as the type of the box to be created. The JDT * figures out what the necessary conversion is, depending on the context - * the expression appears in, and stores it in <code>x.implicitConversion</code>, - * so extract it from there. + * the expression appears in, and stores it in + * <code>x.implicitConversion</code>, so extract it from there. */ private JPrimitiveType implicitConversionTargetType(Expression x) throws InternalCompilerException { @@ -2862,8 +2866,7 @@ JLiteral initializer = field.getConstInitializer(); JType type = initializer.getType(); - if (type instanceof JPrimitiveType - || type == program.getTypeJavaLangString()) { + if (type instanceof JPrimitiveType || program.isJavaLangString(type)) { GenerateJavaScriptLiterals generator = new GenerateJavaScriptLiterals( jsProgram); generator.accept(initializer); @@ -2882,10 +2885,9 @@ private void processMethod(JsNameRef nameRef, SourceInfo info, JMethod method, JsContext<JsExpression> ctx) { - JReferenceType enclosingType = method.getEnclosingType(); + JDeclaredType enclosingType = method.getEnclosingType(); if (enclosingType != null) { - JClassType jsoImplType = program.typeOracle.getSingleJsoImpls().get( - enclosingType); + JClassType jsoImplType = program.typeOracle.getSingleJsoImpl(enclosingType); if (jsoImplType != null) { JsniCollector.reportJsniError(info, methodDecl, "Illegal reference to method '" + method.getName()
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 a107a0d..7bc960f 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
@@ -1086,7 +1086,7 @@ @Override public void endVisit(JNewInstance x, Context ctx) { JsNew newOp = new JsNew(x.getSourceInfo()); - JsNameRef nameRef = names.get(x.getType()).makeRef(x.getSourceInfo()); + JsNameRef nameRef = names.get(x.getClassType()).makeRef(x.getSourceInfo()); newOp.setConstructorExpression(nameRef); push(newOp); } @@ -1949,7 +1949,7 @@ * Because we modify String's prototype, all fields and polymorphic methods on * String's super types need special handling. */ - private final Set<JReferenceType> specialObfuscatedTypes = new HashSet<JReferenceType>(); + private final Set<JDeclaredType> specialObfuscatedTypes = new HashSet<JDeclaredType>(); /** * Maps JsNames to machine-usable identifiers. @@ -1961,7 +1961,7 @@ */ private final JsScope topScope; - private final Map<JsStatement, JReferenceType> typeForStatMap = new HashMap<JsStatement, JReferenceType>(); + private final Map<JsStatement, JClassType> typeForStatMap = new HashMap<JsStatement, JClassType>(); private final JTypeOracle typeOracle; @@ -2121,11 +2121,11 @@ generator.accept(program); final Map<JsName, JMethod> nameToMethodMap = new HashMap<JsName, JMethod>(); final HashMap<JsName, JField> nameToFieldMap = new HashMap<JsName, JField>(); - final HashMap<JsName, JReferenceType> constructorNameToTypeMap = new HashMap<JsName, JReferenceType>(); + final HashMap<JsName, JClassType> constructorNameToTypeMap = new HashMap<JsName, JClassType>(); for (JDeclaredType type : program.getDeclaredTypes()) { JsName typeName = names.get(type); - if (typeName != null) { - constructorNameToTypeMap.put(typeName, type); + if (type instanceof JClassType && typeName != null) { + constructorNameToTypeMap.put(typeName, (JClassType) type); } for (JField field : type.getFields()) { if (field.isStatic()) { @@ -2152,7 +2152,7 @@ return names.get(method); } - public JsName nameForType(JReferenceType type) { + public JsName nameForType(JClassType type) { return names.get(type); } @@ -2164,11 +2164,11 @@ return nameToMethodMap.get(name); } - public JReferenceType nameToType(JsName name) { + public JClassType nameToType(JsName name) { return constructorNameToTypeMap.get(name); } - public JReferenceType typeForStatement(JsStatement stat) { + public JClassType typeForStatement(JsStatement stat) { return typeForStatMap.get(stat); }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaScriptObjectNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JavaScriptObjectNormalizer.java index 0760216..1eb02f0 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaScriptObjectNormalizer.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JavaScriptObjectNormalizer.java
@@ -22,10 +22,10 @@ import com.google.gwt.dev.jjs.ast.JClassLiteral; import com.google.gwt.dev.jjs.ast.JClassType; import com.google.gwt.dev.jjs.ast.JConditional; +import com.google.gwt.dev.jjs.ast.JDeclaredType; import com.google.gwt.dev.jjs.ast.JExpression; import com.google.gwt.dev.jjs.ast.JField; import com.google.gwt.dev.jjs.ast.JInstanceOf; -import com.google.gwt.dev.jjs.ast.JInterfaceType; import com.google.gwt.dev.jjs.ast.JLocal; import com.google.gwt.dev.jjs.ast.JLocalRef; import com.google.gwt.dev.jjs.ast.JMethod; @@ -33,14 +33,13 @@ import com.google.gwt.dev.jjs.ast.JMethodCall; import com.google.gwt.dev.jjs.ast.JModVisitor; import com.google.gwt.dev.jjs.ast.JNewArray; +import com.google.gwt.dev.jjs.ast.JNonNullType; import com.google.gwt.dev.jjs.ast.JParameter; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JType; import com.google.gwt.dev.jjs.ast.js.JMultiExpression; -import java.util.Map; -import java.util.Set; import java.util.Stack; /** @@ -110,8 +109,8 @@ */ @Override public void endVisit(JMethodCall x, Context ctx) { - JType targetClass = x.getTarget().getEnclosingType(); - if (jsoSingleImpls.containsKey(targetClass)) { + JDeclaredType targetClass = x.getTarget().getEnclosingType(); + if (program.typeOracle.getSingleJsoImpl(targetClass) != null) { SourceInfo info = x.getSourceInfo().makeChild( JavaScriptObjectNormalizer.class, @@ -121,7 +120,7 @@ JMethod jsoMethod = findJsoMethod(x.getTarget()); assert jsoMethod != null; - if (dualImpls.contains(targetClass)) { + if (program.typeOracle.isDualJsoInterface(targetClass)) { /* * This is the special-case code to handle interfaces. */ @@ -163,7 +162,7 @@ @Override public void endVisit(JNewArray x, Context ctx) { - x.setType(translate(x.getType())); + x.setType((JNonNullType) translate(x.getType())); } @Override @@ -198,7 +197,7 @@ } private JMethod findJsoMethod(JMethod interfaceMethod) { - JClassType jsoClass = jsoSingleImpls.get(interfaceMethod.getEnclosingType()); + JClassType jsoClass = program.typeOracle.getSingleJsoImpl(interfaceMethod.getEnclosingType()); assert program.isJavaScriptObject(jsoClass); assert jsoClass != null; @@ -245,22 +244,28 @@ } private JType translate(JType type) { - if (program.isJavaScriptObject(type)) { - return program.getJavaScriptObject(); + if (!(type instanceof JReferenceType)) { + return type; + } + JReferenceType refType = (JReferenceType) type; + boolean canBeNull = refType.canBeNull(); + refType = refType.getUnderlyingType(); - } else if (jsoSingleImpls.containsKey(type) && !dualImpls.contains(type)) { + if (program.isJavaScriptObject(refType)) { + refType = program.getJavaScriptObject(); + } else if (program.typeOracle.getSingleJsoImpl(refType) != null + && !program.typeOracle.isDualJsoInterface(refType)) { // Optimization: narrow to JSO if it's not a dual impl. - return program.getJavaScriptObject(); - - } else if (type instanceof JArrayType) { - JArrayType arrayType = (JArrayType) type; + refType = program.getJavaScriptObject(); + } else if (refType instanceof JArrayType) { + JArrayType arrayType = (JArrayType) refType; JType leafType = arrayType.getLeafType(); JType replacement = translate(leafType); if (leafType != replacement) { - return program.getTypeArray(replacement, arrayType.getDims()); + refType = program.getTypeArray(replacement, arrayType.getDims()); } } - return type; + return canBeNull ? refType : program.getNonNullType(refType); } } @@ -268,22 +273,10 @@ new JavaScriptObjectNormalizer(program).execImpl(); } - /** - * Interfaces implemented both by a JSO type and a regular Java type. - */ - private final Set<JInterfaceType> dualImpls; - - /** - * Maps SingleJsoImpl interfaces onto the single JSO implementation. - */ - private final Map<JInterfaceType, JClassType> jsoSingleImpls; - private final JProgram program; private JavaScriptObjectNormalizer(JProgram program) { this.program = program; - dualImpls = program.typeOracle.getInterfacesWithJavaAndJsoImpls(); - jsoSingleImpls = program.typeOracle.getSingleJsoImpls(); } private void execImpl() {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaToJavaScriptMap.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JavaToJavaScriptMap.java index 3a232b1..89aace3 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaToJavaScriptMap.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JavaToJavaScriptMap.java
@@ -15,9 +15,9 @@ */ package com.google.gwt.dev.jjs.impl; +import com.google.gwt.dev.jjs.ast.JClassType; import com.google.gwt.dev.jjs.ast.JField; import com.google.gwt.dev.jjs.ast.JMethod; -import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.js.ast.JsName; import com.google.gwt.dev.js.ast.JsStatement; @@ -33,7 +33,7 @@ /** * Return the JavaScript name corresponding to a Java type. */ - JsName nameForType(JReferenceType type); + JsName nameForType(JClassType type); /** * If <code>name</code> is the name of a @@ -52,13 +52,13 @@ * If <code>name</code> is the name of a constructor function corresponding to * a Java type, then return that type. Otherwise, return <code>null</code>. */ - JReferenceType nameToType(JsName name); + JClassType nameToType(JsName name); /** * If <code>stat</code> is used to set up the definition of some class, return * that class. Otherwise, return null. */ - JReferenceType typeForStatement(JsStatement stat); + JClassType typeForStatement(JsStatement stat); /** * If <code>stat</code> is used to set up a vtable entry for a method, then
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/LongCastNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/LongCastNormalizer.java index d5929ac..9e71d88 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/LongCastNormalizer.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/LongCastNormalizer.java
@@ -59,7 +59,7 @@ JType resultType = x.getType(); JBinaryOperator op = x.getOp(); - if (resultType == program.getTypeJavaLangString()) { + if (program.isJavaLangString(resultType)) { // Don't mess with concat. return; } @@ -123,8 +123,8 @@ if (init != null) { init = checkAndReplace(init, x.getVariableRef().getType()); if (init != x.getInitializer()) { - JDeclarationStatement newStmt = new JDeclarationStatement(x.getSourceInfo(), - x.getVariableRef(), init); + JDeclarationStatement newStmt = new JDeclarationStatement( + x.getSourceInfo(), x.getVariableRef(), init); ctx.replaceMe(newStmt); } }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/LongEmulationNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/LongEmulationNormalizer.java index d35dbc9..4a00988 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/LongEmulationNormalizer.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/LongEmulationNormalizer.java
@@ -50,7 +50,7 @@ @Override public void endVisit(JBinaryOperation x, Context ctx) { // Concats are handled by CastNormalizer.ConcatVisitor. - if (x.getType() == program.getTypeJavaLangString()) { + if (program.isJavaLangString(x.getType())) { return; } JType lhsType = x.getLhs().getType();
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 1fe417a..d0bf13f 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
@@ -159,7 +159,7 @@ // Setup parameters; map from the old params to the new params JParameter thisParam = JParameter.create(sourceInfo.makeChild( CreateStaticImplsVisitor.class, "Instance parameter"), "this$static", - enclosingType, true, true, newMethod); + program.getNonNullType(enclosingType), true, true, newMethod); Map<JParameter, JParameter> varMap = new IdentityHashMap<JParameter, JParameter>(); for (int i = 0; i < x.getParams().size(); ++i) { JParameter oldVar = x.getParams().get(i); @@ -171,7 +171,7 @@ // Set the new original param types based on the old original param types List<JType> originalParamTypes = new ArrayList<JType>(); - originalParamTypes.add(enclosingType); + originalParamTypes.add(program.getNonNullType(enclosingType)); originalParamTypes.addAll(x.getOriginalParamTypes()); newMethod.setOriginalTypes(x.getOriginalReturnType(), originalParamTypes); @@ -186,7 +186,8 @@ x.setBody(newBody); JMethodCall newCall = new JMethodCall(delegateCallSourceInfo, null, newMethod); - newCall.addArg(new JThisRef(delegateCallSourceInfo, enclosingType)); + newCall.addArg(new JThisRef(delegateCallSourceInfo, + program.getNonNullType(enclosingType))); for (int i = 0; i < x.getParams().size(); ++i) { JParameter param = x.getParams().get(i); newCall.addArg(new JParameterRef(delegateCallSourceInfo, param));
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java index 4e5cb42..d5ec679 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java
@@ -26,7 +26,6 @@ import com.google.gwt.dev.jjs.ast.JNullType; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JReferenceType; -import com.google.gwt.dev.jjs.ast.JType; /** * Update polymorphic method calls to tighter bindings based on the type of the @@ -54,7 +53,7 @@ return; } - JType instanceType = instance.getType(); + JReferenceType instanceType = ((JReferenceType) instance.getType()).getUnderlyingType(); JReferenceType enclosingType = method.getEnclosingType(); if (instanceType == enclosingType
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRebinds.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRebinds.java index 841735a..048a7b0 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRebinds.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRebinds.java
@@ -18,11 +18,11 @@ import com.google.gwt.dev.jjs.InternalCompilerException; import com.google.gwt.dev.jjs.ast.Context; import com.google.gwt.dev.jjs.ast.JClassType; +import com.google.gwt.dev.jjs.ast.JDeclaredType; import com.google.gwt.dev.jjs.ast.JGwtCreate; import com.google.gwt.dev.jjs.ast.JModVisitor; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JReboundEntryPoint; -import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JType; import java.util.List; @@ -91,7 +91,7 @@ throw new InternalCompilerException("Unexpected failure to rebind '" + reqType + "'"); } - JReferenceType result = program.getFromTypeMap(reboundClassName); + JDeclaredType result = program.getFromTypeMap(reboundClassName); assert (result != null); return (JClassType) result; }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/SourceGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/SourceGenerationVisitor.java index abbef1d..6c35360 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/SourceGenerationVisitor.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/SourceGenerationVisitor.java
@@ -17,12 +17,12 @@ import com.google.gwt.dev.jjs.ast.Context; import com.google.gwt.dev.jjs.ast.JClassType; +import com.google.gwt.dev.jjs.ast.JDeclaredType; import com.google.gwt.dev.jjs.ast.JField; import com.google.gwt.dev.jjs.ast.JInterfaceType; import com.google.gwt.dev.jjs.ast.JMethod; import com.google.gwt.dev.jjs.ast.JMethodBody; import com.google.gwt.dev.jjs.ast.JProgram; -import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.util.TextOutput; /** @@ -99,7 +99,7 @@ @Override public boolean visit(JProgram x, Context ctx) { for (int i = 0; i < x.getDeclaredTypes().size(); ++i) { - JReferenceType type = x.getDeclaredTypes().get(i); + JDeclaredType type = x.getDeclaredTypes().get(i); accept(type); newline(); newline();
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 e7bb5a4..04ed496 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
@@ -306,7 +306,8 @@ */ JMethod staticImplFor = program.staticImplFor(x); if (staticImplFor == null - || !staticImplFor.getEnclosingType().getMethods().contains(staticImplFor)) { + || !staticImplFor.getEnclosingType().getMethods().contains( + staticImplFor)) { // The instance method has already been pruned. return true; } @@ -404,17 +405,18 @@ if (triviallyTrue) { // remove the cast operation ctx.replaceMe(x.getExpr()); - } else if (triviallyFalse) { - // replace with a magic NULL cast + } else if (triviallyFalse && toType != program.getTypeNull()) { + // replace with a magic NULL cast, unless it's already a cast to NULL JCastOperation newOp = new JCastOperation(x.getSourceInfo(), program.getTypeNull(), x.getExpr()); ctx.replaceMe(newOp); } else { // If possible, try to use a narrower cast - JClassType concreteType = getSingleConcreteType(toType); - if (concreteType != null) { + JReferenceType tighterType = getSingleConcreteType(toType); + + if (tighterType != null && tighterType != toType) { JCastOperation newOp = new JCastOperation(x.getSourceInfo(), - concreteType, x.getExpr()); + tighterType, x.getExpr()); ctx.replaceMe(newOp); } } @@ -442,10 +444,10 @@ @Override public void endVisit(JGwtCreate x, Context ctx) { - List<JClassType> typeList = new ArrayList<JClassType>(); + List<JReferenceType> typeList = new ArrayList<JReferenceType>(); for (JExpression expr : x.getInstantiationExpressions()) { - JType type = expr.getType(); - typeList.add((JClassType) type); + JReferenceType type = (JReferenceType) expr.getType(); + typeList.add(type); } JReferenceType resultType = program.generalizeTypes(typeList); @@ -493,7 +495,7 @@ ctx.replaceMe(program.getLiteralBoolean(false)); } else { // If possible, try to use a narrower cast - JClassType concreteType = getSingleConcreteType(toType); + JReferenceType concreteType = getSingleConcreteType(toType); if (concreteType != null) { JInstanceOf newOp = new JInstanceOf(x.getSourceInfo(), concreteType, x.getExpr()); @@ -528,7 +530,7 @@ return; } - JClassType concreteType = getSingleConcreteType(x.getType()); + JReferenceType concreteType = getSingleConcreteType(x.getType()); if (concreteType != null) { x.setType(concreteType); myDidChange = true; @@ -545,13 +547,6 @@ // tighten based on both returned types and possible overrides List<JReferenceType> typeList = new ArrayList<JReferenceType>(); - /* - * Always assume at least one null assignment; if there really aren't any - * other assignments, then this variable will get the null type. If there - * are, it won't hurt anything because null type will always lose. - */ - typeList.add(typeNull); - Set<JExpression> myReturns = returns.get(x); if (myReturns != null) { for (JExpression expr : myReturns) { @@ -565,7 +560,13 @@ } } - JReferenceType resultType = program.generalizeTypes(typeList); + JReferenceType resultType; + if (typeList.isEmpty()) { + // The method returns nothing + resultType = typeNull; + } else { + resultType = program.generalizeTypes(typeList); + } resultType = program.strongerType(refType, resultType); if (refType != resultType) { x.setType(resultType); @@ -657,14 +658,18 @@ * Given an abstract type, return the single concrete implementation of that * type. */ - private JClassType getSingleConcreteType(JType type) { + private JReferenceType getSingleConcreteType(JType type) { if (type instanceof JReferenceType) { JReferenceType refType = (JReferenceType) type; if (refType.isAbstract()) { - JClassType singleConcrete = getSingleConcrete((JReferenceType) type, - implementors); + JClassType singleConcrete = getSingleConcrete( + refType.getUnderlyingType(), implementors); assert (singleConcrete == null || program.typeOracle.isInstantiatedType(singleConcrete)); - return singleConcrete; + if (singleConcrete == null) { + return null; + } + return refType.canBeNull() ? singleConcrete + : program.getNonNullType(singleConcrete); } } return null; @@ -715,7 +720,7 @@ } // tighten based on leaf types - JClassType leafType = getSingleConcreteType(refType); + JReferenceType leafType = getSingleConcreteType(refType); if (leafType != null) { x.setType(leafType); myDidChange = true; @@ -726,15 +731,13 @@ List<JReferenceType> typeList = new ArrayList<JReferenceType>(); /* - * For non-parameters, always assume at least one null assignment; if - * there really aren't any other assignments, then this variable will get - * the null type. If there are, it won't hurt anything because null type - * will always lose. - * - * For parameters, don't perform any tightening if we can't find any - * actual assignments. The method should eventually get pruned. + * For fields without an initializer, add a null assignment, because the + * field might be accessed before initialized. Technically even a field + * with an initializer might be accessed before initialization, but + * presumably that is not the programmer's intent, so the compiler cheats + * and assumes the initial null will not be seen. */ - if (!(x instanceof JParameter)) { + if ((x instanceof JField) && !x.hasInitializer()) { typeList.add(typeNull); } @@ -758,12 +761,22 @@ } } - if (typeList.isEmpty()) { - return; + JReferenceType resultType; + if (!typeList.isEmpty()) { + resultType = program.generalizeTypes(typeList); + resultType = program.strongerType(refType, resultType); + } else { + if (x instanceof JParameter) { + /* + * There is no need to tighten unused parameters, because they will be + * pruned. + */ + resultType = refType; + } else { + resultType = typeNull; + } } - JReferenceType resultType = program.generalizeTypes(typeList); - resultType = program.strongerType(refType, resultType); if (refType != resultType) { x.setType(resultType); myDidChange = true;
diff --git a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java index 1a154a6..1f4bed4 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java +++ b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java
@@ -15,8 +15,8 @@ */ package com.google.gwt.dev.js; +import com.google.gwt.dev.jjs.ast.JClassType; import com.google.gwt.dev.jjs.ast.JMethod; -import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.impl.FragmentExtractor; import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap; import com.google.gwt.dev.js.ast.JsBlock; @@ -121,7 +121,7 @@ private JsName nameToBillTo(JsVisitable node) { if (node instanceof JsStatement) { JsStatement stat = (JsStatement) node; - JReferenceType type = map.typeForStatement(stat); + JClassType type = map.typeForStatement(stat); if (type != null) { return map.nameForType(type); }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/JjsTypeTest.java b/dev/core/test/com/google/gwt/dev/jjs/JjsTypeTest.java new file mode 100644 index 0000000..38821ac --- /dev/null +++ b/dev/core/test/com/google/gwt/dev/jjs/JjsTypeTest.java
@@ -0,0 +1,304 @@ +/* + * Copyright 2009 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; + +import com.google.gwt.dev.jjs.ast.JArrayType; +import com.google.gwt.dev.jjs.ast.JClassType; +import com.google.gwt.dev.jjs.ast.JInterfaceType; +import com.google.gwt.dev.jjs.ast.JNonNullType; +import com.google.gwt.dev.jjs.ast.JProgram; +import com.google.gwt.dev.jjs.ast.JReferenceType; +import com.google.gwt.dev.jjs.ast.JTypeOracle; + +import junit.framework.TestCase; + +import org.eclipse.jdt.core.compiler.CharOperation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Test basic operations on the types used by the JJS compiler. See + * {@link com.google.gwt.dev.jjs.ast.JType}. + */ +public class JjsTypeTest extends TestCase { + private JArrayType arrayOfA; + private JArrayType arrayOfArrayOfB; + private JReferenceType arrayOfArrayOfInt; + private JArrayType arrayOfB; + private JArrayType arrayOfBSub; + private JArrayType arrayOfC; + private JReferenceType arrayOfInt; + private JArrayType arrayOfObject; + private JClassType classA; + private JClassType classB; + private JClassType classBase; + private JNonNullType classBaseNn; + private JNonNullType classBnn; + private JClassType classBSub; + private JClassType classC; + private JClassType classJso; + private JClassType classJso1; + private JClassType classJso2; + private JClassType classObject; + private JClassType classString; + private JInterfaceType intfI; + private JInterfaceType intfIBase; + private JInterfaceType intfJ; + private JProgram program; + private SourceInfo synthSource; + private JReferenceType typeNull; + private JTypeOracle typeOracle; + + public void testCanTheoreticallyCast() { + assertFalse(typeOracle.canTheoreticallyCast(classBnn, typeNull)); + + assertTrue(typeOracle.canTheoreticallyCast(classBSub, classB)); + assertTrue(typeOracle.canTheoreticallyCast(classB, classBSub)); + + assertTrue(typeOracle.canTheoreticallyCast(classB, classBnn)); + assertTrue(typeOracle.canTheoreticallyCast(classBnn, classB)); + + assertTrue(typeOracle.canTheoreticallyCast(classB, classB)); + + assertTrue(typeOracle.canTheoreticallyCast(classObject, arrayOfB)); + assertFalse(typeOracle.canTheoreticallyCast(arrayOfA, arrayOfArrayOfB)); + + assertTrue(typeOracle.canTheoreticallyCast(arrayOfObject, arrayOfArrayOfB)); + + assertTrue(typeOracle.canTheoreticallyCast(arrayOfB, arrayOfBSub)); + + assertTrue(typeOracle.canTheoreticallyCast(classBase, intfI)); + assertFalse(typeOracle.canTheoreticallyCast(classA, intfJ)); + + assertTrue(typeOracle.canTheoreticallyCast(intfIBase, intfI)); + + assertTrue(typeOracle.canTheoreticallyCast(intfIBase, classBase)); + assertFalse(typeOracle.canTheoreticallyCast(intfJ, classA)); + } + + public void testCanTriviallyCast() { + assertTrue(typeOracle.canTriviallyCast(classB, classB)); + + assertTrue(typeOracle.canTriviallyCast(classBSub, classB)); + assertFalse(typeOracle.canTriviallyCast(classB, classBSub)); + + assertFalse(typeOracle.canTriviallyCast(classC, classA)); + assertFalse(typeOracle.canTriviallyCast(classA, classC)); + + assertTrue(typeOracle.canTriviallyCast(classB, intfI)); + assertFalse(typeOracle.canTriviallyCast(intfI, classB)); + + assertTrue(typeOracle.canTriviallyCast(classB, classObject)); + assertFalse(typeOracle.canTriviallyCast(classObject, classB)); + + assertTrue(typeOracle.canTriviallyCast(classB, intfI)); + assertFalse(typeOracle.canTriviallyCast(intfI, classB)); + + assertTrue(typeOracle.canTriviallyCast(classBnn, classB)); + assertFalse(typeOracle.canTriviallyCast(classB, classBnn)); + + assertTrue(typeOracle.canTriviallyCast(typeNull, classB)); + assertFalse(typeOracle.canTriviallyCast(classB, typeNull)); + + assertTrue(typeOracle.canTriviallyCast(arrayOfBSub, arrayOfB)); + assertFalse(typeOracle.canTriviallyCast(arrayOfB, arrayOfBSub)); + + assertFalse(typeOracle.canTriviallyCast(arrayOfA, arrayOfB)); + assertFalse(typeOracle.canTriviallyCast(arrayOfB, arrayOfA)); + + assertFalse(typeOracle.canTriviallyCast(arrayOfArrayOfB, arrayOfB)); + assertFalse(typeOracle.canTriviallyCast(arrayOfB, arrayOfArrayOfB)); + + assertTrue(typeOracle.canTriviallyCast(arrayOfArrayOfB, arrayOfObject)); + assertFalse(typeOracle.canTriviallyCast(arrayOfObject, arrayOfArrayOfB)); + + assertTrue(typeOracle.canTriviallyCast(classJso1, classJso2)); + assertTrue(typeOracle.canTriviallyCast(classJso2, classJso1)); + + assertTrue(typeOracle.canTriviallyCast(classJso, classJso1)); + assertTrue(typeOracle.canTriviallyCast(classJso, classJso1)); + + /* + * Test that two types cannot both be trivially castable to each other, + * unless they are the same type. Or, unless they are both JSOs. + */ + for (JReferenceType type1 : severalTypes()) { + for (JReferenceType type2 : severalTypes()) { + if (type1 != type2) { + if (!isJso(type1) || !isJso(type2)) { + assertFalse(typeOracle.canTriviallyCast(type1, type2) + && typeOracle.canTriviallyCast(type2, type1)); + } + } + } + } + } + + public void testGeneralizeTypes() { + assertSame(classA, generalizeTypes(classA, classA)); + assertSame(classB, generalizeTypes(classB, classBnn)); + assertSame(classB, generalizeTypes(classBnn, classB)); + assertSame(classBaseNn, generalizeTypes(classBnn, classBaseNn)); + assertSame(classB, generalizeTypes(classB, typeNull)); + assertSame(classB, generalizeTypes(typeNull, classB)); + + assertSame(intfIBase, generalizeTypes(intfI, intfIBase)); + assertSame(intfIBase, generalizeTypes(intfIBase, intfI)); + assertSame(classObject, generalizeTypes(intfJ, intfI)); + + assertSame(classObject, generalizeTypes(arrayOfB, arrayOfInt)); + assertSame(classObject, generalizeTypes(arrayOfC, arrayOfArrayOfB)); + assertSame(arrayOfObject, generalizeTypes(arrayOfC, arrayOfB)); + assertSame(arrayOfObject, generalizeTypes(arrayOfObject, arrayOfArrayOfInt)); + + assertSame(intfI, generalizeTypes(classB, intfI)); + assertSame(classObject, generalizeTypes(classB, intfJ)); + + assertSame(classObject, generalizeTypes(intfI, arrayOfInt)); + + for (JReferenceType type1 : severalTypes()) { + for (JReferenceType type2 : severalTypes()) { + JReferenceType generalized = generalizeTypes(type1, type2); + assertTrue(typeOracle.canTriviallyCast(type1, generalized)); + assertTrue(typeOracle.canTriviallyCast(type2, generalized)); + } + } + } + + public void testStrongerType() { + assertSame(classA, program.strongerType(classA, classA)); + assertSame(classBnn, program.strongerType(classB, classBnn)); + assertSame(classB, program.strongerType(classB, classBase)); + assertSame(classB, program.strongerType(classBase, classB)); + assertSame(intfI, program.strongerType(intfI, intfJ)); + } + + @Override + protected void setUp() { + createSampleProgram(); + } + + private JClassType createClass(String className, JClassType superClass, + boolean isAbstract, boolean isFinal) { + JClassType clazz = program.createClass(synthSource, CharOperation.splitOn( + '.', className.toCharArray()), isAbstract, isFinal); + clazz.setSuperClass(superClass); + return clazz; + } + + private JInterfaceType createInterface(String className) { + JInterfaceType intf = program.createInterface(synthSource, + CharOperation.splitOn('.', className.toCharArray())); + return intf; + } + + private void createSampleProgram() { + // Make the program itself + program = new JProgram(); + typeOracle = program.typeOracle; + synthSource = program.createSourceInfoSynthetic(JjsTypeTest.class, + "synthetic node used for testing"); + + classObject = createClass("java.lang.Object", null, false, false); + classString = createClass("java.lang.String", classObject, false, true); + classJso = createClass("com.google.gwt.core.client.JavaScriptObject", + classObject, false, false); + + intfIBase = createInterface("IBase"); + + intfI = createInterface("I"); + intfI.addImplements(intfIBase); + + intfJ = createInterface("J"); + + classBase = createClass("Base", classObject, false, false); + + classA = createClass("A", classBase, false, false); + + classB = createClass("B", classBase, false, false); + classB.addImplements(intfI); + + classC = createClass("C", classObject, false, false); + classC.addImplements(intfI); + + classBSub = createClass("BSub", classB, false, false); + + classJso1 = createClass("Jso1", classJso, false, false); + classJso2 = createClass("Jso2", classJso, false, false); + + program.typeOracle.computeBeforeAST(); + + // Save off some miscellaneous types to test against + typeNull = program.getTypeNull(); + + classBnn = program.getNonNullType(classB); + classBaseNn = program.getNonNullType(classBase); + + arrayOfA = program.getTypeArray(classA, 1); + arrayOfB = program.getTypeArray(classB, 1); + arrayOfBSub = program.getTypeArray(classBSub, 1); + arrayOfC = program.getTypeArray(classC, 1); + arrayOfObject = program.getTypeArray(classObject, 1); + arrayOfInt = program.getTypeArray(program.getTypePrimitiveInt(), 1); + arrayOfArrayOfInt = program.getTypeArray(program.getTypePrimitiveInt(), 2); + + arrayOfArrayOfB = program.getTypeArray(classB, 2); + } + + private JReferenceType generalizeTypes(JReferenceType type1, + JReferenceType type2) { + List<JReferenceType> types = new ArrayList<JReferenceType>(2); + types.add(type1); + types.add(type2); + return program.generalizeTypes(types); + } + + private boolean isJso(JReferenceType type) { + return typeOracle.canTriviallyCast(type, classJso); + } + + /** + * Return several types, for exhaustively testing basic properties. + */ + private Collection<JReferenceType> severalTypes() { + List<JReferenceType> types = new ArrayList<JReferenceType>(); + types.add(arrayOfA); + types.add(arrayOfB); + types.add(arrayOfArrayOfB); + types.add(arrayOfBSub); + types.add(arrayOfObject); + types.add(classA); + types.add(classB); + types.add(classBSub); + types.add(classBnn); + types.add(classBase); + types.add(classC); + types.add(classObject); + types.add(classString); + types.add(intfI); + types.add(intfJ); + types.add(intfIBase); + types.add(classJso1); + types.add(classJso2); + types.add(classJso); + types.add(typeNull); + + return types; + } +}