com.google.gwt.dev.jjs.impl.UnifyAst Maven / Gradle / Ivy
/*
* Copyright 2011 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.impl;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.dev.CompilerContext;
import com.google.gwt.dev.MinimalRebuildCache;
import com.google.gwt.dev.cfg.ConfigurationProperty;
import com.google.gwt.dev.cfg.Property;
import com.google.gwt.dev.javac.CompilationProblemReporter;
import com.google.gwt.dev.javac.CompilationState;
import com.google.gwt.dev.javac.CompilationUnit;
import com.google.gwt.dev.javac.CompiledClass;
import com.google.gwt.dev.jdt.RebindPermutationOracle;
import com.google.gwt.dev.jjs.PrecompilationContext;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.SourceOrigin;
import com.google.gwt.dev.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.HasName;
import com.google.gwt.dev.jjs.ast.JArrayType;
import com.google.gwt.dev.jjs.ast.JBinaryOperation;
import com.google.gwt.dev.jjs.ast.JBooleanLiteral;
import com.google.gwt.dev.jjs.ast.JCastOperation;
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.JConstructor;
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.jjs.ast.JEnumType;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JExpressionStatement;
import com.google.gwt.dev.jjs.ast.JField;
import com.google.gwt.dev.jjs.ast.JFieldRef;
import com.google.gwt.dev.jjs.ast.JInstanceOf;
import com.google.gwt.dev.jjs.ast.JInterfaceType;
import com.google.gwt.dev.jjs.ast.JMember;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JMethod.Specialization;
import com.google.gwt.dev.jjs.ast.JMethodBody;
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JModVisitor;
import com.google.gwt.dev.jjs.ast.JNameOf;
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.JNullLiteral;
import com.google.gwt.dev.jjs.ast.JPermutationDependentValue;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JReferenceType;
import com.google.gwt.dev.jjs.ast.JStringLiteral;
import com.google.gwt.dev.jjs.ast.JThisRef;
import com.google.gwt.dev.jjs.ast.JTryStatement;
import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.jjs.ast.JUnsafeTypeCoercion;
import com.google.gwt.dev.jjs.ast.JVariable;
import com.google.gwt.dev.jjs.ast.RuntimeConstants;
import com.google.gwt.dev.jjs.ast.js.JDebuggerStatement;
import com.google.gwt.dev.jjs.ast.js.JsniFieldRef;
import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
import com.google.gwt.dev.jjs.ast.js.JsonArray;
import com.google.gwt.dev.js.ast.JsNestingScope;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsRootScope;
import com.google.gwt.dev.util.JsniRef;
import com.google.gwt.dev.util.Name.BinaryName;
import com.google.gwt.dev.util.Name.InternalName;
import com.google.gwt.dev.util.StringInterner;
import com.google.gwt.dev.util.log.MetricName;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import com.google.gwt.thirdparty.guava.common.base.Predicates;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
import com.google.gwt.thirdparty.guava.common.collect.Iterables;
import com.google.gwt.thirdparty.guava.common.collect.LinkedListMultimap;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Multimap;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import com.google.gwt.thirdparty.guava.common.collect.Sets.SetView;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
/**
* Take independently-compiled types and merge them into a single AST.
*
* Works kind of like {@link ControlFlowAnalyzer} in terms of reachability,
* except that in some cases it's easier to be conservative and visit relatively
* more nodes than CFA would.
*
* Operates based on a work-queue to prevent recursion sickness.
*
* Must handle:
*
* - Type reference resolution
*
* - Field and method reference resolution
*
* - General code flow like ControlFlowAnalyzer
*
* - GWT.create(), GWT.runAsync(), Impl.getNameOf()
*
* - Stitch native methods into JsProgram
*
* - Class.desiredAssertionStatus, Class.isClassMetaDataEnabled, GWT.isClient,
* GWT.isProdMode, GWT.isScript.
*/
// TODO: SOYC correlations.
// TODO(stalcup): perform only binary name based lookups so that libraries
// don't need to index compilation units by both source and binary name
// TODO(stalcup): shrink the translate/flowInto graph for reference only types to eliminate
// unnecessary loading of types and increase performance.
public class UnifyAst {
/**
* Embodies the access methods for the compiled class, compilation unit and type for a flavor of
* type name.
*/
private abstract class NameBasedTypeLocator {
private final Map compiledClassesByTypeName;
private NameBasedTypeLocator(Map compiledClassesByTypeName) {
this.compiledClassesByTypeName = compiledClassesByTypeName;
}
protected abstract boolean hasCompileErrors(String typeName);
protected abstract void logErrorTrace(TreeLogger branch, Type logLevel, String sourceName);
protected CompilationUnit getCompilationUnitFromSource(String typeName) {
return compiledClassesByTypeName.get(typeName).getUnit();
}
protected JDeclaredType getResolvedType(String typeName) {
JDeclaredType resolvedType = program.getFromTypeMap(typeName);
return resolvedType;
}
protected boolean resolvedTypeIsAvailable(String typeName) {
return program.getFromTypeMap(typeName) != null;
}
protected boolean sourceCompilationUnitIsAvailable(String typeName) {
return compiledClassesByTypeName.containsKey(typeName);
}
}
private class UnifyVisitor extends JModVisitor {
private JMethod currentMethod;
@Override
public void endVisit(JArrayType x, Context ctx) {
assert false : "Should not get here";
}
@Override
public void endVisit(JBinaryOperation x, Context ctx) {
// Concat ops need to resolve string type.
x.setType(translate(x.getType().getUnderlyingType()));
}
@Override
public void endVisit(JCastOperation x, Context ctx) {
x.resolve(translate(x.getCastType()));
}
@Override
public void endVisit(JClassLiteral x, Context ctx) {
JType refType = translate(x.getRefType());
x.resolve(refType);
// ImplementClassLiteralsAsFields: rescue enumType.values()/valueOf().
if (refType instanceof JArrayType) {
refType = ((JArrayType) refType).getLeafType();
}
JEnumType enumType = refType.isEnumOrSubclass();
if (enumType == null) {
return;
}
for (JMethod method : enumType.getMethods()) {
if (!method.isStatic()) {
continue;
}
if (method.getSignature().startsWith("values()") ||
method.getSignature().startsWith("valueOf(Ljava/lang/String;)")) {
flowInto(method);
}
}
}
@Override
public void endVisit(JClassType x, Context ctx) {
assert false : "Should not get here";
}
@Override
public void endVisit(JConditional x, Context ctx) {
x.setType(translate(x.getType()));
}
@Override
public void endVisit(JConstructor x, Context ctx) {
// Process as method.
super.endVisit(x, ctx);
instantiate(x.getEnclosingType());
}
@Override
public void endVisit(JDeclaredType x, Context ctx) {
assert false : "Should not get here";
}
@Override
public void endVisit(JExpression x, Context ctx) {
assert !x.getType().isExternal() || errorsFound;
}
@Override
public void endVisit(JField x, Context ctx) {
assert false : "Should not get here";
}
@Override
public void endVisit(JFieldRef x, Context ctx) {
JField field = translate(x.getSourceInfo(), x.getField());
flowInto(field);
x.resolve(field);
// Should not have an overridden type at this point.
assert x.getType() == x.getField().getType();
assert !x.getEnclosingType().isExternal();
}
@Override
public void endVisit(JInstanceOf x, Context ctx) {
x.resolve(translate(x.getTestType()));
}
@Override
public void endVisit(JInterfaceType x, Context ctx) {
assert false : "Should not get here";
}
@Override
public void endVisit(JMethod x, Context ctx) {
currentMethod = null;
}
@Override
public void endVisit(JMethodCall x, Context ctx) {
// Already resolved during visit().
JMethod target = x.getTarget();
if (target.isExternal()) {
assert errorsFound;
return;
}
// Should not have an overridden type at this point.
assert x instanceof JNewInstance || x.getType() == target.getType();
flowInto(target);
}
@Override
public void endVisit(JNameOf x, Context ctx) {
HasName node = x.getNode();
if (node instanceof JType) {
node = translate((JType) node);
} else if (node instanceof JMember) {
node = translate(x.getSourceInfo(), (JMember) node);
} else {
assert false : "Should not get here";
}
x.resolve(node, (JClassType) translate(x.getType().getUnderlyingType()));
}
@Override
public void endVisit(JNewArray x, Context ctx) {
x.setType((JArrayType) translate(x.getArrayType()));
}
@Override
public void endVisit(JNewInstance x, Context ctx) {
JConstructor target = x.getTarget();
if (target.isExternal()) {
assert errorsFound;
return;
}
flowInto(target);
}
@Override
public void endVisit(JsniFieldRef x, Context ctx) {
endVisit((JFieldRef) x, ctx);
}
@Override
public void endVisit(JsniMethodBody x, Context ctx) {
JsNestingScope funcScope = (JsNestingScope) x.getFunc().getScope();
assert funcScope.getParent() == JsRootScope.INSTANCE;
funcScope.nestInto(jsProgram.getScope());
}
@Override
public void endVisit(JsniMethodRef x, Context ctx) {
JMethod target = translate(x.getSourceInfo(), x.getTarget());
x.resolve(target, program.getJavaScriptObject());
flowInto(target);
}
@Override
public void endVisit(JsonArray x, Context ctx) {
x.resolve(translate(x.getType()));
}
@Override
public void endVisit(JStringLiteral x, Context ctx) {
JClassType stringType = program.getTypeJavaLangString();
x.resolve(stringType);
instantiate(stringType);
}
@Override
public void endVisit(JThisRef x, Context ctx) {
assert !x.getType().isExternal();
}
@Override
public void endVisit(JTryStatement x, Context ctx) {
// Needs to resolve the Exceptions Types explicitly they are multiple in Java 7 and
// potentially different from the one in the exception variable.
for (JTryStatement.CatchClause clause : x.getCatchClauses()) {
List types = clause.getTypes();
for (int i = 0; i < types.size(); i++) {
JReferenceType resolvedType = translate((JReferenceType) types.get(i));
assert resolvedType.replaces(types.get(i));
types.set(i, resolvedType);
}
}
}
@Override
public void endVisit(JUnsafeTypeCoercion x, Context ctx) {
x.resolve(translate(x.getCoercionType()));
}
@Override
public void endVisit(JVariable x, Context ctx) {
x.setType(translate(x.getType()));
}
@Override
public boolean visit(JExpressionStatement x, Context ctx) {
if (x.getExpr() instanceof JMethodCall) {
JMethodCall call = (JMethodCall) x.getExpr();
JMethod target = call.getTarget();
if (GWT_DEBUGGER_METHOD_CALLS.contains(target.getQualifiedName())) {
// We should see all calls here because GWT.debugger() returns void.
ctx.replaceMe(new JDebuggerStatement(x.getSourceInfo()));
}
}
return true;
}
@Override
public boolean visit(JMethod x, Context ctx) {
currentMethod = x;
// Only visit contents of methods defined in types which are part of this compile. Visit
// also clinits that are reachable to make sure all the nodes that are needed for
// propagating compile time constants are available.
return !program.isReferenceOnly(x.getEnclosingType()) ||
x == x.getEnclosingType().getClinitMethod();
}
@Override
public boolean visit(JMethodCall x, Context ctx) {
JMethod target = translate(x.getSourceInfo(), x.getTarget());
x.resolve(target);
// Special handling for magic method calls.
JExpression replacement = maybeHandleMagicMethodCall(x);
if (replacement != null) {
ctx.replaceMe(accept(replacement));
return false;
}
return true;
}
private JExpression maybeHandleMagicMethodCall(JMethodCall methodCall) {
JExpression result;
switch (methodCall.getTarget().getQualifiedName()) {
case GWT_CREATE:
case OLD_GWT_CREATE:
result = createRebindExpression(methodCall);
break;
case IMPL_GET_NAME_OF:
result = handleImplNameOf(methodCall);
break;
case SYSTEM_GET_PROPERTY:
case SYSTEM_GET_PROPERTY_WITH_DEFAULT:
result = handleSystemGetProperty(methodCall);
break;
default:
// Not a magic method call, return null so that it does not get replaced.
return null;
}
if (result == null) {
// Handled magic call possibly with an error.
return JNullLiteral.INSTANCE;
}
return result;
}
private JExpression handleSystemGetProperty(JMethodCall gwtGetPropertyCall) {
assert (gwtGetPropertyCall.getArgs().size() == 1 || gwtGetPropertyCall.getArgs().size() == 2);
JExpression propertyNameExpression = gwtGetPropertyCall.getArgs().get(0);
boolean defaultVersionCalled = gwtGetPropertyCall.getArgs().size() == 2;
JExpression defaultValueExpression = defaultVersionCalled ?
gwtGetPropertyCall.getArgs().get(1) : null;
if (!(propertyNameExpression instanceof JStringLiteral)) {
error(gwtGetPropertyCall,
"Only string constants may be used as property name in System.getProperty()");
return null;
}
String propertyName = ((JStringLiteral) propertyNameExpression).getValue();
if (!defaultVersionCalled && !isPropertyDefined(propertyName)) {
error(gwtGetPropertyCall, "Property '" + propertyName + "' is not defined.");
return null;
}
if (isMultivaluedProperty(propertyName)) {
error(gwtGetPropertyCall,
"Property '" + propertyName + "' is multivalued. " +
"Multivalued properties are not supported by System.getProperty().");
return null;
}
if (defaultValueExpression != null) {
defaultValueExpression = accept(defaultValueExpression);
}
return JPermutationDependentValue.createRuntimeProperty(
program, gwtGetPropertyCall.getSourceInfo(), propertyName, defaultValueExpression);
}
private JExpression createRebindExpression(JMethodCall gwtCreateCall) {
assert (gwtCreateCall.getArgs().size() == 1);
JExpression arg = gwtCreateCall.getArgs().get(0);
if (!(arg instanceof JClassLiteral)) {
error(gwtCreateCall, "Only class literals may be used as arguments to GWT.create()");
return null;
}
JClassLiteral classLiteral = (JClassLiteral) arg;
if (!(classLiteral.getRefType() instanceof JDeclaredType)) {
error(gwtCreateCall,
"Only classes and interfaces may be used as arguments to GWT.create()");
return null;
}
Event event = SpeedTracerLogger.start(CompilerEventType.VISIT_GWT_CREATE,
"argument", classLiteral.getRefType().getName(),
"caller", gwtCreateCall.getSourceInfo().getFileName());
try {
return createStaticRebindExpression(gwtCreateCall, classLiteral);
} finally {
event.end();
}
}
private JExpression createStaticRebindExpression(JMethodCall gwtCreateCall,
JClassLiteral classLiteral) {
JDeclaredType type = (JDeclaredType) classLiteral.getRefType();
String reboundTypeName = type.getName();
// TODO(stalcup): below a MinimalRebuildCache pattern of "clear cache entries for a type" and
// "rebuild cache entries for that type" is followed. There is a danger that a compile error
// could occur between the two stages and leave the cache in an invalid state. Switch to a
// transactionally safe update pattern like always updating a copy and swapping out the
// original for the copy at the end of a successful compile.
if (incrementalCompile) {
// If this is the first time we've rebound this type during this compile.
if (reboundTypeNames.add(reboundTypeName)) {
// The rebinding of this type will accumulate rebound type to input resource associations,
// but the accumulation should start from scratch, so clear any existing associations that
// might have been collected in previous compiles.
minimalRebuildCache.clearReboundTypeAssociations(reboundTypeName);
}
minimalRebuildCache.recordRebinderTypeForReboundType(reboundTypeName,
currentMethod.getEnclosingType().getName());
rebindPermutationOracle
.getGeneratorContext().setCurrentRebindBinaryTypeName(reboundTypeName);
}
String requestedType = BinaryName.toSourceName(reboundTypeName);
List answers;
try {
answers = Lists.newArrayList(
rebindPermutationOracle.getAllPossibleRebindAnswers(logger, requestedType));
if (incrementalCompile) {
// Accumulate generated artifacts so that they can be output on recompiles even if no
// generators are run.
ArtifactSet artifacts = rebindPermutationOracle.getGeneratorContext().getArtifacts();
minimalRebuildCache.addGeneratedArtifacts(artifacts);
}
rebindPermutationOracle.getGeneratorContext().finish(logger);
if (incrementalCompile) {
// There may be more types known to be modified after Generator execution, which would
// mean the previous stale types calculation was too small. Redo it.
staleTypeNames =
minimalRebuildCache.computeAndClearStaleTypesCache(logger, program.typeOracle);
checkPreambleTypesStillFresh(logger);
fullFlowIntoRemainingStaleTypes();
}
} catch (UnableToCompleteException e) {
error(gwtCreateCall, "Failed to resolve '" + requestedType + "' via deferred binding");
return null;
}
List instantiationExpressions =
Lists.newArrayListWithCapacity(answers.size());
for (String answer : answers) {
JDeclaredType answerType = internalFindType(answer, sourceNameBasedTypeLocator, true);
if (answerType == null) {
error(gwtCreateCall, "Rebind result '" + answer + "' could not be found");
return null;
}
if (!(answerType instanceof JClassType)) {
error(gwtCreateCall, "Rebind result '" + answer + "' must be a class");
return null;
}
if (answerType.isAbstract()) {
error(gwtCreateCall, "Rebind result '" + answer + "' cannot be abstract");
return null;
}
if (isJso(answerType)) {
error(gwtCreateCall, "Rebind result '" + answer + "' cannot be a JSO");
return null;
}
JExpression result = JjsUtils
.createDefaultConstructorInstantiation(gwtCreateCall.getSourceInfo(),
(JClassType) answerType);
if (result == null) {
error(gwtCreateCall,
"Rebind result '" + answer + "' has no default (zero argument) constructors");
return null;
}
instantiationExpressions.add(result);
}
assert answers.size() == instantiationExpressions.size();
if (answers.size() == 1) {
return instantiationExpressions.get(0);
}
return JPermutationDependentValue
.createTypeRebind(program, gwtCreateCall.getSourceInfo(), requestedType,
answers, instantiationExpressions);
}
private JExpression handleImplNameOf(final JMethodCall x) {
assert (x.getArgs().size() == 1);
JExpression arg = x.getArgs().get(0);
if (!(arg instanceof JStringLiteral)) {
error(x, "Only string literals may be used as arguments to Impl.getNameOf()");
return null;
}
JStringLiteral stringLiteral = (JStringLiteral) arg;
String stringValue = stringLiteral.getValue();
JNode node = null;
JsniRef ref = JsniRef.parse(stringValue);
if (ref != null) {
node = JsniRefLookup.findJsniRefTarget(ref, program, new JsniRefLookup.ErrorReporter() {
@Override
public void reportError(String errMsg) {
error(x, errMsg);
}
});
}
if (node == null) {
// Not found, must be null
return null;
}
if (node instanceof JMethod) {
flowInto((JMethod) node);
program.addPinnedMethod((JMethod) node);
}
return new JNameOf(x.getSourceInfo(), program.getTypeJavaLangString(), (HasName) node);
}
}
private boolean isMultivaluedProperty(String propertyName) {
Property property = compilerContext.getModule().getProperties().find(propertyName);
if (!(property instanceof ConfigurationProperty)) {
return false;
}
return ((ConfigurationProperty) property).allowsMultipleValues();
}
private boolean isPropertyDefined(String propertyName) {
return compilerContext.getModule().getProperties().find(propertyName) != null;
}
private static final String CLASS_DESIRED_ASSERTION_STATUS =
"java.lang.Class.desiredAssertionStatus()Z";
private static final String CLASS_IS_CLASS_METADATA_ENABLED =
"java.lang.Class.isClassMetadataEnabled()Z";
public static final String GWT_CREATE =
"com.google.gwt.core.shared.GWT.create(Ljava/lang/Class;)Ljava/lang/Object;";
public static final String SYSTEM_GET_PROPERTY =
"java.lang.System.getProperty(Ljava/lang/String;)Ljava/lang/String;";
public static final String SYSTEM_GET_PROPERTY_WITH_DEFAULT =
"java.lang.System.getProperty(Ljava/lang/String;Ljava/lang/String;)" +
"Ljava/lang/String;";
private static final String GWT_DEBUGGER_SHARED = "com.google.gwt.core.shared.GWT.debugger()V";
private static final String GWT_DEBUGGER_CLIENT = "com.google.gwt.core.client.GWT.debugger()V";
private static final String GWT_IS_CLIENT = "com.google.gwt.core.shared.GWT.isClient()Z";
private static final String GWT_IS_PROD_MODE = "com.google.gwt.core.shared.GWT.isProdMode()Z";
private static final String GWT_IS_SCRIPT = "com.google.gwt.core.shared.GWT.isScript()Z";
private static final String IMPL_GET_NAME_OF =
"com.google.gwt.core.client.impl.Impl.getNameOf(Ljava/lang/String;)Ljava/lang/String;";
public static final String OLD_GWT_CREATE =
"com.google.gwt.core.client.GWT.create(Ljava/lang/Class;)Ljava/lang/Object;";
private static final String OLD_GWT_IS_CLIENT = "com.google.gwt.core.client.GWT.isClient()Z";
private static final String OLD_GWT_IS_PROD_MODE = "com.google.gwt.core.client.GWT.isProdMode()Z";
private static final String OLD_GWT_IS_SCRIPT = "com.google.gwt.core.client.GWT.isScript()Z";
/**
* Methods for which the call site must be replaced with magic AST nodes.
*/
private static final Set GWT_DEBUGGER_METHOD_CALLS =
Sets.newLinkedHashSet(Arrays.asList(GWT_DEBUGGER_SHARED, GWT_DEBUGGER_CLIENT));
/**
* Methods with magic implementations that the compiler must insert.
*/
private final Map replacementValueByMagicMethodQualifiedName;
private final CompilationState compilationState;
private final Map compiledClassesByInternalName;
private final Map compiledClassesBySourceName;
/**
* JVisitor interferes with any exceptions thrown inside of a visitor traversal call tree so any
* time UnifyAst wants to log an error and end operation care it should be done by manually
* logging an error line and setting errorsFound to true. Adequate checking is already in place to
* interpret this as ending further exploration and errorsFound = true is already being converted
* to an UnableToCompleteException at the UnifyAst public function boundaries
*/
private boolean errorsFound = false;
private final Set unitsWithErrorsAlreadyReported = Sets.newIdentityHashSet();
/**
* The set of types currently known to be instantiable. Like
* {@link ControlFlowAnalyzer#instantiatedTypes}.
*/
private final Set instantiatedTypes = Sets.newIdentityHashSet();
private final JsProgram jsProgram;
/**
* Fields and methods that are referenceable. Like
* {@link ControlFlowAnalyzer#liveFieldsAndMethods}.
*/
private final Set liveFieldsAndMethods = Sets.newIdentityHashSet();
/**
* Types which have had all of their fields and methods resolved (as opposed to the default
* behavior of only resolving the reachable ones). Currently only used when performing per-file
* compilation/recompilation.
*/
private final Set fullFlowTypes = Sets.newHashSet();
private final TreeLogger logger;
private final CompilerContext compilerContext;
private final Map resolvedMembersByQualifiedName = Maps.newHashMap();
private final JProgram program;
private final RebindPermutationOracle rebindPermutationOracle;
private final Set reboundTypeNames = Sets.newHashSet();
/**
* The names of types whose per-file compilation cached Js and StatementRanges are known to no
* longer be valid.
*
* Is initialized to the full initial list at the beginning of exec() and may be recalculated
* (larger) after Generator executions reveal more modified types.
*/
private Set staleTypeNames = Sets.newHashSet();
/**
* The names of stale types that have been processed (fully traversed) so far.
*/
private Set processedStaleTypeNames = Sets.newHashSet();
/**
* A work queue of methods whose bodies we need to traverse. Prevents
* excessive stack use.
*/
private final Queue methodsPending = Lists.newLinkedList();
private final Set liveVirtualMethods = Sets.newHashSet();
private final Multimap pendingVirtualMethodsBySignature =
LinkedListMultimap.create();
private NameBasedTypeLocator sourceNameBasedTypeLocator;
private NameBasedTypeLocator binaryNameBasedTypeLocator;
private NameBasedTypeLocator internalNameBasedTypeLocator;
private MinimalRebuildCache minimalRebuildCache;
private boolean incrementalCompile;
private final List rootTypeSourceNames = Lists.newArrayList();
public UnifyAst(TreeLogger logger, CompilerContext compilerContext, JProgram program,
JsProgram jsProgram, PrecompilationContext precompilationContext) {
this.incrementalCompile = compilerContext.getOptions().isIncrementalCompileEnabled();
this.logger = logger;
this.compilerContext = compilerContext;
this.program = program;
this.jsProgram = jsProgram;
this.rebindPermutationOracle = precompilationContext.getRebindPermutationOracle();
this.compilationState = rebindPermutationOracle.getCompilationState();
this.compiledClassesByInternalName = compilationState.getClassFileMap();
this.compiledClassesBySourceName = compilationState.getClassFileMapBySource();
initializeNameBasedLocators();
this.minimalRebuildCache = compilerContext.getMinimalRebuildCache();
if (incrementalCompile) {
this.staleTypeNames =
minimalRebuildCache.computeAndClearStaleTypesCache(logger, program.typeOracle);
checkPreambleTypesStillFresh(logger);
}
// Magical methods are implemented by replacing their bodies during unification.
replacementValueByMagicMethodQualifiedName =
ImmutableMap.builder()
.put(GWT_IS_CLIENT, JBooleanLiteral.TRUE)
.put(OLD_GWT_IS_CLIENT, JBooleanLiteral.TRUE)
.put(GWT_IS_PROD_MODE, JBooleanLiteral.TRUE)
.put(OLD_GWT_IS_PROD_MODE, JBooleanLiteral.TRUE)
.put(GWT_IS_SCRIPT, JBooleanLiteral.TRUE)
.put(OLD_GWT_IS_SCRIPT, JBooleanLiteral.TRUE)
.put(
CLASS_DESIRED_ASSERTION_STATUS,
JBooleanLiteral.get(compilerContext.getOptions().isEnableAssertions()))
.put(
CLASS_IS_CLASS_METADATA_ENABLED,
JBooleanLiteral.get(!compilerContext.getOptions().isClassMetadataDisabled()))
.build();
}
public void addRootTypes(Collection rootTypeSourceNames) {
assert this.rootTypeSourceNames.isEmpty();
this.rootTypeSourceNames.addAll(rootTypeSourceNames);
}
/**
* Special AST construction, useful for tests. Everything is resolved,
* translated, and unified.
*/
public void buildEverything() throws UnableToCompleteException {
for (String internalName : compiledClassesByInternalName.keySet()) {
String typeName = InternalName.toBinaryName(internalName);
internalFindType(typeName, binaryNameBasedTypeLocator, true);
}
for (JDeclaredType type : program.getDeclaredTypes()) {
fullFlowIntoType(type);
}
mainLoop();
computeOverrides();
if (errorsFound) {
throw new UnableToCompleteException();
}
JavaAstVerifier.assertProgramIsConsistent(program);
}
/**
* Translates and stitches (unifies) type ASTs into one connected graph.
*
* Only types reachable from entry points are traversed. This speeds, saves memory trims
* unreferenced elements.
*/
public void exec() throws UnableToCompleteException {
// Trace execution from entry points and resolve references.
List entryMethodNames = Lists.newArrayList();
for (JMethod entryMethod : program.getEntryMethods()) {
flowInto(entryMethod);
entryMethodNames.add(entryMethod.getJsniSignature(true, true));
}
// Ensure that root types are loaded and possibly (depending on mode) traversed.
List rootTypeBinaryNames = Lists.newArrayList();
for (String rootTypeSourceName : rootTypeSourceNames) {
JDeclaredType rootType =
internalFindType(rootTypeSourceName, sourceNameBasedTypeLocator, true);
if (rootType == null) {
continue;
}
rootTypeBinaryNames.add(rootType.getName());
if (rootType.hasJsInteropEntryPoints()) {
fullFlowIntoType(rootType);
}
}
minimalRebuildCache.setRootTypeNames(rootTypeBinaryNames);
minimalRebuildCache.setEntryMethodNames(entryMethodNames);
// Some fields and methods in codegen types might only become referenced as the result of
// visitor execution after unification. Since we don't want those fields are methods to be
// prematurely pruned here we defensively trace them now.
for (JClassType type : program.codeGenTypes) {
flowInto(type);
}
// Make sure that the rewriting pass for the types that are represented as natives have the
// needed members available.
for (JDeclaredType type : program.getRepresentedAsNativeTypes()) {
flowInto(type);
}
if (incrementalCompile) {
fullFlowIntoRemainingStaleTypes();
}
/*
* Since we're not actually optimizing here, it's easier to just visit
* certain things up front instead of duplicating the exacting semantics of
* ControlFlowAnalyzer.
*/
// String literals.
instantiate(program.getTypeJavaLangString());
// ControlFlowAnalyzer.rescueByConcat().
flowInto(program.getIndexedMethod(RuntimeConstants.OBJECT_TO_STRING));
flowInto((JMethod)
resolvedMembersByQualifiedName.get("java.lang.String.valueOf(C)Ljava/lang/String;"));
// FixAssignmentsToUnboxOrCast
AutoboxUtils autoboxUtils = new AutoboxUtils(program);
for (JMethod method : autoboxUtils.getBoxMethods()) {
flowInto(method);
}
for (JMethod method : autoboxUtils.getUnboxMethods()) {
flowInto(method);
}
// ReplaceRunAsyncs
if (compilerContext.getOptions().isRunAsyncEnabled()) {
flowInto(program.getIndexedMethod(RuntimeConstants.ASYNC_FRAGMENT_LOADER_ON_LOAD));
flowInto(program.getIndexedMethod(RuntimeConstants.ASYNC_FRAGMENT_LOADER_RUN_ASYNC));
}
// ImplementClassLiteralsAsFields
staticInitialize(program.getTypeClassLiteralHolder());
for (JMethod method : program.getTypeJavaLangClass().getMethods()) {
if (method.isStatic() && method.getName().startsWith("createFor")) {
flowInto(method);
}
}
mainLoop();
if (incrementalCompile) {
int declaredTypesInModule = program.getModuleDeclaredTypes().size();
MetricName.DECLARED_TYPES_IN_MODULE.setAmount(logger, declaredTypesInModule);
logger.log(TreeLogger.INFO, "Unification traversed " + liveFieldsAndMethods.size()
+ " fields and methods and " + program.getDeclaredTypes().size() + " types. "
+ declaredTypesInModule
+ " are considered part of the current module and " + fullFlowTypes.size()
+ " had all of their fields and methods traversed.");
Set remainingStaleTypeNames = computeRemainingStaleTypeNames();
if (!remainingStaleTypeNames.isEmpty()) {
logger.log(TreeLogger.WARN, "Some stale types (" + remainingStaleTypeNames
+ ") were not reprocessed as was expected. This is either a compiler bug or a "
+ "Generator has legitimately stopped creating these types.");
}
// Record the list of names of stale types that were processed, for test assertion purposes.
minimalRebuildCache.setProcessedStaleTypeNames(fullFlowTypes);
}
// Compute overrides before pruning, otherwise if a parent class method is pruned an overriding
// child class method might not look like an override.
List newStubMethods = computeOverrides();
// Make sure the created methods have the right liveness computation and don't get incorrectly
// pruned.
for (JMethod method : newStubMethods) {
if (instantiatedTypes.contains(method.getEnclosingType()) &&
liveVirtualMethods.contains(method.getSignature())) {
liveFieldsAndMethods.add(method);
}
}
if (!incrementalCompile) {
// Post-stitching clean-ups.
pruneDeadFieldsAndMethods();
}
if (errorsFound) {
// Already logged.
throw new UnableToCompleteException();
}
JavaAstVerifier.assertProgramIsConsistent(program);
}
/**
* Attempts to eagerly load and traverse all remaining known-stale types.
*
* Some types may not exist till after some Generator execution so missing types will be
* temporarily ignored.
*/
private void fullFlowIntoRemainingStaleTypes() {
for (String staleTypeName : computeRemainingStaleTypeNames()) {
JDeclaredType staleType =
internalFindType(staleTypeName, binaryNameBasedTypeLocator, false);
if (staleType == null) {
// The type is Generator output and so is not usually available in the list of types
// provided from initial JDT compilation. The staleness marking process has already
// handled this type by cascading the staleness marking onto the types that contain the
// GWT.create() calls that process that create this type.
continue;
}
// It's possible that the type was previously loaded before it was discovered to be stale (it
// became stale as a result of a Generator execution). If this happens then the type will have
// already been marked "reference only" in JProgram. This needs to be undone.
program.removeReferenceOnlyType(staleType);
// Make sure that the entire type is traversed.
fullFlowIntoType(staleType);
}
}
private void pruneDeadFieldsAndMethods() {
assert !incrementalCompile;
for (JDeclaredType type : program.getDeclaredTypes()) {
// Remove dead fields.
for (int fieldIndex = 0; fieldIndex < type.getFields().size(); ++fieldIndex) {
JField field = type.getFields().get(fieldIndex);
if (!liveFieldsAndMethods.contains(field)) {
type.removeField(fieldIndex);
--fieldIndex;
}
}
// Empty the body of dead clinits.
JMethod clinit = type.getClinitMethod();
if (!liveFieldsAndMethods.contains(clinit)) {
clinit.setBody(new JMethodBody(SourceOrigin.UNKNOWN));
}
// Remove dead methods, but never remove clinit.
for (int methodIndex = 1; methodIndex < type.getMethods().size(); ++methodIndex) {
JMethod method = type.getMethods().get(methodIndex);
// Pruning dead methods from the override list can only be done accurately in
// non-incremental compiles because of differences in which types are loaded and thus
// which methods are considered live.
Iterables.removeIf(method.getOverriddenMethods(),
Predicates.not(Predicates.in(liveFieldsAndMethods)));
Iterables.removeIf(method.getOverridingMethods(),
Predicates.not(Predicates.in(liveFieldsAndMethods)));
if (!liveFieldsAndMethods.contains(method)) {
type.removeMethod(methodIndex);
--methodIndex;
}
}
}
}
private void assimilateSourceUnit(CompilationUnit unit, boolean reportErrors) {
if (unit.isError()) {
if (reportErrors && unitsWithErrorsAlreadyReported.add(unit)) {
CompilationProblemReporter.reportErrors(logger, unit, false);
CompilationProblemReporter.logErrorTrace(logger, TreeLogger.ERROR,
compilerContext, unit.getTypeName(), true);
errorsFound = true;
}
return;
}
// Staleness calculations need to be able to trace from CompilationUnit name to the names of
// immediately nested types. So record those associations now.
if (incrementalCompile) {
compilerContext.getMinimalRebuildCache().recordNestedTypeNamesPerType(unit);
}
// TODO(zundel): ask for a recompile if deserialization fails?
List types = unit.getTypes();
assert containsAllTypes(unit, types);
for (JDeclaredType type : types) {
program.addType(type);
// If we're compiling per file and we already have currently valid output for this type.
if (incrementalCompile && !needsNewJs(type)) {
// Then make sure we don't output new Js for this type.
program.addReferenceOnlyType(type);
}
}
for (JDeclaredType type : types) {
resolveType(type);
processType(type);
}
// When compiling per file.
if (incrementalCompile) {
// It's possible that a users' edits have made a type referenceable that was not previously
// referenceable.
for (JDeclaredType type : types) {
// Such a type won't have any cached JS and will need a full traversal to ensure it is
// output (the full type with all fields and methods) as new JS.
if (needsNewJs(type)) {
fullFlowIntoType(type);
}
}
}
for (JDeclaredType type : types) {
/*
* Eagerly instantiate any type that requires devirtualization, i.e. String and
* JavaScriptObject subtypes. That way we don't have to copy the exact semantics of
* ControlFlowAnalyzer.
*/
if (requiresDevirtualization(type)) {
instantiate(type);
}
/*
* We also flow into the types with JsInterop entry point because our first pass on root types
* with JsInterop entry points are missing these inner classes. For native types this ensures
* that the constructor is considered reachable as it might be needed later for instanceof
* and casts.
*/
if (type.hasJsInteropEntryPoints() || type.isJsNative() || type.isJsFunction()) {
fullFlowIntoType(type);
}
}
}
/**
* Ensure that if any preamble types have become stale then adequate steps are taken to ensure the
* recreation of the entire preamble chunk.
*/
private void checkPreambleTypesStillFresh(TreeLogger logger) {
SetView stalePreambleTypes =
Sets.intersection(staleTypeNames, minimalRebuildCache.getPreambleTypeNames());
if (!stalePreambleTypes.isEmpty()) {
// Stale preamble types can't be gracefully replaced. We need to clear all per-file compile
// related caches to force a full build.
logger.log(TreeLogger.WARN,
"Some preamble types became stale. Recreating them is forcing a full "
+ "recompile. Stale preamble types: " + stalePreambleTypes + ".");
minimalRebuildCache.clearPerTypeJsCache();
staleTypeNames.clear();
// TODO: might be able to preserve the cache of all non-stale and non-preamble types.
}
}
/**
* Compute all overrides.
*/
private List computeOverrides() {
return new ComputeOverridesAndImplementDefaultMethods().exec(program);
}
private Set computeRemainingStaleTypeNames() {
return Sets.newHashSet(Sets.difference(staleTypeNames, processedStaleTypeNames));
}
private boolean containsAllTypes(CompilationUnit unit, List types) {
Set binaryTypeNames = Sets.newHashSet();
for (JDeclaredType type : types) {
binaryTypeNames.add(type.getName());
}
for (CompiledClass cc : unit.getCompiledClasses()) {
if (!binaryTypeNames.contains(InternalName.toBinaryName(cc.getInternalName()))) {
return false;
}
}
return true;
}
private void error(JNode x, String errorMessage) {
error(x.getSourceInfo(), errorMessage);
}
private void error(SourceInfo sourceInfo, String errorMessage) {
errorsFound = true;
TreeLogger branch =
logger
.branch(TreeLogger.ERROR, "Errors in '" + sourceInfo.getFileName() + "'", null);
// Append 'Line #: msg' to the error message.
StringBuilder msgBuf = new StringBuilder();
int line = sourceInfo.getStartLine();
if (line > 0) {
msgBuf.append("Line ");
msgBuf.append(line);
msgBuf.append(": ");
}
msgBuf.append(errorMessage);
branch.log(TreeLogger.ERROR, msgBuf.toString());
}
/**
* Resolves all fields and methods in the given type and marks it instantiable.
*
* The net effect is to ensure the entire type is kept and inserted into the unified AST.
*/
private void fullFlowIntoType(JDeclaredType type) {
String typeName = type.getName();
if (fullFlowTypes.contains(typeName) || typeName.endsWith("package-info")) {
return;
}
// The traversal of this type will accumulate rebinder type to rebound type associations, but
// the accumulation should start from scratch, so clear any existing associations that might
// have been collected in previous compiles.
minimalRebuildCache.clearRebinderTypeAssociations(typeName);
fullFlowTypes.add(typeName);
// Remove the type from the remaining stale types set so that the fullFlowIntoStaleTypes()
// attempt is shorter.
processedStaleTypeNames.add(typeName);
instantiate(type);
flowInto(type);
}
private void flowInto(JDeclaredType type) {
for (JMethod method : type.getMethods()) {
flowInto(method);
}
for (JField field : type.getFields()) {
flowInto(field);
}
}
private void flowInto(JField field) {
if (field.isExternal()) {
assert errorsFound;
return;
}
if (field == JField.NULL_FIELD) {
return;
}
if (liveFieldsAndMethods.contains(field)) {
// already flown into.
return;
}
liveFieldsAndMethods.add(field);
field.setType(translate(field.getType()));
if (field.isStatic()) {
staticInitialize(field.getEnclosingType());
}
}
private void flowInto(JMethod method) {
if (method.isExternal()) {
assert errorsFound;
return;
}
if (method == JMethod.NULL_METHOD) {
return;
}
if (liveFieldsAndMethods.contains(method)) {
return;
}
liveFieldsAndMethods.add(method);
method.resolve(
translate(method.getOriginalReturnType()),
translate(method.getOriginalParamTypes()),
translate(method.getType()),
translate(method.getThrownExceptions()));
if (method.isStatic()) {
staticInitialize(method.getEnclosingType());
} else if (method.canBePolymorphic()) {
String signature = method.getSignature();
if (!liveVirtualMethods.contains(signature)) {
liveVirtualMethods.add(signature);
for (JMethod pendingMethod : pendingVirtualMethodsBySignature.removeAll(signature)) {
assert instantiatedTypes.contains(pendingMethod.getEnclosingType());
flowInto(pendingMethod);
}
}
}
resolveSpecialization(method);
// Queue up the method to resolve the method body.
methodsPending.add(method);
}
private void resolveSpecialization(JMethod method) {
// TODO (cromwellian): Move to GwtAstBuilder eventually
if (method.getSpecialization() == null) {
return;
}
Specialization specialization = method.getSpecialization();
if (specialization.getParams() == null) {
logger.log(Type.ERROR, "Missing 'params' attribute at @SpecializeMethod for method "
+ method.getQualifiedName());
errorsFound = true;
return;
}
List resolvedParams = translate(specialization.getParams());
JType resolvedReturn = translate(specialization.getReturns());
String targetMethodSignature = JjsUtils.computeSignature(
specialization.getTarget(), resolvedParams, resolvedReturn, false);
JMethod targetMethod = JMethod.getExternalizedMethod(
method.getEnclosingType().getName(), targetMethodSignature, false);
JMethod resolvedTargetMethod = translate(method.getSourceInfo(), targetMethod);
if (resolvedTargetMethod.isExternal()) {
error(method.getSourceInfo(), "Unable to locate @SpecializeMethod target "
+ targetMethodSignature + " for method " + method.getQualifiedName());
return;
}
flowInto(resolvedTargetMethod);
specialization.resolve(resolvedParams, resolvedReturn, resolvedTargetMethod);
}
public NameBasedTypeLocator getSourceNameBasedTypeLocator() {
return sourceNameBasedTypeLocator;
}
private void initializeNameBasedLocators() {
sourceNameBasedTypeLocator = new NameBasedTypeLocator(compiledClassesBySourceName) {
@Override
protected boolean hasCompileErrors(String sourceName) {
return compilerContext.getCompilationErrorsIndex().hasCompileErrors(sourceName);
}
@Override
protected void logErrorTrace(TreeLogger branch, Type logLevel, String sourceName) {
CompilationProblemReporter.logErrorTrace(branch, logLevel, compilerContext, sourceName,
false);
}
};
binaryNameBasedTypeLocator = new NameBasedTypeLocator(null) {
@Override
protected CompilationUnit getCompilationUnitFromSource(String binaryName) {
// There is no binary name based index for this, use the internal name based one instead.
return internalNameBasedTypeLocator.getCompilationUnitFromSource(
BinaryName.toInternalName(binaryName));
}
@Override
protected boolean sourceCompilationUnitIsAvailable(String binaryName) {
// There is no binary name based index for this, use the internal name based one instead.
return internalNameBasedTypeLocator.sourceCompilationUnitIsAvailable(
BinaryName.toInternalName(binaryName));
}
@Override
protected boolean hasCompileErrors(String binaryName) {
return sourceNameBasedTypeLocator.hasCompileErrors(
BinaryName.toSourceName(binaryName));
}
@Override
protected void logErrorTrace(TreeLogger branch, Type logLevel, String binaryName) {
sourceNameBasedTypeLocator.logErrorTrace(branch, logLevel,
BinaryName.toSourceName(binaryName));
}
};
internalNameBasedTypeLocator = new NameBasedTypeLocator(compiledClassesByInternalName) {
@Override
protected JDeclaredType getResolvedType(String internalName) {
// There is no internal name based index for this, use the binary name based one instead.
return binaryNameBasedTypeLocator.getResolvedType(InternalName.toBinaryName(internalName));
}
@Override
protected boolean resolvedTypeIsAvailable(String internalName) {
// There is no internal name based index for this, use the binary name based one instead.
return binaryNameBasedTypeLocator.resolvedTypeIsAvailable(
InternalName.toBinaryName(internalName));
}
@Override
protected boolean hasCompileErrors(String internalName) {
return sourceNameBasedTypeLocator.hasCompileErrors(
InternalName.toSourceName(internalName));
}
@Override
protected void logErrorTrace(TreeLogger branch, Type logLevel, String internalName) {
sourceNameBasedTypeLocator.logErrorTrace(branch, logLevel,
BinaryName.toSourceName(internalName));
}
};
}
private void instantiate(JDeclaredType type) {
// Don't flow into all the parts of types defined outside this compile; except when the type is
// requires devirtualization (JSOs, Strings, etc) in which case the original (non devirtualized)
// methods may not be reachable anymore.
if (program.isReferenceOnly(type) && !requiresDevirtualization(type)) {
return;
}
if (type.isExternal()) {
assert errorsFound;
return;
}
if (instantiatedTypes.contains(type)) {
return;
}
instantiatedTypes.add(type);
if (type.getSuperClass() != null) {
instantiate(translate(type.getSuperClass()));
}
for (JInterfaceType intf : type.getImplements()) {
instantiate(translate(intf));
}
staticInitialize(type);
// Flow into any reachable virtual methods.
for (JMethod method : type.getMethods()) {
if (method.canBeReferencedExternally()) {
flowInto(method);
continue;
}
if (!method.canBePolymorphic()) {
continue;
}
String signature = method.getSignature();
if (liveVirtualMethods.contains(signature)) {
assert !pendingVirtualMethodsBySignature.containsKey(signature);
flowInto(method);
} else {
pendingVirtualMethodsBySignature.put(signature, method);
}
}
for (JField field : type.getFields()) {
if (field.canBeReferencedExternally()) {
flowInto(field);
}
}
}
private boolean requiresDevirtualization(JDeclaredType type) {
// NOTE: these types are the ones {@link Devirtualizer} handles.
return isJso(type) || type.isJsNative() ||
// Use the version that takes names instead of instances as some
// relevant instances might have not been leaded yet.
JProgram.isRepresentedAsNative(type.getName());
}
private boolean isJso(JDeclaredType type) {
if (type == null) {
return false;
}
return type == program.getJavaScriptObject() || isJso(type.getSuperClass());
}
/**
* Main loop: run through the queue doing deferred resolution. We could have
* made this entirely recursive, but a work queue uses much less max stack.
*/
private void mainLoop() {
UnifyVisitor visitor = new UnifyVisitor();
while (!methodsPending.isEmpty()) {
visitor.accept(methodsPending.poll());
}
}
private void processType(JDeclaredType type) {
assert !type.isExternal();
for (JMember member : type.getMembers()) {
String qualifiedName = member.getQualifiedName();
resolvedMembersByQualifiedName.put(qualifiedName, member);
replaceMagicMethodBodies(member);
}
}
private void replaceMagicMethodBodies(JMember member) {
JExpression replacementExpression =
replacementValueByMagicMethodQualifiedName.get(member.getQualifiedName());
if (replacementExpression == null) {
// Not a special method that needs replacement
return;
}
JjsUtils.replaceMethodBody((JMethod) member, replacementExpression);
}
/**
* During per file compilation, returns whether the given type has cached JS that can be reused.
*/
private boolean needsNewJs(JDeclaredType type) {
String typeName = type.getName();
boolean hasOwnJs = minimalRebuildCache.hasJs(typeName);
boolean isPartOfPreamble = minimalRebuildCache.getPreambleTypeNames().contains(typeName);
return !hasOwnJs && !isPartOfPreamble;
}
private void resolveType(JDeclaredType type) {
assert !type.isExternal();
if (type.getEnclosingType() != null) {
type.setEnclosingType(translate(type.getEnclosingType()));
}
if (type instanceof JClassType && type.getSuperClass() != null) {
((JClassType) type).setSuperClass(translate(type.getSuperClass()));
}
List resolvedInterfaces = Lists.newArrayList();
for (JInterfaceType intf : type.getImplements()) {
resolvedInterfaces.add((JInterfaceType) translate(intf));
}
type.resolve(resolvedInterfaces, findPackageInfo(type));
}
private JDeclaredType findPackageInfo(JDeclaredType type) {
String packagePrefix = type.getName();
// Package prefix with trailing dot. Empty string if default package.
packagePrefix = packagePrefix.substring(0, packagePrefix.lastIndexOf('.') + 1);
String pkgInfoClassName = StringInterner.get().intern(packagePrefix + "package-info");
JDeclaredType pkgInfo = internalFindType(pkgInfoClassName, binaryNameBasedTypeLocator, false);
// package-info classes are loaded only for their package level annotations' possible effect on
// JsInterop configuration. They are not intended to be included in output.
if (pkgInfo != null) {
program.addReferenceOnlyType(pkgInfo);
}
return pkgInfo;
}
public JDeclaredType findType(String typeName, NameBasedTypeLocator nameBasedTypeLocator)
throws UnableToCompleteException {
JDeclaredType type = internalFindType(typeName, nameBasedTypeLocator, true);
if (errorsFound) {
// Already logged.
throw new UnableToCompleteException();
}
return type;
}
private JDeclaredType internalFindType(String typeName,
NameBasedTypeLocator nameBasedTypeLocator, boolean reportErrors) {
if (nameBasedTypeLocator.resolvedTypeIsAvailable(typeName)) {
// The type was already resolved.
return nameBasedTypeLocator.getResolvedType(typeName);
}
if (nameBasedTypeLocator.sourceCompilationUnitIsAvailable(typeName)) {
// Resolve from source.
assimilateSourceUnit(nameBasedTypeLocator.getCompilationUnitFromSource(typeName),
reportErrors);
return nameBasedTypeLocator.getResolvedType(typeName);
}
if (reportErrors) {
// The type could not be resolved as source; report the appropriate error.
if (nameBasedTypeLocator.hasCompileErrors(typeName)) {
TreeLogger branch = logger.branch(TreeLogger.ERROR, String.format(
"Type %s could not be referenced because it previously failed to "
+ "compile with errors:", typeName));
nameBasedTypeLocator.logErrorTrace(branch, TreeLogger.ERROR, typeName);
} else {
logger.log(TreeLogger.ERROR, String.format(
"Could not find %s in types compiled from source. Is the source glob too strict?",
typeName));
}
errorsFound = true;
}
return null;
}
private void staticInitialize(JDeclaredType type) {
if (type.isExternal()) {
assert errorsFound;
return;
}
JMethod clinit = type.getClinitMethod();
if (!liveFieldsAndMethods.contains(clinit)) {
flowInto(clinit);
if (type.getSuperClass() != null) {
staticInitialize(translate(type.getSuperClass()));
}
}
}
/**
* Replaces an external (stub) reference node to a particular type by the actual AST node if
* necessary.
*/
private T translate(T type) {
if (!type.isExternal()) {
return type;
}
T resolvedType = (T) internalFindType(type.getName(), binaryNameBasedTypeLocator, true);
if (resolvedType == null) {
assert errorsFound;
return type;
}
assert !resolvedType.isExternal();
return resolvedType;
}
/**
* Replaces an external (stub) reference node to a particular member by the actual AST node if
* necessary.
*/
private T translate(SourceInfo sourceInfo, T member) {
if (!member.isExternal()) {
return member;
}
JDeclaredType enclosingType = translate(member.getEnclosingType());
if (enclosingType.isExternal()) {
assert errorsFound;
return member;
}
String qualifiedName = member.getQualifiedName();
T resolvedMember = (T) resolvedMembersByQualifiedName.get(qualifiedName);
if (resolvedMember == null) {
error(sourceInfo, "Reference to '" + qualifiedName + "' could not be resolved");
return member;
}
assert !resolvedMember.isExternal();
return resolvedMember;
}
/**
* Replaces an external (stub) reference node to a particular type by the actual AST node if
* necessary.
*/
private JReferenceType translate(JReferenceType type) {
JReferenceType result = type.getUnderlyingType();
if (type instanceof JArrayType) {
JArrayType arrayType = (JArrayType) type;
result = program.getTypeArray(translate(arrayType.getElementType()));
} else if (type.isExternal()) {
assert type instanceof JDeclaredType : "Unknown external type " + type.getName();
result = translate((JDeclaredType) type);
}
assert !result.isExternal();
if (!type.canBeNull()) {
result = result.strengthenToNonNull();
}
return result;
}
private JType translate(JType type) {
if (type.isPrimitiveType()) {
return type;
}
return translate((JReferenceType) type);
}
private List translate(List types) {
List translatedTypes = Lists.newArrayListWithCapacity(types.size());
for (T type : types) {
translatedTypes.add((T) translate(type));
}
return translatedTypes;
}
}