blob: 3a7b7ad32a449463970b80e62effcf3784af0cae [file] [log] [blame]
/*
* Copyright 2010 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.client.impl.DoNotInline;
import com.google.gwt.core.client.impl.HasNoSideEffects;
import com.google.gwt.core.client.impl.SpecializeMethod;
import com.google.gwt.dev.CompilerContext;
import com.google.gwt.dev.javac.JSORestrictionsChecker;
import com.google.gwt.dev.javac.JdtUtil;
import com.google.gwt.dev.javac.JsInteropUtil;
import com.google.gwt.dev.javac.JsniMethod;
import com.google.gwt.dev.jdt.SafeASTVisitor;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.SourceOrigin;
import com.google.gwt.dev.jjs.ast.AccessModifier;
import com.google.gwt.dev.jjs.ast.JAbsentArrayDimension;
import com.google.gwt.dev.jjs.ast.JArrayLength;
import com.google.gwt.dev.jjs.ast.JArrayRef;
import com.google.gwt.dev.jjs.ast.JArrayType;
import com.google.gwt.dev.jjs.ast.JAssertStatement;
import com.google.gwt.dev.jjs.ast.JBinaryOperation;
import com.google.gwt.dev.jjs.ast.JBinaryOperator;
import com.google.gwt.dev.jjs.ast.JBlock;
import com.google.gwt.dev.jjs.ast.JBooleanLiteral;
import com.google.gwt.dev.jjs.ast.JBreakStatement;
import com.google.gwt.dev.jjs.ast.JCaseStatement;
import com.google.gwt.dev.jjs.ast.JCastOperation;
import com.google.gwt.dev.jjs.ast.JCharLiteral;
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.JContinueStatement;
import com.google.gwt.dev.jjs.ast.JDeclarationStatement;
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.jjs.ast.JDeclaredType.JsInteropType;
import com.google.gwt.dev.jjs.ast.JDoStatement;
import com.google.gwt.dev.jjs.ast.JDoubleLiteral;
import com.google.gwt.dev.jjs.ast.JEnumField;
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.JField.Disposition;
import com.google.gwt.dev.jjs.ast.JFieldRef;
import com.google.gwt.dev.jjs.ast.JFloatLiteral;
import com.google.gwt.dev.jjs.ast.JForStatement;
import com.google.gwt.dev.jjs.ast.JIfStatement;
import com.google.gwt.dev.jjs.ast.JInstanceOf;
import com.google.gwt.dev.jjs.ast.JIntLiteral;
import com.google.gwt.dev.jjs.ast.JInterfaceType;
import com.google.gwt.dev.jjs.ast.JLabel;
import com.google.gwt.dev.jjs.ast.JLabeledStatement;
import com.google.gwt.dev.jjs.ast.JLiteral;
import com.google.gwt.dev.jjs.ast.JLocal;
import com.google.gwt.dev.jjs.ast.JLocalRef;
import com.google.gwt.dev.jjs.ast.JLongLiteral;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JMethodBody;
import com.google.gwt.dev.jjs.ast.JMethodCall;
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.JNullType;
import com.google.gwt.dev.jjs.ast.JParameter;
import com.google.gwt.dev.jjs.ast.JParameterRef;
import com.google.gwt.dev.jjs.ast.JPostfixOperation;
import com.google.gwt.dev.jjs.ast.JPrefixOperation;
import com.google.gwt.dev.jjs.ast.JPrimitiveType;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JReferenceType;
import com.google.gwt.dev.jjs.ast.JReturnStatement;
import com.google.gwt.dev.jjs.ast.JStatement;
import com.google.gwt.dev.jjs.ast.JStringLiteral;
import com.google.gwt.dev.jjs.ast.JSwitchStatement;
import com.google.gwt.dev.jjs.ast.JThisRef;
import com.google.gwt.dev.jjs.ast.JThrowStatement;
import com.google.gwt.dev.jjs.ast.JTryStatement;
import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.jjs.ast.JUnaryOperator;
import com.google.gwt.dev.jjs.ast.JVariable;
import com.google.gwt.dev.jjs.ast.JWhileStatement;
import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
import com.google.gwt.dev.jjs.ast.js.JsniClassLiteral;
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.js.JsAbstractSymbolResolver;
import com.google.gwt.dev.js.ast.JsContext;
import com.google.gwt.dev.js.ast.JsExpression;
import com.google.gwt.dev.js.ast.JsFunction;
import com.google.gwt.dev.js.ast.JsModVisitor;
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.js.ast.JsNameRef;
import com.google.gwt.dev.js.ast.JsNode;
import com.google.gwt.dev.js.ast.JsParameter;
import com.google.gwt.dev.util.StringInterner;
import com.google.gwt.dev.util.collect.Stack;
import com.google.gwt.thirdparty.guava.common.base.Function;
import com.google.gwt.thirdparty.guava.common.base.Preconditions;
import com.google.gwt.thirdparty.guava.common.collect.Interner;
import com.google.gwt.thirdparty.guava.common.collect.Iterables;
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.Sets;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.AND_AND_Expression;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.AnnotationMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.ArrayAllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
import org.eclipse.jdt.internal.compiler.ast.ArrayReference;
import org.eclipse.jdt.internal.compiler.ast.AssertStatement;
import org.eclipse.jdt.internal.compiler.ast.Assignment;
import org.eclipse.jdt.internal.compiler.ast.BinaryExpression;
import org.eclipse.jdt.internal.compiler.ast.Block;
import org.eclipse.jdt.internal.compiler.ast.BreakStatement;
import org.eclipse.jdt.internal.compiler.ast.CaseStatement;
import org.eclipse.jdt.internal.compiler.ast.CastExpression;
import org.eclipse.jdt.internal.compiler.ast.CharLiteral;
import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess;
import org.eclipse.jdt.internal.compiler.ast.Clinit;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.CompoundAssignment;
import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ContinueStatement;
import org.eclipse.jdt.internal.compiler.ast.DoStatement;
import org.eclipse.jdt.internal.compiler.ast.DoubleLiteral;
import org.eclipse.jdt.internal.compiler.ast.EmptyStatement;
import org.eclipse.jdt.internal.compiler.ast.EqualExpression;
import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.ExtendedStringLiteral;
import org.eclipse.jdt.internal.compiler.ast.FalseLiteral;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldReference;
import org.eclipse.jdt.internal.compiler.ast.FloatLiteral;
import org.eclipse.jdt.internal.compiler.ast.ForStatement;
import org.eclipse.jdt.internal.compiler.ast.ForeachStatement;
import org.eclipse.jdt.internal.compiler.ast.FunctionalExpression;
import org.eclipse.jdt.internal.compiler.ast.IfStatement;
import org.eclipse.jdt.internal.compiler.ast.Initializer;
import org.eclipse.jdt.internal.compiler.ast.InstanceOfExpression;
import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
import org.eclipse.jdt.internal.compiler.ast.LabeledStatement;
import org.eclipse.jdt.internal.compiler.ast.LambdaExpression;
import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.jdt.internal.compiler.ast.LongLiteral;
import org.eclipse.jdt.internal.compiler.ast.MarkerAnnotation;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.NameReference;
import org.eclipse.jdt.internal.compiler.ast.NormalAnnotation;
import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
import org.eclipse.jdt.internal.compiler.ast.OR_OR_Expression;
import org.eclipse.jdt.internal.compiler.ast.OperatorIds;
import org.eclipse.jdt.internal.compiler.ast.PostfixExpression;
import org.eclipse.jdt.internal.compiler.ast.PrefixExpression;
import org.eclipse.jdt.internal.compiler.ast.QualifiedAllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedSuperReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedThisReference;
import org.eclipse.jdt.internal.compiler.ast.ReferenceExpression;
import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation;
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
import org.eclipse.jdt.internal.compiler.ast.StringLiteralConcatenation;
import org.eclipse.jdt.internal.compiler.ast.SuperReference;
import org.eclipse.jdt.internal.compiler.ast.SwitchStatement;
import org.eclipse.jdt.internal.compiler.ast.SynchronizedStatement;
import org.eclipse.jdt.internal.compiler.ast.ThisReference;
import org.eclipse.jdt.internal.compiler.ast.ThrowStatement;
import org.eclipse.jdt.internal.compiler.ast.TrueLiteral;
import org.eclipse.jdt.internal.compiler.ast.TryStatement;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.ast.UnaryExpression;
import org.eclipse.jdt.internal.compiler.ast.UnionTypeReference;
import org.eclipse.jdt.internal.compiler.ast.WhileStatement;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.IntersectionTypeBinding18;
import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
import org.eclipse.jdt.internal.compiler.lookup.MethodVerifier;
import org.eclipse.jdt.internal.compiler.lookup.NestedTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.SyntheticArgumentBinding;
import org.eclipse.jdt.internal.compiler.lookup.SyntheticMethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.lookup.VariableBinding;
import org.eclipse.jdt.internal.compiler.util.Util;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
/**
* Constructs a GWT Java AST from a single isolated compilation unit. The AST is
* not associated with any {@link com.google.gwt.dev.jjs.ast.JProgram} and will
* contain unresolved references.
*/
public class GwtAstBuilder {
public static final String CLINIT_NAME = "$clinit";
public static final String INIT_NAME = "$init";
public static final String STATIC_INIT_NAME = "$" + INIT_NAME;
public static final String OUTER_LAMBDA_PARAM_NAME = "$$outer_0";
/**
* Visit the JDT AST and produce our own AST. By the end of this pass, the
* produced AST should contain every piece of information we'll ever need
* about the code. The JDT nodes should never again be referenced after this.
*/
class AstVisitor extends SafeASTVisitor {
/**
* Collects JSNI references from native method bodies and replaces the ones referring to
* compile time constants by their corresponding constant value.
*/
private class JsniReferenceCollector extends JsModVisitor {
private final JsniMethodBody nativeMethodBody;
private JsniReferenceCollector(JsniMethodBody nativeMethodBody) {
this.nativeMethodBody = nativeMethodBody;
}
@Override
public void endVisit(JsNameRef x, JsContext ctx) {
if (!x.isJsniReference()) {
return;
}
String ident = x.getIdent();
Binding binding = jsniRefs.get(ident);
SourceInfo info = x.getSourceInfo();
assert binding != null;
if (binding instanceof TypeBinding) {
JType type = typeMap.get((TypeBinding) binding);
processClassLiteral(x, info, type, ctx);
} else if (binding instanceof FieldBinding) {
FieldBinding fieldBinding = (FieldBinding) binding;
if (isOptimizableCompileTimeConstant(fieldBinding)) {
// Replace any compile-time constants with the constant value of the field.
assert !ctx.isLvalue();
JExpression constant = getConstant(info, fieldBinding.constant());
JsExpression result = JjsUtils.translateLiteral((JLiteral) constant);
assert (result != null);
ctx.replaceMe(result);
} else {
// Normal: create a jsniRef.
JField field = typeMap.get(fieldBinding);
processField(x, info, field, ctx);
}
} else {
JMethod method = typeMap.get((MethodBinding) binding);
processMethod(x, info, method);
}
}
private void processClassLiteral(JsNameRef nameRef, SourceInfo info, JType type,
JsContext ctx) {
assert !ctx.isLvalue();
JsniClassLiteral classLiteral = new JsniClassLiteral(info, nameRef.getIdent(), type);
nativeMethodBody.addClassRef(classLiteral);
}
private void processField(JsNameRef nameRef, SourceInfo info, JField field, JsContext ctx) {
JsniFieldRef fieldRef =
new JsniFieldRef(info, nameRef.getIdent(), field, curClass.type, ctx.isLvalue());
nativeMethodBody.addJsniRef(fieldRef);
}
private void processMethod(JsNameRef nameRef, SourceInfo info, JMethod method) {
JsniMethodRef methodRef =
new JsniMethodRef(info, nameRef.getIdent(), method, javaLangObject);
nativeMethodBody.addJsniRef(methodRef);
}
}
/**
* Resolves the scope of JS identifiers solely within the scope of a method.
*/
private class JsParameterResolver extends JsAbstractSymbolResolver {
private final JsFunction jsFunction;
public JsParameterResolver(JsFunction jsFunction) {
this.jsFunction = jsFunction;
}
@Override
public void resolve(JsNameRef x) {
// Only resolve unqualified names
if (x.getQualifier() == null) {
JsName name = getScope().findExistingName(x.getIdent());
// Ensure that we're resolving a name from the function's parameters
JsNode node = name == null ? null : name.getStaticRef();
if (node instanceof JsParameter) {
JsParameter param = (JsParameter) node;
if (jsFunction.getParameters().contains(param)) {
x.resolve(name);
}
}
}
}
}
private final Stack<ClassInfo> classStack = new Stack<ClassInfo>();
private ClassInfo curClass = null;
private MethodInfo curMethod = null;
private final Stack<MethodInfo> methodStack = new Stack<MethodInfo>();
private final List<JNode> nodeStack = Lists.newArrayList();
@Override
public void endVisit(AllocationExpression x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
List<JExpression> arguments = popCallArgs(info, x.arguments, x.binding);
pushNewExpression(info, x, null, arguments, scope);
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(AND_AND_Expression x, BlockScope scope) {
pushBinaryOp(x, JBinaryOperator.AND);
}
@Override
public void endVisit(AnnotationMethodDeclaration x, ClassScope classScope) {
endVisit((MethodDeclaration) x, classScope);
}
@Override
public void endVisit(ArrayAllocationExpression x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JArrayType type = (JArrayType) typeMap.get(x.resolvedType);
if (x.initializer != null) {
// handled by ArrayInitializer.
} else {
// Annoyingly, JDT only visits non-null dims, so we can't popList().
List<JExpression> dims = Lists.newArrayList();
for (int i = x.dimensions.length - 1; i >= 0; --i) {
JExpression dimension = pop(x.dimensions[i]);
// can be null if index expression was empty
if (dimension == null) {
dimension = JAbsentArrayDimension.INSTANCE;
}
dims.add(dimension);
}
// Undo the stack reversal.
Collections.reverse(dims);
push(JNewArray.createDims(info, type, dims));
}
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(ArrayInitializer x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JArrayType type = (JArrayType) typeMap.get(x.resolvedType);
List<JExpression> expressions = pop(x.expressions);
push(JNewArray.createInitializers(info, type, expressions));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(ArrayReference x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JExpression position = pop(x.position);
JExpression receiver = pop(x.receiver);
push(new JArrayRef(info, receiver, position));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(AssertStatement x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JExpression exceptionArgument = pop(x.exceptionArgument);
JExpression assertExpression = pop(x.assertExpression);
push(new JAssertStatement(info, assertExpression, exceptionArgument));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(Assignment x, BlockScope scope) {
pushBinaryOp(x, JBinaryOperator.ASG);
}
@Override
public void endVisit(BinaryExpression x, BlockScope scope) {
JBinaryOperator op;
int binOp = (x.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT;
switch (binOp) {
case OperatorIds.LEFT_SHIFT:
op = JBinaryOperator.SHL;
break;
case OperatorIds.RIGHT_SHIFT:
op = JBinaryOperator.SHR;
break;
case OperatorIds.UNSIGNED_RIGHT_SHIFT:
op = JBinaryOperator.SHRU;
break;
case OperatorIds.PLUS:
if (javaLangString == typeMap.get(x.resolvedType)) {
op = JBinaryOperator.CONCAT;
} else {
op = JBinaryOperator.ADD;
}
break;
case OperatorIds.MINUS:
op = JBinaryOperator.SUB;
break;
case OperatorIds.REMAINDER:
op = JBinaryOperator.MOD;
break;
case OperatorIds.XOR:
op = JBinaryOperator.BIT_XOR;
break;
case OperatorIds.AND:
op = JBinaryOperator.BIT_AND;
break;
case OperatorIds.MULTIPLY:
op = JBinaryOperator.MUL;
break;
case OperatorIds.OR:
op = JBinaryOperator.BIT_OR;
break;
case OperatorIds.DIVIDE:
op = JBinaryOperator.DIV;
break;
case OperatorIds.LESS_EQUAL:
op = JBinaryOperator.LTE;
break;
case OperatorIds.GREATER_EQUAL:
op = JBinaryOperator.GTE;
break;
case OperatorIds.GREATER:
op = JBinaryOperator.GT;
break;
case OperatorIds.LESS:
op = JBinaryOperator.LT;
break;
default:
throw translateException(x, new InternalCompilerException(
"Unexpected operator for BinaryExpression"));
}
pushBinaryOp(x, op);
}
@Override
public void endVisit(Block x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JBlock block = popBlock(info, x.statements);
push(block);
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(BreakStatement x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
push(new JBreakStatement(info, getOrCreateLabel(info, x.label)));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(CaseStatement x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JExpression caseExpression = pop(x.constantExpression);
if (caseExpression != null && x.constantExpression.resolvedType.isEnum()) {
caseExpression = synthesizeCallToOrdinal(scope, info, caseExpression);
}
push(new JCaseStatement(info, caseExpression));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(CastExpression x, BlockScope scope) {
/**
* Our output of a ((A & I1 & I2) a) looks like this:
*
* ((A)(I1)(I2)a).
*/
try {
SourceInfo info = makeSourceInfo(x);
JType[] type = processCastType(x.resolvedType);
JExpression expression = pop(x.expression);
push(buildCastOperation(info, type, expression));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(CharLiteral x, BlockScope scope) {
try {
push(JCharLiteral.get(x.constant.charValue()));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(ClassLiteralAccess x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JType type = typeMap.get(x.targetType);
push(new JClassLiteral(info, type));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(CompoundAssignment x, BlockScope scope) {
JBinaryOperator op;
switch (x.operator) {
case OperatorIds.PLUS:
if (javaLangString == typeMap.get(x.resolvedType)) {
op = JBinaryOperator.ASG_CONCAT;
} else {
op = JBinaryOperator.ASG_ADD;
}
break;
case OperatorIds.MINUS:
op = JBinaryOperator.ASG_SUB;
break;
case OperatorIds.MULTIPLY:
op = JBinaryOperator.ASG_MUL;
break;
case OperatorIds.DIVIDE:
op = JBinaryOperator.ASG_DIV;
break;
case OperatorIds.AND:
op = JBinaryOperator.ASG_BIT_AND;
break;
case OperatorIds.OR:
op = JBinaryOperator.ASG_BIT_OR;
break;
case OperatorIds.XOR:
op = JBinaryOperator.ASG_BIT_XOR;
break;
case OperatorIds.REMAINDER:
op = JBinaryOperator.ASG_MOD;
break;
case OperatorIds.LEFT_SHIFT:
op = JBinaryOperator.ASG_SHL;
break;
case OperatorIds.RIGHT_SHIFT:
op = JBinaryOperator.ASG_SHR;
break;
case OperatorIds.UNSIGNED_RIGHT_SHIFT:
op = JBinaryOperator.ASG_SHRU;
break;
default:
throw translateException(x, new InternalCompilerException(
"Unexpected operator for CompoundAssignment"));
}
pushBinaryOp(x, op);
}
@Override
public void endVisit(ConditionalExpression x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JType type = typeMap.get(x.resolvedType);
JExpression valueIfFalse = pop(x.valueIfFalse);
JExpression valueIfTrue = pop(x.valueIfTrue);
JExpression condition = pop(x.condition);
push(new JConditional(info, type, condition, valueIfTrue, valueIfFalse));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(ConstructorDeclaration x, ClassScope scope) {
try {
List<JStatement> statements = pop(x.statements);
JStatement constructorCall = pop(x.constructorCall);
JBlock block = curMethod.body.getBlock();
SourceInfo info = curMethod.method.getSourceInfo();
/*
* Determine if we have an explicit this call. The presence of an
* explicit this call indicates we can skip certain initialization steps
* (as the callee will perform those steps for us). These skippable
* steps are 1) assigning synthetic args to fields and 2) running
* initializers.
*/
boolean hasExplicitThis = (x.constructorCall != null) && !x.constructorCall.isSuperAccess();
/*
* All synthetic fields must be assigned, unless we have an explicit
* this constructor call, in which case the callee will assign them for
* us.
*/
if (!hasExplicitThis) {
ReferenceBinding declaringClass = (ReferenceBinding) x.binding.declaringClass.erasure();
if (JdtUtil.isInnerClass(declaringClass)) {
NestedTypeBinding nestedBinding = (NestedTypeBinding) declaringClass;
if (nestedBinding.enclosingInstances != null) {
for (SyntheticArgumentBinding arg : nestedBinding.enclosingInstances) {
JBinaryOperation asg = assignSyntheticField(info, arg);
block.addStmt(asg.makeStatement());
}
}
if (nestedBinding.outerLocalVariables != null) {
for (SyntheticArgumentBinding arg : nestedBinding.outerLocalVariables) {
JBinaryOperation asg = assignSyntheticField(info, arg);
block.addStmt(asg.makeStatement());
}
}
}
}
if (constructorCall != null) {
block.addStmt(constructorCall);
}
/*
* Call the synthetic instance initializer method, unless we have an
* explicit this constructor call, in which case the callee will.
*/
if (!hasExplicitThis) {
JMethod initMethod = curClass.type.getInitMethod();
JMethodCall initCall = new JMethodCall(info, makeThisRef(info), initMethod);
block.addStmt(initCall.makeStatement());
}
// user code (finally!)
block.addStmts(statements);
popMethodInfo();
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(ContinueStatement x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
push(new JContinueStatement(info, getOrCreateLabel(info, x.label)));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(DoStatement x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JExpression condition = pop(x.condition);
JStatement action = pop(x.action);
push(new JDoStatement(info, condition, action));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(DoubleLiteral x, BlockScope scope) {
try {
push(JDoubleLiteral.get(x.constant.doubleValue()));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(EmptyStatement x, BlockScope scope) {
push(null);
}
@Override
public void endVisit(EqualExpression x, BlockScope scope) {
JBinaryOperator op;
switch ((x.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT) {
case OperatorIds.EQUAL_EQUAL:
op = JBinaryOperator.EQ;
break;
case OperatorIds.NOT_EQUAL:
op = JBinaryOperator.NEQ;
break;
default:
throw translateException(x, new InternalCompilerException(
"Unexpected operator for EqualExpression"));
}
pushBinaryOp(x, op);
}
@Override
public void endVisit(ExplicitConstructorCall x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JConstructor ctor = (JConstructor) typeMap.get(x.binding);
JExpression trueQualifier = makeThisRef(info);
JMethodCall call = new JMethodCall(info, trueQualifier, ctor);
List<JExpression> callArgs = popCallArgs(info, x.arguments, x.binding);
if (curClass.classType.isEnumOrSubclass() != null) {
// Enums: wire up synthetic name/ordinal params to the super method.
JParameterRef enumNameRef = new JParameterRef(info, curMethod.method.getParams().get(0));
call.addArg(enumNameRef);
JParameterRef enumOrdinalRef =
new JParameterRef(info, curMethod.method.getParams().get(1));
call.addArg(enumOrdinalRef);
}
if (x.isSuperAccess()) {
JExpression qualifier = pop(x.qualification);
ReferenceBinding superClass = x.binding.declaringClass;
boolean nestedSuper = JdtUtil.isInnerClass(superClass);
if (nestedSuper) {
processSuperCallThisArgs(superClass, call, qualifier, x.qualification);
}
call.addArgs(callArgs);
if (nestedSuper) {
processSuperCallLocalArgs(superClass, call);
}
} else {
assert (x.qualification == null);
ReferenceBinding declaringClass = x.binding.declaringClass;
boolean nested = JdtUtil.isInnerClass(declaringClass);
if (nested) {
processThisCallThisArgs(declaringClass, call);
}
call.addArgs(callArgs);
if (nested) {
processThisCallLocalArgs(declaringClass, call);
}
}
call.setStaticDispatchOnly();
push(call.makeStatement());
} catch (Throwable e) {
throw translateException(x, e);
} finally {
scope.methodScope().isConstructorCall = false;
}
}
@Override
public void endVisit(ExtendedStringLiteral x, BlockScope scope) {
endVisit((StringLiteral) x, scope);
}
@Override
public void endVisit(FalseLiteral x, BlockScope scope) {
push(JBooleanLiteral.FALSE);
}
@Override
public void endVisit(FieldDeclaration x, MethodScope scope) {
try {
JExpression initialization = pop(x.initialization);
JField field = typeMap.get(x.binding);
if (field instanceof JEnumField) {
// An enum field must be initialized!
assert (initialization instanceof JNewInstance);
}
if (initialization != null) {
SourceInfo info = makeSourceInfo(x);
JExpression instance = null;
if (!x.isStatic()) {
instance = makeThisRef(info);
}
// JDeclarationStatement's ctor sets up the field's initializer.
JStatement decl =
new JDeclarationStatement(info, new JFieldRef(info, instance, field, curClass.type),
initialization);
// will either be init or clinit
curMethod.body.getBlock().addStmt(decl);
}
popMethodInfo();
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(FieldReference x, BlockScope scope) {
try {
FieldBinding fieldBinding = x.binding;
SourceInfo info = makeSourceInfo(x);
JExpression instance = pop(x.receiver);
JExpression expr;
if (fieldBinding.declaringClass == null) {
if (!ARRAY_LENGTH_FIELD.equals(String.valueOf(fieldBinding.name))) {
throw new InternalCompilerException("Expected [array].length.");
}
expr = new JArrayLength(info, instance);
} else {
JField field = typeMap.get(fieldBinding);
expr = new JFieldRef(info, instance, field, curClass.type);
}
if (x.genericCast != null) {
JType castType = typeMap.get(x.genericCast);
/*
* Note, this may result in an invalid AST due to an LHS cast
* operation. We fix this up in FixAssignmentsToUnboxOrCast.
*/
expr = maybeCast(castType, expr);
}
push(expr);
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(FloatLiteral x, BlockScope scope) {
try {
push(JFloatLiteral.get(x.constant.floatValue()));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(ForeachStatement x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JBlock body = popBlock(info, x.action);
JExpression collection = pop(x.collection);
JDeclarationStatement elementDecl = pop(x.elementVariable);
assert (elementDecl.initializer == null);
JLocal elementVar = (JLocal) curMethod.locals.get(x.elementVariable.binding);
String elementVarName = elementVar.getName();
JForStatement result;
if (x.collectionVariable != null) {
/**
* <pre>
* for (final T[] i$array = collection,
* int i$index = 0,
* final int i$max = i$array.length;
* i$index < i$max; ++i$index) {
* T elementVar = i$array[i$index];
* // user action
* }
* </pre>
*/
JLocal arrayVar = JProgram.createLocal(info, elementVarName + "$array",
typeMap.get(x.collection.resolvedType), true, curMethod.body);
JLocal indexVar =
JProgram.createLocal(info, elementVarName + "$index", JPrimitiveType.INT, false,
curMethod.body);
JLocal maxVar =
JProgram.createLocal(info, elementVarName + "$max", JPrimitiveType.INT, true,
curMethod.body);
List<JStatement> initializers = Lists.newArrayListWithCapacity(3);
// T[] i$array = arr
initializers.add(makeDeclaration(info, arrayVar, collection));
// int i$index = 0
initializers.add(makeDeclaration(info, indexVar, JIntLiteral.get(0)));
// int i$max = i$array.length
initializers.add(makeDeclaration(info, maxVar, new JArrayLength(info, new JLocalRef(info,
arrayVar))));
// i$index < i$max
JExpression condition =
new JBinaryOperation(info, JPrimitiveType.BOOLEAN, JBinaryOperator.LT, new JLocalRef(
info, indexVar), new JLocalRef(info, maxVar));
// ++i$index
JExpression increments = new JPrefixOperation(info, JUnaryOperator.INC,
new JLocalRef(info, indexVar));
// T elementVar = i$array[i$index];
elementDecl.initializer =
new JArrayRef(info, new JLocalRef(info, arrayVar), new JLocalRef(info, indexVar));
body.addStmt(0, elementDecl);
result = new JForStatement(info, initializers, condition, increments, body);
} else {
/**
* <pre>
* for (Iterator&lt;T&gt; i$iterator = collection.iterator(); i$iterator.hasNext();) {
* T elementVar = i$iterator.next();
* // user action
* }
* </pre>
*/
CompilationUnitScope cudScope = scope.compilationUnitScope();
ReferenceBinding javaUtilIterator = scope.getJavaUtilIterator();
ReferenceBinding javaLangIterable = scope.getJavaLangIterable();
MethodBinding iterator = javaLangIterable.getExactMethod(ITERATOR, NO_TYPES, cudScope);
MethodBinding hasNext = javaUtilIterator.getExactMethod(HAS_NEXT, NO_TYPES, cudScope);
MethodBinding next = javaUtilIterator.getExactMethod(NEXT, NO_TYPES, cudScope);
JLocal iteratorVar =
JProgram.createLocal(info, (elementVarName + "$iterator"), typeMap
.get(javaUtilIterator), false, curMethod.body);
List<JStatement> initializers = Lists.newArrayListWithCapacity(1);
// Iterator<T> i$iterator = collection.iterator()
initializers.add(makeDeclaration(info, iteratorVar, new JMethodCall(info, collection,
typeMap.get(iterator))));
// i$iterator.hasNext()
JExpression condition =
new JMethodCall(info, new JLocalRef(info, iteratorVar), typeMap.get(hasNext));
// T elementVar = (T) i$iterator.next();
elementDecl.initializer =
new JMethodCall(info, new JLocalRef(info, iteratorVar), typeMap.get(next));
// Perform any implicit reference type casts (due to generics).
// Note this occurs before potential unboxing.
if (elementVar.getType() != javaLangObject) {
TypeBinding collectionElementType = (TypeBinding) collectionElementTypeField.get(x);
JType toType = typeMap.get(collectionElementType);
assert (toType instanceof JReferenceType);
elementDecl.initializer = maybeCast(toType, elementDecl.initializer);
}
body.addStmt(0, elementDecl);
result = new JForStatement(info, initializers, condition,
null, body);
}
// May need to box or unbox the element assignment.
elementDecl.initializer =
maybeBoxOrUnbox(elementDecl.initializer, x.elementVariableImplicitWidening);
push(result);
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(ForStatement x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JStatement action = pop(x.action);
// JDT represents the 3rd for component (increments) as a list of statements. These
// statements are always expression statements as per JLS 14.14.1
// Here the List<JExpressionStatement> is transformed into a more adequate List<Expression>.
List<JExpression> incrementsExpressions = Lists.transform(pop(x.increments),
new Function<JStatement, JExpression>() {
@Override
public JExpression apply(JStatement statement) {
Preconditions.checkArgument(statement instanceof JExpressionStatement);
return ((JExpressionStatement) statement).getExpr();
}
});
// And turned into a single expression (possibly null if empty).
JExpression incrementsExpression =
singleExpressionFromExpressionList(info, incrementsExpressions);
JExpression condition = pop(x.condition);
List<JStatement> initializations = pop(x.initializations);
push(new JForStatement(info, initializations, condition, incrementsExpression, action));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(IfStatement x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JStatement elseStatement = pop(x.elseStatement);
JStatement thenStatement = pop(x.thenStatement);
JExpression condition = pop(x.condition);
push(new JIfStatement(info, condition, thenStatement, elseStatement));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(Initializer x, MethodScope scope) {
try {
JBlock block = pop(x.block);
if (block != null) {
curMethod.body.getBlock().addStmt(block);
}
popMethodInfo();
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(InstanceOfExpression x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JExpression expr = pop(x.expression);
JReferenceType testType = (JReferenceType) typeMap.get(x.type.resolvedType);
push(new JInstanceOf(info, testType, expr));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(IntLiteral x, BlockScope scope) {
try {
push(JIntLiteral.get(x.constant.intValue()));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(LabeledStatement x, BlockScope scope) {
try {
JStatement statement = pop(x.statement);
if (statement == null) {
push(null);
return;
}
SourceInfo info = makeSourceInfo(x);
push(new JLabeledStatement(info, getOrCreateLabel(info, x.label), statement));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public boolean visit(ReferenceExpression x, BlockScope blockScope) {
// T[][][]::new => lambda$n(int x) { return new T[int x][][]; }
if (x.isArrayConstructorReference()) {
// ensure array[]::new synthetic method (created by JDT) has an associated JMethod
JMethod synthMethod = typeMap.get(x.binding);
if (synthMethod.getBody() == null) {
JMethodBody body = new JMethodBody(synthMethod.getSourceInfo());
List<JExpression> dims = new ArrayList<JExpression>();
JArrayType arrayType = (JArrayType) synthMethod.getType();
JParameter dimParam = synthMethod.getParams().get(0);
JExpression dimArgExpr = new JParameterRef(dimParam.getSourceInfo(), dimParam);
dims.add(dimArgExpr);
for (int i = 1; i < arrayType.getDims(); i++) {
dims.add(JAbsentArrayDimension.INSTANCE);
}
JNewArray newArray = JNewArray.createDims(synthMethod.getSourceInfo(), arrayType, dims);
JReturnStatement retArray = new JReturnStatement(synthMethod.getSourceInfo(), newArray);
body.getBlock().addStmt(retArray);
synthMethod.setBody(body);
}
push(null); // no qualifier
}
return true;
}
@Override
public boolean visit(LambdaExpression x, BlockScope blockScope) {
// Fetch the variables 'captured' by this lambda
SyntheticArgumentBinding[] synthArgs = x.outerLocalVariables;
// Get the parameter names, captured locals + lambda arguments
String paramNames[] = computeCombinedParamNames(x, synthArgs);
SourceInfo info = makeSourceInfo(x);
// JDT synthesizes a method lambda$n(capture1, capture2, ..., lambda_arg1, lambda_arg2, ...)
// Here we create a JMethod from this
JMethod lambdaMethod = createSyntheticMethodFromBinding(info, x.binding,
paramNames);
JMethodBody methodBody = new JMethodBody(info);
lambdaMethod.setBody(methodBody);
// We need to push this method on the stack as it introduces a scope, and
// expressions in the body need to lookup variable refs like parameters from it
pushMethodInfo(new MethodInfo(lambdaMethod, methodBody, x.scope));
pushLambdaExpressionLocalsIntoMethodScope(x, synthArgs, lambdaMethod);
// now the body of the lambda is processed
return true;
}
private void pushLambdaExpressionLocalsIntoMethodScope(LambdaExpression x, SyntheticArgumentBinding[] synthArgs,
JMethod lambdaMethod) {
Iterator<JParameter> it = lambdaMethod.getParams().iterator();
if (synthArgs != null) {
MethodScope scope = x.getScope();
for (SyntheticArgumentBinding sa : synthArgs) {
VariableBinding[] path = scope.getEmulationPath(sa.actualOuterLocalVariable);
assert path.length == 1 && path[0] instanceof LocalVariableBinding;
JParameter param = it.next();
curMethod.locals.put((LocalVariableBinding) path[0], param);
}
for (Argument a : x.arguments) {
curMethod.locals.put(a.binding, it.next());
}
}
}
/**
* Calculate the names of all the parameters a lambda method will need, that is, the combination of all
* captured locals plus all arguments to the lambda expression.
*/
private String[] computeCombinedParamNames(LambdaExpression x, SyntheticArgumentBinding[] synthArgs) {
String[] paramNames;
paramNames = new String[x.binding.parameters.length];
int numSynthArgs = synthArgs != null ? synthArgs.length : 0;
for (int i = 0; i < paramNames.length; i++) {
if (i < numSynthArgs) {
paramNames[i] = nameForSyntheticArgument(synthArgs[i]);
} else {
paramNames[i] = nameForArgument(x.arguments, i - numSynthArgs, i);
}
}
return paramNames;
}
private String nameForArgument(Argument[] arguments, int argIndex, int argPosition) {
return new String(arguments[argIndex].name) + "_" + argPosition;
}
private String nameForSyntheticArgument(SyntheticArgumentBinding synthArg) {
return synthArg.actualOuterLocalVariable != null ?
intern(intern(synthArg.actualOuterLocalVariable.name) + "_" + synthArg.resolvedPosition) :
intern(synthArg.name);
}
@Override
public void endVisit(LambdaExpression x, BlockScope blockScope) {
/**
* Our output of a (args) -> expression_using_locals(locals) looks like this.
*
* class Enclosing {
*
* T lambda$0(locals, args) {...lambda expr }
*
* class lambda$0$type implements I {
* ctor([outer], locals) { ... }
* R <SAM lambdaMethod>(args) { return [outer].lambda$0(locals, args); }
* }
* }
*
* And replaces the lambda with new lambda$0$Type([outer this], captured locals...).
*/
// The target accepting this lambda is looking for which type? (e.g. ClickHandler, Runnable, etc)
TypeBinding binding = x.expectedType();
// Find the single abstract method of this interface
MethodBinding samBinding = binding.getSingleAbstractMethod(blockScope, false);
assert (samBinding != null && samBinding.isValidBinding());
// Lookup the JMethod version
JMethod interfaceMethod = typeMap.get(samBinding);
// And its JInterface container we must implement
// There may be more than more JInterface containers to be implemented
// if the lambda expression is cast to a IntersectionCastType.
JInterfaceType[] funcType;
if (binding instanceof IntersectionTypeBinding18) {
funcType = processIntersectionTypeForLambda((IntersectionTypeBinding18) binding, blockScope,
JdtUtil.signature(samBinding));
} else {
funcType = new JInterfaceType[] {(JInterfaceType) typeMap.get(binding)};
}
SourceInfo info = makeSourceInfo(x);
// Create an inner class to implement the interface and SAM method.
// class lambda$0$Type implements T {}
JClassType innerLambdaClass = createInnerClass(JdtUtil.asDottedString(x.binding.declaringClass.compoundName) +
"$" + new String(x.binding.selector), x, info, funcType);
JConstructor ctor = new JConstructor(info, innerLambdaClass, AccessModifier.PRIVATE);
// locals captured by the lambda and saved as fields on the anonymous inner class
List<JField> locals = new ArrayList<JField>();
SyntheticArgumentBinding[] synthArgs = x.outerLocalVariables;
// create the constructor for the anonymous inner and return the field used to store the enclosing 'this'
// which is needed by the SAM method implementation later
JField outerField =
createLambdaConstructor(x, info, innerLambdaClass, ctor, locals, synthArgs);
// the method containing the lambda expression that the anonymous inner class delegates to,
// it corresponds directly to the lambda expression itself, produced by JDT as a helper method
JMethod lambdaMethod = createLambdaMethod(x);
// Now that we've added an implementation method for the lambda, we must create the inner class method
// that implements the target interface type that delegates to the target lambda method
JMethod samMethod = new JMethod(info, interfaceMethod.getName(), innerLambdaClass, interfaceMethod.getType(),
false, false, true, interfaceMethod.getAccess());
// implements the SAM, e.g. Callback.onCallback(), Runnable.run(), etc
createLambdaSamMethod(x, interfaceMethod, info, innerLambdaClass, locals, outerField,
lambdaMethod,
samMethod);
ctor.freezeParamTypes();
samMethod.freezeParamTypes();
// replace (x,y,z) -> expr with 'new Lambda(args)'
replaceLambdaWithInnerClassAllocation(x, info, innerLambdaClass, ctor, synthArgs);
popMethodInfo();
// Add the newly generated type
newTypes.add(innerLambdaClass);
}
private void createLambdaSamMethod(LambdaExpression x, JMethod interfaceMethod, SourceInfo info,
JClassType innerLambdaClass, List<JField> locals, JField outerField, JMethod lambdaMethod,
JMethod samMethod) {
// The parameters to this method will be the same as the Java interface that must be implemented
for (JParameter origParam : interfaceMethod.getParams()) {
JType origType = origParam.getType();
samMethod.addParam(new JParameter(origParam.getSourceInfo(),
origParam.getName(), origType,
origParam.isFinal(), origParam.isThis(),
samMethod));
}
// Create a body like void onClick(ClickEvent e) { OuterClass.lambdaMethod(locals, e); }
JMethodBody samMethodBody = new JMethodBody(info);
// First we create the method call to the outer lambda method
JMethodCall samCall = new JMethodCall(info, x.shouldCaptureInstance ?
new JFieldRef(info, new JThisRef(info, innerLambdaClass), outerField, innerLambdaClass) :
null, lambdaMethod);
// and add any locals that were storing captured outer variables as arguments to the call first
for (JField localField : locals) {
samCall.addArg(new JFieldRef(info, new JThisRef(info, innerLambdaClass),
localField, innerLambdaClass));
}
// and now we propagate the rest of the actual interface method parameters on the end (e.g. ClickEvent e)
for (JParameter param : samMethod.getParams()) {
samCall.addArg(new JParameterRef(info, param));
}
// we either add a return statement, or don't, depending on what the interface wants
if (samMethod.getType() != JPrimitiveType.VOID) {
samMethodBody.getBlock().addStmt(new JReturnStatement(info, samCall));
} else {
samMethodBody.getBlock().addStmt(samCall.makeStatement());
}
samMethod.setBody(samMethodBody);
innerLambdaClass.addMethod(samMethod);
}
private JField createLambdaConstructor(LambdaExpression x, SourceInfo info,
JClassType innerLambdaClass, JConstructor ctor, List<JField> locals,
SyntheticArgumentBinding[] synthArgs) {
// Create a constructor to accept all "captured" locals
// CTor([OuterClassRef ref], capture1, capture2) { }
JMethodBody ctorBody = new JMethodBody(info);
JField outerField = null;
// if this lambda refers to fields on the enclosing instance
if (x.shouldCaptureInstance) {
// ctor($$outer) { this.$$outer = $$outer; }
outerField = createAndBindCapturedLambdaParameter(info, OUTER_LAMBDA_PARAM_NAME,
innerLambdaClass.getEnclosingType(),
ctor, ctorBody);
}
// Now we add parameters to the ctor
// this is the outer instance (if needed), plus any method local variables captured
String paramNames[] = new String[x.binding.parameters.length];
int numSynthArgs = synthArgs != null ? synthArgs.length : 0;
for (int i = 0; i < paramNames.length; i++) {
// Setup params, fields, and ctor assignments for the outer captured vars
if (i < numSynthArgs) {
paramNames[i] = nameForSyntheticArgument(synthArgs[i]);
JType captureType = typeMap.get(synthArgs[i].type);
// adds ctor(..., param, ...) { ...this.param = param }
JField captureField = createAndBindCapturedLambdaParameter(info, paramNames[i], captureType, ctor, ctorBody);
locals.add(captureField);
} else {
// Record the names of the actual closure arguments, e.g. (ClickEvent x) -> expr will be 'x'
paramNames[i] = nameForArgument(x.arguments, i - numSynthArgs, i);
}
}
ctor.setBody(ctorBody);
innerLambdaClass.addMethod(ctor);
return outerField;
}
private JMethod createLambdaMethod(LambdaExpression x) {
// First let's get that synthetic method we created in the visit() call on the containing class?
JMethod lambdaMethod = curMethod.method;
// And pop off the body nodes of the LambdaExpression that was processed as children
// Deal with any boxing/unboxing needed
JNode node = pop();
if (node instanceof JExpression) {
node = simplify((JExpression) node, (Expression) x.body);
}
JMethodBody body = (JMethodBody) curMethod.method.getBody();
// and copy those nodes into the body of our synthetic method
JStatement lambdaStatement = node instanceof JExpression ?
(((JExpression) node).getType() == JPrimitiveType.VOID ? ((JExpression) node).makeStatement() :
new JReturnStatement(node.getSourceInfo(), (JExpression) node)) : (JStatement) node;
body.getBlock().addStmt(lambdaStatement);
lambdaMethod.setBody(body);
return lambdaMethod;
}
private void replaceLambdaWithInnerClassAllocation(LambdaExpression x, SourceInfo info,
JClassType innerLambdaClass, JConstructor ctor, SyntheticArgumentBinding[] synthArgs) {
// Finally, we replace the LambdaExpression with
// new InnerLambdaClass(this, local1, local2, ...);
assert ctor.getEnclosingType() == innerLambdaClass;
JNewInstance allocLambda = new JNewInstance(info, ctor);
// only pass 'this' if lambda refers to fields on outer class
if (x.shouldCaptureInstance) {
allocLambda.addArg(new JThisRef(info, innerLambdaClass.getEnclosingType()));
}
for (SyntheticArgumentBinding sa : synthArgs) {
allocLambda.addArg(makeLocalRef(info, sa.actualOuterLocalVariable, methodStack.peek()));
}
// put the result on the stack, and pop out synthetic method from the scope
push(allocLambda);
}
private JField createAndBindCapturedLambdaParameter(SourceInfo info,
String paramName, JType captureType,
JConstructor ctor, JMethodBody ctorBody) {
JField paramField;
JParameter param = createLambdaParameter(info, paramName, captureType, ctor);
// Plus a field to store it
paramField = createLambdaField(info, paramName, captureType, ctor.getEnclosingType());
// Now add the initializers to bind the param to field
// this.paramField = param
JThisRef thisRef = new JThisRef(info, ctor.getEnclosingType());
JFieldRef paramFieldRef = new JFieldRef(info, thisRef, paramField, ctor.getEnclosingType());
JParameterRef paramRef = new JParameterRef(info, param);
ctorBody.getBlock().addStmt(
new JBinaryOperation(info, paramFieldRef.getType(),
JBinaryOperator.ASG,
paramFieldRef, paramRef).makeStatement());
return paramField;
}
private JField createLambdaField(SourceInfo info, String fieldName, JType fieldType,
JClassType enclosingType) {
JField outerField;
outerField = new JField(info, fieldName, enclosingType, fieldType, false, Disposition.NONE,
AccessModifier.PRIVATE);
enclosingType.addField(outerField);
return outerField;
}
private JParameter createLambdaParameter(SourceInfo info, String paramName, JType paramType,
JConstructor ctor) {
JParameter outerParam = new JParameter(info, paramName, paramType,
true, false, ctor);
ctor.addParam(outerParam);
return outerParam;
}
private JClassType createInnerClass(String name, FunctionalExpression x, SourceInfo info,
JInterfaceType... funcType) {
JClassType innerLambdaClass = new JClassType(info, name + "$Type", false, true);
innerLambdaClass.setEnclosingType((JDeclaredType) typeMap.get(x.binding.declaringClass));
for (JInterfaceType type : funcType) {
innerLambdaClass.addImplements(type);
}
innerLambdaClass.setSuperClass(javaLangObject);
createSyntheticMethod(info, CLINIT_NAME, innerLambdaClass, JPrimitiveType.VOID, false, true,
true, AccessModifier.PRIVATE);
createSyntheticMethod(info, INIT_NAME, innerLambdaClass, JPrimitiveType.VOID, false, false,
true, AccessModifier.PRIVATE);
// Add a getClass() implementation for all non-Object classes.
createSyntheticMethod(info, "getClass", innerLambdaClass, javaLangClass, false, false, false,
AccessModifier.PUBLIC,
new JReturnStatement(info, new JClassLiteral(info, innerLambdaClass)));
return innerLambdaClass;
}
@Override
public void endVisit(LocalDeclaration x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JLocal local = (JLocal) curMethod.locals.get(x.binding);
assert local != null;
JLocalRef localRef = new JLocalRef(info, local);
JExpression initialization = pop(x.initialization);
push(new JDeclarationStatement(info, localRef, initialization));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(LongLiteral x, BlockScope scope) {
try {
push(JLongLiteral.get(x.constant.longValue()));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(MessageSend x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JMethod method = typeMap.get(x.binding);
List<JExpression> arguments = popCallArgs(info, x.arguments, x.binding);
JExpression receiver = pop(x.receiver);
if (x.receiver instanceof ThisReference) {
if (method.isStatic()) {
// don't bother qualifying it, it's a no-op
receiver = null;
} else if ((x.bits & ASTNode.DepthMASK) != 0) {
// outer method can be reached through emulation if implicit access
ReferenceBinding targetType =
scope.enclosingSourceType().enclosingTypeAt(
(x.bits & ASTNode.DepthMASK) >> ASTNode.DepthSHIFT);
receiver = resolveThisReference(info, targetType, true, scope);
} else if (x.receiver.sourceStart == 0) {
// Synthetic this ref with bad source info; fix the info.
JThisRef oldRef = (JThisRef) receiver;
receiver = new JThisRef(info, oldRef.getClassType());
}
}
JMethodCall call = new JMethodCall(info, receiver, method);
// On a super ref, don't allow polymorphic dispatch. Oddly enough,
// QualifiedSuperReference not derived from SuperReference!
boolean isSuperRef =
x.receiver instanceof SuperReference || x.receiver instanceof QualifiedSuperReference;
if (isSuperRef) {
call.setStaticDispatchOnly();
}
// The arguments come first...
call.addArgs(arguments);
if (x.valueCast != null) {
JType castType = typeMap.get(x.valueCast);
push(maybeCast(castType, call));
} else {
push(call);
}
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(MethodDeclaration x, ClassScope scope) {
try {
if (x.isNative()) {
processNativeMethod(x);
} else {
List<JStatement> statements = pop(x.statements);
curMethod.body.getBlock().addStmts(statements);
}
popMethodInfo();
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(NullLiteral x, BlockScope scope) {
push(JNullLiteral.INSTANCE);
}
@Override
public void endVisit(OR_OR_Expression x, BlockScope scope) {
pushBinaryOp(x, JBinaryOperator.OR);
}
@Override
public void endVisit(PostfixExpression x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JUnaryOperator op;
switch (x.operator) {
case OperatorIds.MINUS:
op = JUnaryOperator.DEC;
break;
case OperatorIds.PLUS:
op = JUnaryOperator.INC;
break;
default:
throw new InternalCompilerException("Unexpected postfix operator");
}
JExpression lhs = pop(x.lhs);
push(new JPostfixOperation(info, op, lhs));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(PrefixExpression x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JUnaryOperator op;
switch (x.operator) {
case OperatorIds.MINUS:
op = JUnaryOperator.DEC;
break;
case OperatorIds.PLUS:
op = JUnaryOperator.INC;
break;
default:
throw new InternalCompilerException("Unexpected prefix operator");
}
JExpression lhs = pop(x.lhs);
push(new JPrefixOperation(info, op, lhs));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(QualifiedAllocationExpression x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
List<JExpression> arguments = popCallArgs(info, x.arguments, x.binding);
pushNewExpression(info, x, x.enclosingInstance(), arguments, scope);
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(QualifiedNameReference x, BlockScope scope) {
try {
JExpression curRef = resolveNameReference(x, scope);
if (curRef == null) {
push(null);
return;
}
if (x.genericCast != null) {
JType castType = typeMap.get(x.genericCast);
curRef = maybeCast(castType, curRef);
}
SourceInfo info = curRef.getSourceInfo();
/*
* JDT represents multiple field access as an array of fields, each
* qualified by everything to the left. So each subsequent item in
* otherBindings takes the current expression as a qualifier.
*/
if (x.otherBindings != null) {
for (int i = 0; i < x.otherBindings.length; ++i) {
FieldBinding fieldBinding = x.otherBindings[i];
if (fieldBinding.declaringClass == null) {
// probably array.length
if (!ARRAY_LENGTH_FIELD.equals(String.valueOf(fieldBinding.name))) {
throw new InternalCompilerException("Expected [array].length.");
}
curRef = new JArrayLength(info, curRef);
} else {
JField field = typeMap.get(fieldBinding);
curRef = new JFieldRef(info, curRef, field, curClass.type);
}
if (x.otherGenericCasts != null && x.otherGenericCasts[i] != null) {
JType castType = typeMap.get(x.otherGenericCasts[i]);
curRef = maybeCast(castType, curRef);
}
}
}
push(curRef);
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(QualifiedSuperReference x, BlockScope scope) {
try {
// Oddly enough, super refs can be modeled as this refs, because
// whatever expression they qualify has already been resolved.
SourceInfo info = makeSourceInfo(x);
ReferenceBinding targetType = (ReferenceBinding) x.qualification.resolvedType.erasure();
if (targetType.isInterface()) {
// Java8 super reference to default method from subtype, X.super.someDefaultMethod
push(makeThisRef(info));
} else {
push(resolveThisReference(info, targetType, true, scope));
}
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(QualifiedThisReference x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
ReferenceBinding targetType = (ReferenceBinding) x.qualification.resolvedType;
push(resolveThisReference(info, targetType, true, scope));
} catch (Throwable e) {
throw translateException(x, e);
}
}
private Map<String, JClassType> lambdaNameToInnerLambdaType = Maps.newHashMap();
@Override
public void endVisit(ReferenceExpression x, BlockScope blockScope) {
/**
* Converts an expression like foo(qualifier::someMethod) into
*
* class Enclosing {
*
* [static] T someMethod(locals, args) {...lambda expr }
*
* class lambda$someMethodType implements I {
* ctor([qualifier]) { ... }
* R <SAM lambdaMethod>(args) { return [outer]someMethod(args); }
* }
* }
*
* and replaces qualifier::someMethod with new lambda$someMethodType([outer this])
*
* [x] denotes optional, depending on context of whether outer this scope is needed.
*/
// Calculate what type this reference is going to bind to, and what single abstract method
TypeBinding binding = x.expectedType();
MethodBinding samBinding = binding.getSingleAbstractMethod(blockScope, false);
// Get the interface method is binds to
JMethod interfaceMethod = typeMap.get(samBinding);
JInterfaceType funcType = (JInterfaceType) typeMap.get(binding);
SourceInfo info = makeSourceInfo(x);
// Get the method that the Type::method is actually referring to
MethodBinding referredMethodBinding = x.binding;
if (referredMethodBinding instanceof SyntheticMethodBinding) {
SyntheticMethodBinding synthRefMethodBinding = (SyntheticMethodBinding) referredMethodBinding;
if (synthRefMethodBinding.targetMethod != null) {
// generated in cases were a private method in an outer class needed to be called
// e.g. outer.access$0 calls some outer.private_method
referredMethodBinding = synthRefMethodBinding.targetMethod;
// privateCtor::new generates overloaded <init> references with fake args that delegate
// to the real ctor (JDT WTF!). Will we ever need to go deeper?
if (synthRefMethodBinding.fakePaddedParameters != 0
&& synthRefMethodBinding.targetMethod instanceof SyntheticMethodBinding) {
referredMethodBinding = ((SyntheticMethodBinding) referredMethodBinding).targetMethod;
}
}
}
JMethod referredMethod = typeMap.get(referredMethodBinding);
boolean haveReceiver = false;
try {
haveReceiver = (Boolean) haveReceiverField.get(x);
} catch (IllegalAccessException e) {
throw translateException(x, e);
}
// Constructors and overloading mean we need generate unique names
String lambdaName = GenerateJavaScriptAST.classNameForMethodReference(funcType,
referredMethod,
haveReceiver);
// Create an inner class to hold the implementation of the interface
JClassType innerLambdaClass = lambdaNameToInnerLambdaType.get(lambdaName);
List<JExpression> enclosingThisRefs = new ArrayList<JExpression>();
if (innerLambdaClass == null) {
innerLambdaClass = createInnerClass(lambdaName, x, info, funcType);
lambdaNameToInnerLambdaType.put(lambdaName, innerLambdaClass);
newTypes.add(innerLambdaClass);
JConstructor ctor = new JConstructor(info, innerLambdaClass, AccessModifier.PRIVATE);
JMethodBody ctorBody = new JMethodBody(info);
JThisRef thisRef = new JThisRef(info, innerLambdaClass);
JExpression instance = null;
List<JField> enclosingInstanceFields = new ArrayList<JField>();
// If we have a qualifier instance, we have to stash it in the constructor
if (haveReceiver) {
// this.$$outer = $$outer
JField outerField = createAndBindCapturedLambdaParameter(info, OUTER_LAMBDA_PARAM_NAME,
innerLambdaClass.getEnclosingType(), ctor, ctorBody);
instance = new JFieldRef(info,
new JThisRef(info, innerLambdaClass), outerField, innerLambdaClass);
} else if (referredMethod instanceof JConstructor) {
// the method we are invoking is a constructor and may need enclosing instances passed to it
// For example, an class Foo { class Inner { Inner(int x) { } } } needs
// it's constructor invoked with an enclosing instance, Inner::new
// Java8 doesn't allow the qualifified case, e.g. x.new Foo() -> x.Foo::new
ReferenceBinding targetBinding = referredMethodBinding.declaringClass;
if (JdtUtil.isInnerClass(targetBinding)) {
for (ReferenceBinding argType : targetBinding.syntheticEnclosingInstanceTypes()) {
argType = (ReferenceBinding) argType.erasure();
JExpression enclosingThisRef = resolveThisReference(info, argType, false, blockScope);
JField enclosingInstance = createAndBindCapturedLambdaParameter(info,
String.valueOf(argType.readableName()).replace('.', '_'),
enclosingThisRef.getType(), ctor, ctorBody);
enclosingInstanceFields.add(enclosingInstance);
enclosingThisRefs.add(enclosingThisRef);
}
}
}
ctor.setBody(ctorBody);
innerLambdaClass.addMethod(ctor);
// Create an implementation of the target interface that invokes the method referred to
// void onClick(ClickEvent e) { outer.referredMethod(e); }
JMethod samMethod = new JMethod(info, interfaceMethod.getName(),
innerLambdaClass, interfaceMethod.getType(),
false, false, true, interfaceMethod.getAccess());
for (JParameter origParam : interfaceMethod.getParams()) {
JType origType = origParam.getType();
samMethod.addParam(new JParameter(origParam.getSourceInfo(),
origParam.getName(), origType,
origParam.isFinal(), origParam.isThis(),
samMethod));
}
JMethodBody samMethodBody = new JMethodBody(info);
Iterator<JParameter> paramIt = samMethod.getParams().iterator();
// here's where it gets tricky. A method can have an implicit qualifier, e.g.
// String::compareToIgnoreCase, it's non-static, it only has one argument, but it binds to
// Comparator<T>.
// The first argument serves as the qualifier, so for example, the method dispatch looks
// like this: int compare(T a, T b) { a.compareTo(b); }
if (!haveReceiver && !referredMethod.isStatic() && instance == null &&
samMethod.getParams().size() == referredMethod.getParams().size() + 1) {
// the instance qualifier is the first parameter in this case.
instance = new JParameterRef(info, paramIt.next());
}
JMethodCall samCall = null;
if (referredMethod.isConstructor()) {
// Constructors must be invoked with JNewInstance
samCall = new JNewInstance(info, (JConstructor) referredMethod);
for (JField enclosingInstance : enclosingInstanceFields) {
samCall.addArg(new JFieldRef(enclosingInstance.getSourceInfo(), thisRef,
enclosingInstance, innerLambdaClass));
}
} else {
// For static methods, instance will be null
samCall = new JMethodCall(info, instance, referredMethod);
// if super::method, we need static dispatch
if (x.lhs instanceof SuperReference) {
samCall.setStaticDispatchOnly();
}
}
// Add the rest of the parameters from the interface method to methodcall
// boxing or unboxing and dealing with varargs
int paramNumber = 0;
// need to build up an array of passed parameters if we have varargs
List<JExpression> varArgInitializers = null;
int varArg = x.binding.parameters.length - 1;
// interface Foo { m(int x, int y); } bound to reference foo(int... args)
// if varargs and incoming param is not already a var-arg, we'll need to convert
// trailing args of the target interface into an array
if (x.binding.isVarargs() && !samBinding.parameters[varArg].isArrayType()) {
varArgInitializers = new ArrayList<JExpression>();
}
while (paramIt.hasNext()) {
JParameter param = paramIt.next();
JExpression paramExpr = new JParameterRef(info, param);
// params may need to be boxed or unboxed
TypeBinding destParam = null;
// if it is not the trailing param or varargs, or interface method is already varargs
if (varArgInitializers == null || !x.binding.isVarargs() || (paramNumber < varArg)) {
destParam = x.binding.parameters[paramNumber];
paramExpr = boxOrUnboxExpression(paramExpr, samBinding.parameters[paramNumber],
destParam);
samCall.addArg(paramExpr);
} else if (!samBinding.parameters[paramNumber].isArrayType()) {
// else add trailing parameters to var-args initializer list for an array
destParam = x.binding.parameters[varArg].leafComponentType();
paramExpr = boxOrUnboxExpression(paramExpr, samBinding.parameters[paramNumber],
destParam);
varArgInitializers.add(paramExpr);
}
paramNumber++;
}
// add trailing new T[] { initializers } var-arg array
if (varArgInitializers != null) {
JArrayType lastParamType =
(JArrayType) typeMap.get(x.binding.parameters[x.binding.parameters.length - 1]);
JNewArray newArray =
JNewArray.createInitializers(info, lastParamType, varArgInitializers);
samCall.addArg(newArray);
}
if (samMethod.getType() != JPrimitiveType.VOID) {
JExpression samExpression = boxOrUnboxExpression(samCall, x.binding.returnType,
samBinding.returnType);
samMethodBody.getBlock().addStmt(new JReturnStatement(info, simplify(samExpression, x)));
} else {
samMethodBody.getBlock().addStmt(samCall.makeStatement());
}
samMethod.setBody(samMethodBody);
innerLambdaClass.addMethod(samMethod);
ctor.freezeParamTypes();
samMethod.freezeParamTypes();
}
JConstructor lambdaCtor = null;
for (JMethod method : innerLambdaClass.getMethods()) {
if (method instanceof JConstructor) {
lambdaCtor = (JConstructor) method;
break;
}
}
assert lambdaCtor != null;
// Replace the ReferenceExpression qualifier::method with new lambdaType(qualifier)
assert lambdaCtor.getEnclosingType() == innerLambdaClass;
JNewInstance allocLambda = new JNewInstance(info, lambdaCtor);
JExpression qualifier = (JExpression) pop();
if (haveReceiver) {
// pop qualifier from stack
allocLambda.addArg(qualifier);
} else {
// you can't simultaneously have a qualifier, and have enclosing inner class refs
// because Java8 won't allow a qualified constructor method reference, e.g. x.Foo::new
for (JExpression enclosingRef : enclosingThisRefs) {
allocLambda.addArg(enclosingRef);
}
}
push(allocLambda);
}
private JExpression boxOrUnboxExpression(JExpression expr, TypeBinding fromType,
TypeBinding toType) {
if (fromType == TypeBinding.VOID || toType == TypeBinding.VOID) {
return expr;
}
int implicitConversion;
if (fromType.isBaseType() && !toType.isBaseType()) {
implicitConversion = (fromType.id & TypeIds.IMPLICIT_CONVERSION_MASK) << 4;
implicitConversion = implicitConversion | TypeIds.BOXING;
expr = box(expr, implicitConversion);
} else if (!fromType.isBaseType() && toType.isBaseType()) {
implicitConversion = (toType.id & TypeIds.IMPLICIT_CONVERSION_MASK) << 4;
implicitConversion = implicitConversion | TypeIds.UNBOXING;
expr = unbox(expr, implicitConversion);
}
return expr;
}
@Override
public void endVisit(ReturnStatement x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JExpression expression = pop(x.expression);
push(new JReturnStatement(info, expression));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(SingleNameReference x, BlockScope scope) {
try {
JExpression result = resolveNameReference(x, scope);
if (result == null) {
push(null);
return;
}
if (x.genericCast != null) {
JType castType = typeMap.get(x.genericCast);
result = maybeCast(castType, result);
}
push(result);
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(StringLiteral x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
push(getStringLiteral(info, x.constant.stringValue()));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(StringLiteralConcatenation x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
push(getStringLiteral(info, x.constant.stringValue()));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(SuperReference x, BlockScope scope) {
try {
assert (typeMap.get(x.resolvedType) == curClass.getClassOrInterface().getSuperClass());
// Super refs can be modeled as a this ref.
push(makeThisRef(makeSourceInfo(x)));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(SwitchStatement x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JBlock block = popBlock(info, x.statements);
JExpression expression = pop(x.expression);
if (x.expression.resolvedType.isEnum()) {
// synthesize a call to ordinal().
expression = synthesizeCallToOrdinal(scope, info, expression);
}
push(new JSwitchStatement(info, expression, block));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(SynchronizedStatement x, BlockScope scope) {
try {
JBlock block = pop(x.block);
JExpression expression = pop(x.expression);
block.addStmt(0, expression.makeStatement());
push(block);
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(ThisReference x, BlockScope scope) {
try {
assert typeMap.get(x.resolvedType) == curClass.getClassOrInterface();
push(makeThisRef(makeSourceInfo(x)));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(ThrowStatement x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JExpression exception = pop(x.exception);
push(new JThrowStatement(info, exception));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(TrueLiteral x, BlockScope scope) {
push(JBooleanLiteral.TRUE);
}
@Override
public void endVisit(TryStatement x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JBlock finallyBlock = pop(x.finallyBlock);
List<JBlock> catchBlocks = pop(x.catchBlocks);
JBlock tryBlock = pop(x.tryBlock);
if (x.resources.length > 0) {
tryBlock = normalizeTryWithResources(info, x, tryBlock, scope);
}
List<JTryStatement.CatchClause> catchClauses = Lists.newArrayList();
if (x.catchBlocks != null) {
for (int i = 0; i < x.catchArguments.length; i++) {
Argument argument = x.catchArguments[i];
JLocal local = (JLocal) curMethod.locals.get(argument.binding);
List<JType> catchTypes = Lists.newArrayList();
if (argument.type instanceof UnionTypeReference) {
// This is a multiexception
for (TypeReference type : ((UnionTypeReference) argument.type).typeReferences) {
catchTypes.add(typeMap.get(type.resolvedType));
}
} else {
// Regular exception
catchTypes.add(local.getType());
}
catchClauses.add(new JTryStatement.CatchClause(catchTypes, new JLocalRef(info, local),
catchBlocks.get(i)));
}
}
push(new JTryStatement(info, tryBlock, catchClauses, finallyBlock));
} catch (Throwable e) {
throw translateException(x, e);
}
}
private JBlock normalizeTryWithResources(SourceInfo info, TryStatement x, JBlock tryBlock,
BlockScope scope) {
/**
* Apply the following source transformation:
*
* try (A1 a1 = new A1(); ... ; An an = new An()) {
* ... tryBlock...
* } ...catch/finally blocks
*
* to
*
* try {
* A1 a1 = new A1();... ; An an = new An();
* Throwable $exception = null;
* try {
* ... tryBlock...
* } catch (Throwable t) {
* $exception = t;
* throw t;
* } finally {
* $exception = Exceptions.safeClose(an, $exception);
* ...
* $exception = Exceptions.safeClose(a1, $exception);
* if ($exception != null) {
* throw $exception;
* }
* } ...catch/finally blocks
*
*/
JBlock innerBlock = new JBlock(info);
// add resource variables
List<JLocal> resourceVariables = Lists.newArrayList();
for (int i = x.resources.length - 1; i >= 0; i--) {
// Needs to iterate back to front to be inline with the contents of the stack.
JDeclarationStatement resourceDecl = pop(x.resources[i]);
JLocal resourceVar = (JLocal) curMethod.locals.get(x.resources[i].binding);
resourceVariables.add(0, resourceVar);
innerBlock.addStmt(0, resourceDecl);
}
// add exception variable
JLocal exceptionVar = createLocalThrowable(info, "$primary_ex");
innerBlock.addStmt(makeDeclaration(info, exceptionVar, JNullLiteral.INSTANCE));
// create catch block
List<JTryStatement.CatchClause> catchClauses = Lists.newArrayListWithCapacity(1);
List<JType> clauseTypes = Lists.newArrayListWithCapacity(1);
clauseTypes.add(javaLangThrowable);
// add catch exception variable.
JLocal catchVar = createLocalThrowable(info, "$caught_ex");
JBlock catchBlock = new JBlock(info);
catchBlock.addStmt(createAssignment(info, javaLangThrowable, exceptionVar, catchVar));
catchBlock.addStmt(new JThrowStatement(info, new JLocalRef(info, exceptionVar)));
catchClauses.add(new JTryStatement.CatchClause(clauseTypes, new JLocalRef(info, catchVar),
catchBlock));
// create finally block
JBlock finallyBlock = new JBlock(info);
for (int i = x.resources.length - 1; i >= 0; i--) {
finallyBlock.addStmt(createCloseBlockFor(info,
resourceVariables.get(i), exceptionVar));
}
// if (exception != null) throw exception
JExpression exceptionNotNull = new JBinaryOperation(info, JPrimitiveType.BOOLEAN,
JBinaryOperator.NEQ, new JLocalRef(info, exceptionVar), JNullLiteral.INSTANCE);
finallyBlock.addStmt(new JIfStatement(info, exceptionNotNull,
new JThrowStatement(info, new JLocalRef(info, exceptionVar)), null));
// Stitch all together into a inner try block
innerBlock.addStmt(new JTryStatement(info, tryBlock, catchClauses,
finallyBlock));
return innerBlock;
}
private JLocal createLocalThrowable(SourceInfo info, String prefix) {
int index = curMethod.body.getLocals().size() + 1;
return JProgram.createLocal(info, prefix + "_" + index,
javaLangThrowable, false, curMethod.body);
}
private JStatement createCloseBlockFor(final SourceInfo info,
JLocal resourceVar, JLocal exceptionVar) {
/**
* Create the following code:
*
* $ex = Exceptions.safeClose(resource, $ex);
*
* which is equivalent to
*
* if (resource != null) {
* try {
* resource.close();
* } catch (Throwable t) {
* if ($ex == null) {
* $ex = t;
* } else {
* $ex.addSuppressed(t);
* }
* }
*/
JMethodCall safeCloseCall = new JMethodCall(info, null, SAFE_CLOSE_METHOD);
safeCloseCall.addArg(0, new JLocalRef(info, resourceVar));
safeCloseCall.addArg(1, new JLocalRef(info, exceptionVar));
return new JBinaryOperation(info, javaLangThrowable, JBinaryOperator.ASG, new JLocalRef(info,
exceptionVar), safeCloseCall).makeStatement();
}
private JStatement createAssignment(SourceInfo info, JType type, JLocal lhs, JLocal rhs) {
return new JBinaryOperation(info, type, JBinaryOperator.ASG, new JLocalRef(info, lhs),
new JLocalRef(info, rhs)).makeStatement();
}
@Override
public void endVisit(TypeDeclaration x, ClassScope scope) {
endVisit(x);
}
@Override
public void endVisit(TypeDeclaration x, CompilationUnitScope scope) {
endVisit(x);
}
@Override
public void endVisit(UnaryExpression x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JUnaryOperator op;
int operator = ((x.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT);
switch (operator) {
case OperatorIds.MINUS:
op = JUnaryOperator.NEG;
break;
case OperatorIds.NOT:
op = JUnaryOperator.NOT;
break;
case OperatorIds.PLUS:
// Odd case.. useless + operator; just leave the operand on the
// stack.
return;
case OperatorIds.TWIDDLE:
op = JUnaryOperator.BIT_NOT;
break;
default:
throw new InternalCompilerException("Unexpected operator for unary expression");
}
JExpression expression = pop(x.expression);
push(new JPrefixOperation(info, op, expression));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisit(WhileStatement x, BlockScope scope) {
try {
SourceInfo info = makeSourceInfo(x);
JStatement action = pop(x.action);
JExpression condition = pop(x.condition);
push(new JWhileStatement(info, condition, action));
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public void endVisitValid(TypeDeclaration x, BlockScope scope) {
endVisit(x);
if (!x.binding.isAnonymousType()) {
// Class declaration as a statement; insert a dummy statement.
push(null);
}
}
@Override
public boolean visit(AnnotationMethodDeclaration x, ClassScope classScope) {
return visit((MethodDeclaration) x, classScope);
}
@Override
public boolean visit(Argument x, BlockScope scope) {
// handled by parents
return true;
}
@Override
public boolean visit(Block x, BlockScope scope) {
x.statements = reduceToReachable(x.statements);
return true;
}
@Override
public boolean visit(ConstructorDeclaration x, ClassScope scope) {
try {
JConstructor method = (JConstructor) typeMap.get(x.binding);
assert !method.isExternal();
JMethodBody body = new JMethodBody(method.getSourceInfo());
method.setBody(body);
pushMethodInfo(new MethodInfo(method, body, x.scope));
// Map all arguments.
Iterator<JParameter> it = method.getParams().iterator();
// Enum arguments have no mapping.
if (curClass.classType.isEnumOrSubclass() != null) {
// Skip past name and ordinal.
it.next();
it.next();
}
// Map synthetic arguments for outer this.
ReferenceBinding declaringClass = (ReferenceBinding) x.binding.declaringClass.erasure();
boolean isNested = JdtUtil.isInnerClass(declaringClass);
if (isNested) {
NestedTypeBinding nestedBinding = (NestedTypeBinding) declaringClass;
if (nestedBinding.enclosingInstances != null) {
for (int i = 0; i < nestedBinding.enclosingInstances.length; ++i) {
SyntheticArgumentBinding arg = nestedBinding.enclosingInstances[i];
curMethod.locals.put(arg, it.next());
}
}
}
// Map user arguments.
if (x.arguments != null) {
for (Argument argument : x.arguments) {
curMethod.locals.put(argument.binding, it.next());
}
}
// Map synthetic arguments for locals.
if (isNested) {
// add synthetic args for locals
NestedTypeBinding nestedBinding = (NestedTypeBinding) declaringClass;
// add synthetic args for outer this and locals
if (nestedBinding.outerLocalVariables != null) {
for (int i = 0; i < nestedBinding.outerLocalVariables.length; ++i) {
SyntheticArgumentBinding arg = nestedBinding.outerLocalVariables[i];
curMethod.locals.put(arg, it.next());
}
}
}
x.statements = reduceToReachable(x.statements);
return true;
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public boolean visit(ExplicitConstructorCall explicitConstructor, BlockScope scope) {
scope.methodScope().isConstructorCall = true;
return true;
}
@Override
public boolean visit(FieldDeclaration x, MethodScope scope) {
try {
assert !typeMap.get(x.binding).isExternal();
pushInitializerMethodInfo(x, scope);
return true;
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public boolean visit(Initializer x, MethodScope scope) {
try {
pushInitializerMethodInfo(x, scope);
return true;
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public boolean visit(LocalDeclaration x, BlockScope scope) {
try {
createLocal(x);
return true;
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public boolean visit(MarkerAnnotation annotation, BlockScope scope) {
return false;
}
@Override
public boolean visit(MethodDeclaration x, ClassScope scope) {
try {
JMethod method = typeMap.get(x.binding);
assert !method.isExternal();
JMethodBody body = null;
if (!method.isNative()) {
body = new JMethodBody(method.getSourceInfo());
method.setBody(body);
}
pushMethodInfo(new MethodInfo(method, body, x.scope));
// Map user arguments.
Iterator<JParameter> it = method.getParams().iterator();
if (x.arguments != null) {
for (Argument argument : x.arguments) {
curMethod.locals.put(argument.binding, it.next());
}
}
x.statements = reduceToReachable(x.statements);
return true;
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public boolean visit(NormalAnnotation annotation, BlockScope scope) {
return false;
}
@Override
public boolean visit(SingleMemberAnnotation annotation, BlockScope scope) {
return false;
}
@Override
public boolean visit(SwitchStatement x, BlockScope scope) {
x.statements = reduceToReachable(x.statements);
return true;
}
@Override
public boolean visit(TryStatement x, BlockScope scope) {
try {
if (x.catchBlocks != null) {
for (Argument argument : x.catchArguments) {
createLocal(argument);
}
}
return true;
} catch (Throwable e) {
throw translateException(x, e);
}
}
@Override
public boolean visit(TypeDeclaration x, ClassScope scope) {
return visit(x);
}
@Override
public boolean visit(TypeDeclaration x, CompilationUnitScope scope) {
return visit(x);
}
@Override
public boolean visitValid(TypeDeclaration x, BlockScope scope) {
// Local types actually need to be created now.
createTypes(x);
resolveTypeRefs(x);
createMembers(x);
return visit(x);
}
protected void endVisit(TypeDeclaration x) {
JDeclaredType type = curClass.type;
/*
* Make clinits chain to super class (JDT doesn't write code to do this).
* Call super class $clinit;
*/
if (type.getSuperClass() != null) {
JMethod myClinit = type.getClinitMethod();
JMethod superClinit = type.getSuperClass().getClinitMethod();
JMethodCall superClinitCall = new JMethodCall(myClinit.getSourceInfo(), null, superClinit);
JMethodBody body = (JMethodBody) myClinit.getBody();
body.getBlock().addStmt(0, superClinitCall.makeStatement());
}
// Implement getClass() implementation for all non-Object classes.
if (isSyntheticGetClassNeeded(x, type)) {
implementGetClass(type);
}
if (type instanceof JEnumType) {
processEnumType((JEnumType) type);
}
if (type instanceof JClassType) {
addBridgeMethods(x.binding);
}
if (JsInteropUtil.isClassWideJsExport(x)) {
for (JMethod m : type.getMethods()) {
if (m.getExportName() != null) {
continue;
}
if (m.isPublic() && (m.isStatic() || (m instanceof JConstructor))) {
m.setExportName("");
}
}
for (JField f : type.getFields()) {
if (f.getExportName() != null) {
continue;
}
if (f.isPublic() && f.isStatic()) {
f.setExportName("");
}
}
}
JsInteropUtil.maybeSetJsNamespace(type, x);
curClass = classStack.pop();
}
protected JBlock pop(Block x) {
return (x == null) ? null : (JBlock) pop();
}
protected JExpression pop(Expression x) {
if (x == null) {
return null;
}
JExpression result = (JExpression) pop();
if (result == null) {
assert x instanceof NameReference;
return null;
}
result = simplify(result, x);
return result;
}
@SuppressWarnings("unchecked")
protected <T extends JExpression> List<T> pop(Expression[] expressions) {
if (expressions == null) {
return Collections.emptyList();
}
List<T> result = (List<T>) popList(expressions.length);
for (int i = 0; i < expressions.length; ++i) {
result.set(i, (T) simplify(result.get(i), expressions[i]));
}
return result;
}
protected JDeclarationStatement pop(LocalDeclaration decl) {
return (decl == null) ? null : (JDeclarationStatement) pop();
}
protected JStatement pop(Statement x) {
JNode pop = (x == null) ? null : pop();
if (x instanceof Expression) {
return simplify((JExpression) pop, (Expression) x).makeStatement();
}
return (JStatement) pop;
}
@SuppressWarnings("unchecked")
protected <T extends JStatement> List<T> pop(Statement[] statements) {
if (statements == null) {
return Collections.emptyList();
}
List<T> result = (List<T>) popList(statements.length);
int i = 0;
for (ListIterator<T> it = result.listIterator(); it.hasNext(); ++i) {
Object element = it.next();
if (element == null) {
it.remove();
} else if (element instanceof JExpression) {
it.set((T) simplify((JExpression) element, (Expression) statements[i]).makeStatement());
}
}
return result;
}
protected JBlock popBlock(SourceInfo info, Statement statement) {
JStatement stmt = pop(statement);
if (stmt instanceof JBlock) {
return (JBlock) stmt;
}
JBlock block = new JBlock(info);
if (stmt != null) {
block.addStmt(stmt);
}
return block;
}
protected JBlock popBlock(SourceInfo info, Statement[] statements) {
List<JStatement> stmts = pop(statements);
JBlock block = new JBlock(info);
block.addStmts(stmts);
return block;
}
protected void pushBinaryOp(Assignment x, JBinaryOperator op) {
pushBinaryOp(x, op, x.lhs, x.expression);
}
protected void pushBinaryOp(BinaryExpression x, JBinaryOperator op) {
pushBinaryOp(x, op, x.left, x.right);
}
protected boolean visit(TypeDeclaration x) {
JDeclaredType type = (JDeclaredType) typeMap.get(x.binding);
assert !type.isExternal();
classStack.push(curClass);
curClass = new ClassInfo(type, x);
/*
* It's okay to defer creation of synthetic fields, they can't be
* referenced until we analyze the code.
*/
SourceTypeBinding binding = x.binding;
if (JdtUtil.isInnerClass(binding)) {
// add synthetic fields for outer this and locals
assert (type instanceof JClassType);
NestedTypeBinding nestedBinding = (NestedTypeBinding) binding;
if (nestedBinding.enclosingInstances != null) {
for (int i = 0; i < nestedBinding.enclosingInstances.length; ++i) {
SyntheticArgumentBinding arg = nestedBinding.enclosingInstances[i];
createSyntheticField(arg, type, Disposition.THIS_REF);
}
}
if (nestedBinding.outerLocalVariables != null) {
for (int i = 0; i < nestedBinding.outerLocalVariables.length; ++i) {
SyntheticArgumentBinding arg = nestedBinding.outerLocalVariables[i];
// See InnerClassTest.testOuterThisFromSuperCall().
boolean isReallyThisRef = false;
if (arg.actualOuterLocalVariable instanceof SyntheticArgumentBinding) {
SyntheticArgumentBinding outer =
(SyntheticArgumentBinding) arg.actualOuterLocalVariable;
if (outer.matchingField != null) {
JField field = typeMap.get(outer.matchingField);
if (field.isThisRef()) {
isReallyThisRef = true;
}
}
}
createSyntheticField(arg, type, isReallyThisRef ? Disposition.THIS_REF
: Disposition.FINAL);
}
}
}
return true;
}
/**
* <p>
* Add a bridge method to <code>clazzBinding</code> for any method it
* inherits that implements an interface method but that has a different
* erased signature from the interface method.
* </p>
*
* <p>
* The need for these bridges was pointed out in issue 3064. The goal is
* that virtual method calls through an interface type are translated to
* JavaScript that will function correctly. If the interface signature
* matches the signature of the implementing method, then nothing special
* needs to be done. If they are different, due to the use of generics, then
* GenerateJavaScriptAST is careful to do the right thing. There is a
* remaining case, though, that GenerateJavaScriptAST is not in a good
* position to fix: a method could be inherited from a superclass, used to
* implement an interface method that has a different type signature, and
* does not have the interface method in its list of overrides. In that
* case, a bridge method should be added that overrides the interface method
* and then calls the implementation method.
* </p>
*
* <p>
* This method should only be called once all regular, non-bridge methods
* have been installed on the GWT types.
* </p>
*/
private void addBridgeMethods(SourceTypeBinding clazzBinding) {
/*
* JDT adds bridge methods in all the places GWT needs them. Use JDT's
* bridge methods.
*/
if (clazzBinding.syntheticMethods() != null) {
for (SyntheticMethodBinding synthmeth : clazzBinding.syntheticMethods()) {
if (synthmeth.purpose == SyntheticMethodBinding.BridgeMethod && !synthmeth.isStatic()) {
createBridgeMethod(synthmeth);
}
}
}
}
private JBinaryOperation assignSyntheticField(SourceInfo info, SyntheticArgumentBinding arg) {
JParameter param = (JParameter) curMethod.locals.get(arg);
assert param != null;
JField field = curClass.syntheticFields.get(arg);
assert field != null;
JFieldRef lhs = makeInstanceFieldRef(info, field);
JParameterRef rhs = new JParameterRef(info, param);
JBinaryOperation asg =
new JBinaryOperation(info, lhs.getType(), JBinaryOperator.ASG, lhs, rhs);
return asg;
}
private JExpression box(JExpression original, int implicitConversion) {
int typeId = (implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4;
ClassScope scope = curClass.scope;
BaseTypeBinding primitiveType = (BaseTypeBinding) TypeBinding.wellKnownType(scope, typeId);
ReferenceBinding boxType = (ReferenceBinding) scope.boxing(primitiveType);
MethodBinding valueOfMethod =
boxType.getExactMethod(VALUE_OF, new TypeBinding[]{primitiveType}, scope
.compilationUnitScope());
assert valueOfMethod != null;
// Add a cast to the correct primitive type if needed.
JType targetPrimitiveType = typeMap.get(primitiveType);
if (original.getType() != targetPrimitiveType) {
original = new JCastOperation(original.getSourceInfo(), targetPrimitiveType, original);
}
JMethod boxMethod = typeMap.get(valueOfMethod);
JMethodCall call = new JMethodCall(original.getSourceInfo(), null, boxMethod);
call.addArg(original);
return call;
}
/**
* Create a bridge method. It calls a same-named method with the same
* arguments, but with a different type signature.
*/
private void createBridgeMethod(SyntheticMethodBinding jdtBridgeMethod) {
JMethod implmeth = typeMap.get(jdtBridgeMethod.targetMethod);
SourceInfo info = implmeth.getSourceInfo();
JMethod bridgeMethod =
new JMethod(info, implmeth.getName(), curClass.type, typeMap
.get(jdtBridgeMethod.returnType), false, false, implmeth.isFinal(), implmeth
.getAccess());
typeMap.setMethod(jdtBridgeMethod, bridgeMethod);
bridgeMethod.setBody(new JMethodBody(info));
curClass.type.addMethod(bridgeMethod);
bridgeMethod.setSynthetic();
int paramIdx = 0;
List<JParameter> implParams = implmeth.getParams();
for (TypeBinding jdtParamType : jdtBridgeMethod.parameters) {
JParameter param = implParams.get(paramIdx++);
JType paramType = typeMap.get(jdtParamType.erasure());
JParameter newParam =
new JParameter(param.getSourceInfo(), param.getName(), paramType, true, false,
bridgeMethod);
bridgeMethod.addParam(newParam);
}
for (ReferenceBinding exceptionReference : jdtBridgeMethod.thrownExceptions) {
bridgeMethod.addThrownException((JClassType) typeMap.get(exceptionReference.erasure()));
}
bridgeMethod.freezeParamTypes();
// create a call and pass all arguments through, casting if necessary
JMethodCall call = new JMethodCall(info, makeThisRef(info), implmeth);
for (int i = 0; i < bridgeMethod.getParams().size(); i++) {
JParameter param = bridgeMethod.getParams().get(i);
JParameterRef paramRef = new JParameterRef(info, param);
call.addArg(maybeCast(implParams.get(i).getType(), paramRef));
}
JMethodBody body = (JMethodBody) bridgeMethod.getBody();
if (bridgeMethod.getType() == JPrimitiveType.VOID) {
body.getBlock().addStmt(call.makeStatement());
} else {
body.getBlock().addStmt(new JReturnStatement(info, call));
}
}
private void writeEnumValuesMethod(JEnumType type, JMethod method) {
// return new E[]{A,B,C};
JArrayType enumArrayType = new JArrayType(type);
SourceInfo info = type.getSourceInfo();
List<JExpression> initializers = Lists.newArrayList();
for (JEnumField field : type.getEnumList()) {
JFieldRef fieldRef = new JFieldRef(info, null, field, type);
initializers.add(fieldRef);
}
JNewArray valuesArrayCopy = JNewArray.createInitializers(info, enumArrayType, initializers);
if (type.getEnumList().size() > MAX_INLINEABLE_ENUM_SIZE) {
// Only inline values() if it is small.
method.setInliningAllowed(false);
}
implementMethod(method, valuesArrayCopy);
}
private JLocal createLocal(LocalDeclaration x) {
LocalVariableBinding b = x.binding;
TypeBinding resolvedType = x.type.resolvedType;
JType localType;
if (resolvedType.constantPoolName() != null) {
localType = typeMap.get(resolvedType);
} else {
// Special case, a statically unreachable local type.
localType = JNullType.INSTANCE;
}
SourceInfo info = makeSourceInfo(x);
JLocal newLocal =
JProgram.createLocal(info, intern(x.name), localType, b.isFinal(), curMethod.body);
curMethod.locals.put(b, newLocal);
return newLocal;
}
private JField createSyntheticField(SyntheticArgumentBinding arg, JDeclaredType enclosingType,
Disposition disposition) {
JType type = typeMap.get(arg.type);
SourceInfo info = enclosingType.getSourceInfo();
// Construct field name including position because JDT can sometimes create multiple synthetic
// fields with the same name. The increased name size won't affect optimized output since
// references are pruned and renamed.
String fieldName = intern(intern(arg.name) + arg.resolvedPosition);
JField field = new JField(info, fieldName, enclosingType, type, false, disposition,
AccessModifier.PRIVATE);
enclosingType.addField(field);
curClass.syntheticFields.put(arg, field);
if (arg.matchingField != null) {
typeMap.setField(arg.matchingField, field);
}
return field;
}
private JExpression getConstant(SourceInfo info, Constant constant) {
switch (constant.typeID()) {
case TypeIds.T_int:
return JIntLiteral.get(constant.intValue());
case TypeIds.T_byte:
return JIntLiteral.get(constant.byteValue());
case TypeIds.T_short:
return JIntLiteral.get(constant.shortValue());
case TypeIds.T_char:
return JCharLiteral.get(constant.charValue());
case TypeIds.T_float:
return JFloatLiteral.get(constant.floatValue());
case TypeIds.T_double:
return JDoubleLiteral.get(constant.doubleValue());
case Constant.T_boolean:
return JBooleanLiteral.get(constant.booleanValue());
case Constant.T_long:
return JLongLiteral.get(constant.longValue());
case Constant.T_JavaLangString:
return getStringLiteral(info, constant.stringValue());
case Constant.T_null:
return JNullLiteral.INSTANCE;
default:
throw new InternalCompilerException("Unknown Constant type: " + constant.typeID());
}
}
/**
* Get a new label of a particular name, or create a new one if it doesn't
* exist already.
*/
private JLabel getOrCreateLabel(SourceInfo info, char[] name) {
if (name == null) {
return null;
}
String sname = intern(name);
JLabel jlabel = curMethod.labels.get(sname);
if (jlabel == null) {
jlabel = new JLabel(info, sname);
curMethod.labels.put(sname, jlabel);
}
return jlabel;
}
private JStringLiteral getStringLiteral(SourceInfo info, char[] chars) {
return new JStringLiteral(info, intern(chars), javaLangString);
}
private JStringLiteral getStringLiteral(SourceInfo info, String string) {
return new JStringLiteral(info, intern(string), javaLangString);
}
/**
* TODO(scottb): move to UnifyAst and only for non-abstract classes.
*/
private void implementGetClass(JDeclaredType type) {
JMethod method = type.getMethods().get(2);
assert ("getClass".equals(method.getName()));
SourceInfo info = method.getSourceInfo();
if ("com.google.gwt.lang.Array".equals(type.getName())) {
/*
* Don't implement, fall through to Object.getClass(). Array emulation code
* in com.google.gwt.lang.Array invokes Array.getClass() and expects to get the
* class literal for the actual runtime type of the array (e.g. Foo[].class) and
* not Array.class.
*/
type.getMethods().remove(2);
} else {
implementMethod(method, new JClassLiteral(info, type));
}
}
private void implementMethod(JMethod method, JExpression returnValue) {
JMethodBody body = (JMethodBody) method.getBody();
JBlock block = body.getBlock();
SourceInfo info;
if (block.getStatements().size() > 0) {
info = block.getStatements().get(0).getSourceInfo();
} else {
info = method.getSourceInfo();
}
block.clear();
block.addStmt(new JReturnStatement(info, returnValue));
}
private JDeclarationStatement makeDeclaration(SourceInfo info, JLocal local,
JExpression value) {
return new JDeclarationStatement(info, new JLocalRef(info, local), value);
}
private JFieldRef makeInstanceFieldRef(SourceInfo info, JField field) {
return new JFieldRef(info, makeThisRef(info), field, curClass.classType);
}
private JExpression makeLocalRef(SourceInfo info, LocalVariableBinding b, MethodInfo cur) {
JVariable variable = cur.locals.get(b);
assert variable != null;
if (variable instanceof JLocal) {
return new JLocalRef(info, (JLocal) variable);
} else {
return new JParameterRef(info, (JParameter) variable);
}
}
private JExpression makeLocalRef(SourceInfo info, LocalVariableBinding b) {
return makeLocalRef(info, b, curMethod);
}
private JThisRef makeThisRef(SourceInfo info) {
return new JThisRef(info, curClass.getClassOrInterface());
}
private JExpression resolveThisReference(SourceInfo info, ReferenceBinding targetType,
boolean exactMatch, BlockScope scope) {
targetType = (ReferenceBinding) targetType.erasure();
Object[] path = scope.getEmulationPath(targetType, exactMatch, false);
if (path == null) {
throw new InternalCompilerException("No emulation path.");
}
if (path == BlockScope.EmulationPathToImplicitThis) {
return makeThisRef(info);
}
JExpression ref;
ReferenceBinding type;
if (curMethod.scope.isInsideInitializer() && path[0] instanceof SyntheticArgumentBinding) {
SyntheticArgumentBinding b = (SyntheticArgumentBinding) path[0];
JField field = curClass.syntheticFields.get(b);
assert field != null;
ref = makeInstanceFieldRef(info, field);
type = (ReferenceBinding) b.type.erasure();
} else if (path[0] instanceof SyntheticArgumentBinding) {
SyntheticArgumentBinding b = (SyntheticArgumentBinding) path[0];
JParameter param = (JParameter) curMethod.locals.get(b);
assert param != null;
ref = new JParameterRef(info, param);
type = (ReferenceBinding) b.type.erasure();
} else if (path[0] instanceof FieldBinding) {
FieldBinding b = (FieldBinding) path[0];
JField field = typeMap.get(b);
assert field != null;
ref = makeInstanceFieldRef(info, field);
type = (ReferenceBinding) b.type.erasure();
} else {
throw new InternalCompilerException("Unknown emulation path.");
}
for (int i = 1; i < path.length; ++i) {
SyntheticMethodBinding b = (SyntheticMethodBinding) path[i];
assert type == b.declaringClass.erasure();
FieldBinding fieldBinding = b.targetReadField;
JField field = typeMap.get(fieldBinding);
assert field != null;
ref = new JFieldRef(info, ref, field, curClass.classType);
type = (ReferenceBinding) fieldBinding.type.erasure();
}
return ref;
}
private JExpression maybeBoxOrUnbox(JExpression original, int implicitConversion) {
if (implicitConversion != -1) {
if ((implicitConversion & TypeIds.BOXING) != 0) {
return box(original, implicitConversion);
} else if ((implicitConversion & TypeIds.UNBOXING) != 0) {
return unbox(original, implicitConversion);
}
}
return original;
}
private JExpression maybeCast(JType expected, JExpression expression) {
if (expected != expression.getType()) {
// Must be a generic; insert a cast operation.
JReferenceType toType = (JReferenceType) expected;
return new JCastOperation(expression.getSourceInfo(), toType, expression);
} else {
return expression;
}
}
private JNode pop() {
return nodeStack.remove(nodeStack.size() - 1);
}
private List<JExpression> popCallArgs(SourceInfo info, Expression[] jdtArgs,
MethodBinding binding) {
List<JExpression> args = pop(jdtArgs);
if (!binding.isVarargs()) {
return args;
}
// Handle the odd var-arg case.
if (jdtArgs == null) {
// Get writable collection (args is currently Collections.emptyList()).
args = Lists.newArrayListWithCapacity(1);
}
TypeBinding[] params = binding.parameters;
int varArg = params.length - 1;
// See if there's a single varArg which is already an array.
if (args.size() == params.length) {
if (jdtArgs[varArg].resolvedType.isCompatibleWith(params[varArg])) {
// Already the correct array type.
return args;
}
}
// Need to synthesize an appropriately-typed array.
List<JExpression> tail = args.subList(varArg, args.size());
List<JExpression> initializers = Lists.newArrayList(tail);
tail.clear();
JArrayType lastParamType = (JArrayType) typeMap.get(params[varArg]);
JNewArray newArray = JNewArray.createInitializers(info, lastParamType, initializers);
args.add(newArray);
return args;
}
private List<? extends JNode> popList(int count) {
List<JNode> tail = nodeStack.subList(nodeStack.size() - count, nodeStack.size());
// Make a copy.
List<JNode> result = Lists.newArrayList(tail);
// Causes the tail to be removed.
tail.clear();
return result;
}
private void popMethodInfo() {
curMethod = methodStack.pop();
}
private void processEnumType(JEnumType type) {
// $clinit, $init, getClass, valueOf, values
JMethod valueOfMethod = type.getMethods().get(3);
JMethod valuesMethod = type.getMethods().get(4);
{
assert "valueOf".equals(valueOfMethod.getName());
writeEnumValueOfMethod(type, valueOfMethod, valuesMethod);
}
{
assert "values".equals(valuesMethod.getName());
writeEnumValuesMethod(type, valuesMethod);
}
}
private void processNativeMethod(MethodDeclaration x) {
JMethod method = curMethod.method;
JsniMethod jsniMethod = jsniMethods.get(x);
assert jsniMethod != null;
SourceInfo info = method.getSourceInfo();
JsFunction jsFunction = jsniMethod.function();
JsniMethodBody body = new JsniMethodBody(info);
method.setBody(body);
jsFunction.setFromJava(true);
body.setFunc(jsFunction);
// Resolve locals, params, and JSNI.
JsParameterResolver localResolver = new JsParameterResolver(jsFunction);
localResolver.accept(jsFunction);
JsniReferenceCollector jsniReferenceCollector = new JsniReferenceCollector(body);
jsniReferenceCollector.accept(jsFunction);
}
private void processSuperCallLocalArgs(ReferenceBinding superClass, JMethodCall call) {
if (superClass.syntheticOuterLocalVariables() != null) {
for (SyntheticArgumentBinding arg : superClass.syntheticOuterLocalVariables()) {
// TODO: use emulation path here.
// Got to be one of my params
JType varType = typeMap.get(arg.type);
String varName = intern(arg.name);
JParameter param = null;
for (JParameter paramIt : curMethod.method.getParams()) {
if (varType == paramIt.getType() && varName.equals(paramIt.getName())) {
param = paramIt;
}
}
if (param == null) {
throw new InternalCompilerException(
"Could not find matching local arg for explicit super ctor call.");
}
call.addArg(new JParameterRef(call.getSourceInfo(), param));
}
}
}
// Only called on nested instances constructors (explicitConstructorCalls) that are of the
// form: outer.super(...) or super(...)
//
// Will set outer (in the first case) or the implicit enclosing object reference to
// be the first parameter of super(...)
private void processSuperCallThisArgs(ReferenceBinding superClass, JMethodCall call,
JExpression qualifier, Expression qualification) {
// Explicit super calls can only happend inside constructors
assert curMethod.scope.isInsideConstructor();
if (superClass.syntheticEnclosingInstanceTypes() != null) {
// there can only be ONE immediate enclosing instance.
assert superClass.syntheticEnclosingInstanceTypes().length == 1;
ReferenceBinding targetType = superClass.syntheticEnclosingInstanceTypes()[0];
if (qualification != null) {
// Outer object is the qualifier.
call.addArg(qualifier);
} else {
// Get implicit outer object.
call.addArg(resolveThisReference(call.getSourceInfo(), targetType, false, curMethod.scope));
}
}
}
private void processThisCallLocalArgs(ReferenceBinding binding, JMethodCall call) {
if (binding.syntheticOuterLocalVariables() != null) {
for (SyntheticArgumentBinding arg : binding.syntheticOuterLocalVariables()) {
JParameter param = (JParameter) curMethod.locals.get(arg);
assert param != null;
call.addArg(new JParameterRef(call.getSourceInfo(), param));
}
}
}
private void processThisCallThisArgs(ReferenceBinding binding, JMethodCall call) {
if (binding.syntheticEnclosingInstanceTypes() != null) {
Iterator<JParameter> paramIt = curMethod.method.getParams().iterator();
if (curClass.classType.isEnumOrSubclass() != null) {
// Skip past the enum args.
paramIt.next();
paramIt.next();
}
for (@SuppressWarnings("unused")
ReferenceBinding argType : binding.syntheticEnclosingInstanceTypes()) {
JParameter param = paramIt.next();
call.addArg(new JParameterRef(call.getSourceInfo(), param));
}
}
}
private void push(JNode node) {
nodeStack.add(node);
}
private void pushBinaryOp(Expression x, JBinaryOperator op, Expression lhs, Expression rhs) {
try {
JType type = typeMap.get(x.resolvedType);
SourceInfo info = makeSourceInfo(x);
JExpression exprArg2 = pop(rhs);
JExpression exprArg1 = pop(lhs);
push(new JBinaryOperation(info, type, op, exprArg1, exprArg2));
} catch (Throwable e) {
throw translateException(x, e);
}
}
private void pushInitializerMethodInfo(FieldDeclaration x, MethodScope scope) {
JMethod initMeth;
if (x.isStatic()) {
initMeth = curClass.type.getClinitMethod();
} else {
initMeth = curClass.type.getInitMethod();
}
pushMethodInfo(new MethodInfo(initMeth, (JMethodBody) initMeth.getBody(), scope));
}
private void pushMethodInfo(MethodInfo newInfo) {
methodStack.push(curMethod);
curMethod = newInfo;
}
private void pushNewExpression(SourceInfo info, AllocationExpression x, Expression qualifier,
List<JExpression> arguments, BlockScope scope) {
TypeBinding typeBinding = x.resolvedType;
if (typeBinding.constantPoolName() == null) {
/*
* Weird case: if JDT determines that this local class is totally
* uninstantiable, it won't bother allocating a local name.
*/
push(JNullLiteral.INSTANCE);
return;
}
assert typeBinding.isClass() || typeBinding.isEnum();
MethodBinding b = x.binding;
assert b.isConstructor();
JConstructor ctor = (JConstructor) typeMap.get(b);
JMethodCall call = new JNewInstance(info, ctor);
JExpression qualExpr = pop(qualifier);
// Enums: hidden arguments for the name and id.
if (x.enumConstant != null) {
call.addArgs(getStringLiteral(info, x.enumConstant.name), JIntLiteral
.get(x.enumConstant.binding.original().id));
}
// Synthetic args for inner classes
ReferenceBinding targetBinding = (ReferenceBinding) b.declaringClass.erasure();
boolean isNested = JdtUtil.isInnerClass(targetBinding);
if (isNested) {
// Synthetic this args for inner classes
if (targetBinding.syntheticEnclosingInstanceTypes() != null) {
ReferenceBinding checkedTargetType =
targetBinding.isAnonymousType() ? (ReferenceBinding) targetBinding.superclass()
.erasure() : targetBinding;
ReferenceBinding targetEnclosingType = checkedTargetType.enclosingType();
for (ReferenceBinding argType : targetBinding.syntheticEnclosingInstanceTypes()) {
argType = (ReferenceBinding) argType.erasure();
if (qualifier != null && argType == targetEnclosingType) {
call.addArg(qualExpr);
} else {
JExpression thisRef = resolveThisReference(info, argType, false, scope);
call.addArg(thisRef);
}
}
}
}
// Plain old regular user arguments
call.addArgs(arguments);
// Synthetic args for inner classes
if (isNested) {
// Synthetic locals for local classes
if (targetBinding.syntheticOuterLocalVariables() != null) {
for (SyntheticArgumentBinding arg : targetBinding.syntheticOuterLocalVariables()) {
LocalVariableBinding targetVariable = arg.actualOuterLocalVariable;
VariableBinding[] path = scope.getEmulationPath(targetVariable);
assert path.length == 1;
if (curMethod.scope.isInsideInitializer()
&& path[0] instanceof SyntheticArgumentBinding) {
SyntheticArgumentBinding sb = (SyntheticArgumentBinding) path[0];
JField field = curClass.syntheticFields.get(sb);
assert field != null;
call.addArg(makeInstanceFieldRef(info, field));
} else if (path[0] instanceof LocalVariableBinding) {
JExpression localRef = makeLocalRef(info, (LocalVariableBinding) path[0]);
call.addArg(localRef);
} else if (path[0] instanceof FieldBinding) {
JField field = typeMap.get((FieldBinding) path[0]);
assert field != null;
call.addArg(makeInstanceFieldRef(info, field));
} else {
throw new InternalCompilerException("Unknown emulation path.");
}
}
}
}
if (ctor.getEnclosingType() == javaLangString) {
/*
* MAGIC: java.lang.String is implemented as a JavaScript String
* primitive with a modified prototype. This requires funky handling of
* constructor calls. We find a method named _String() whose signature
* matches the requested constructor
*
* TODO(scottb): consider moving this to a later pass.
*/
MethodBinding staticBinding =
targetBinding.getExactMethod(_STRING, b.parameters, curCud.scope);
assert staticBinding.isStatic();
JMethod staticMethod = typeMap.get(staticBinding);
JMethodCall newCall = new JMethodCall(info, null, staticMethod);
newCall.addArgs(call.getArgs());
call = newCall;
}
push(call);
}
/**
* Don't process unreachable statements, because JDT doesn't always fully
* resolve them, which can crash us.
*/
private Statement[] reduceToReachable(Statement[] statements) {
if (statements == null) {
return null;
}
int reachableCount = 0;
for (Statement statement : statements) {
if ((statement.bits & ASTNode.IsReachable) != 0) {
++reachableCount;
}
}
if (reachableCount == statements.length) {
return statements;
}
Statement[] newStatments = new Statement[reachableCount];
int index = 0;
for (Statement statement : statements) {
if ((statement.bits & ASTNode.IsReachable) != 0) {
newStatments[index++] = statement;
}
}
return newStatments;
}
private JExpression resolveNameReference(NameReference x, BlockScope scope) {
SourceInfo info = makeSourceInfo(x);
Binding binding = x.binding;
if (isOptimizableCompileTimeConstant(binding)) {
return getConstant(info, x.constant);
}
JExpression result = null;
if (binding instanceof LocalVariableBinding) {
LocalVariableBinding b = (LocalVariableBinding) binding;
if ((x.bits & ASTNode.DepthMASK) != 0 || scope.isLambdaScope()) {
VariableBinding[] path = scope.getEmulationPath(b);
if (path == null) {
/*
* Don't like this, but in rare cases (e.g. the variable is only
* ever used as an unnecessary qualifier) JDT provides no emulation
* to the desired variable.
*/
// throw new InternalCompilerException("No emulation path.");
return null;
}
assert path.length == 1;
if (curMethod.scope.isInsideInitializer() && path[0] instanceof SyntheticArgumentBinding) {
SyntheticArgumentBinding sb = (SyntheticArgumentBinding) path[0];
JField field = curClass.syntheticFields.get(sb);
assert field != null;
result = makeInstanceFieldRef(info, field);
} else if (path[0] instanceof LocalVariableBinding) {
result = makeLocalRef(info, (LocalVariableBinding) path[0]);
} else if (path[0] instanceof FieldBinding) {
FieldBinding fb = (FieldBinding) path[0];
assert curClass.typeDecl.binding.isCompatibleWith(x.actualReceiverType.erasure());
JField field = typeMap.get(fb);
assert field != null;
result = makeInstanceFieldRef(info, field);
} else {
throw new InternalCompilerException("Unknown emulation path.");
}
} else {
result = makeLocalRef(info, b);
}
} else if (binding instanceof FieldBinding) {
FieldBinding b = ((FieldBinding) x.binding).original();
JField field = typeMap.get(b);
assert field != null;
JExpression thisRef = null;
if (!b.isStatic()) {
thisRef = resolveThisReference(info, (ReferenceBinding) x.actualReceiverType, false, scope);
}
result = new JFieldRef(info, thisRef, field, curClass.type);
} else {
return null;
}
assert result != null;
return result;
}
private JExpression simplify(JExpression result, Expression x) {
return maybeBoxOrUnbox(result, x.implicitConversion);
}
private JExpression synthesizeCallToOrdinal(BlockScope scope, SourceInfo info,
JExpression expression) {
ReferenceBinding javaLangEnum = scope.getJavaLangEnum();
MethodBinding ordinal = javaLangEnum.getMethods(ORDINAL)[0];
expression = new JMethodCall(info, expression, typeMap.get(ordinal));
return expression;
}
private JExpression unbox(JExpression original, int implicitConversion) {
int compileTypeId = implicitConversion & TypeIds.COMPILE_TYPE_MASK;
ClassScope scope = curClass.scope;
TypeBinding targetBinding = TypeBinding.wellKnownType(scope, compileTypeId);
if (!(targetBinding instanceof BaseTypeBinding)) {
// Direct cast from non-boxed-type reference type to a primitive type,
// wrap with a cast operation of the (boxed) expected type.
int runtimeTypeId = (implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4;
TypeBinding runtimeTypeBinding = TypeBinding.wellKnownType(scope, runtimeTypeId);
ReferenceBinding boxType = (ReferenceBinding) scope.boxing(runtimeTypeBinding);
original =
new JCastOperation(original.getSourceInfo(), typeMap.get(boxType), original);
targetBinding = runtimeTypeBinding;
assert (targetBinding instanceof BaseTypeBinding);
}
BaseTypeBinding primitiveType = (BaseTypeBinding) targetBinding;
ReferenceBinding boxType = (ReferenceBinding) scope.boxing(primitiveType);
char[] selector = CharOperation.concat(primitiveType.simpleName, VALUE);
MethodBinding valueMethod =
boxType.getExactMethod(selector, NO_TYPES, scope.compilationUnitScope());
assert valueMethod != null;
JMethod unboxMethod = typeMap.get(valueMethod);
JMethodCall call = new JMethodCall(original.getSourceInfo(), original, unboxMethod);
return call;
}
private void writeEnumValueOfMethod(JEnumType type, JMethod method, JMethod valuesMethod) {
JField mapField;
TypeBinding mapType;
ReferenceBinding enumType = curCud.scope.getJavaLangEnum();
{
/*
* Make an inner class to hold a lazy-init name-value map. We use a
* class to take advantage of its clinit.
*
* class Map { $MAP = Enum.createValueOfMap(values()); }
*/
SourceInfo info = type.getSourceInfo();
JClassType mapClass = new JClassType(info, intern(type.getName() + "$Map"), false, true);
mapClass.setSuperClass(javaLangObject);
mapClass.setEnclosingType(type);
newTypes.add(mapClass);
MethodBinding[] createValueOfMapBindings = enumType.getMethods(CREATE_VALUE_OF_MAP);
if (createValueOfMapBindings.length == 0) {
throw new RuntimeException(
"Unexpectedly unable to access Enum.createValueOfMap via reflection. "
+ "Likely a dependency on the com.google.gwt.user.User module is missing.");
}
MethodBinding createValueOfMapBinding = createValueOfMapBindings[0];
mapType = createValueOfMapBinding.returnType;
mapField = new JField(info, "$MAP", mapClass, typeMap.get(mapType), true, Disposition.FINAL,
AccessModifier.PRIVATE);
mapClass.addField(mapField);
JMethodCall call = new JMethodCall(info, null, typeMap.get(createValueOfMapBinding));
call.addArg(new JMethodCall(info, null, valuesMethod));
JFieldRef mapRef = new JFieldRef(info, null, mapField, mapClass);
JDeclarationStatement declStmt = new JDeclarationStatement(info, mapRef, call);
JMethod clinit =
createSyntheticMethod(info, "$clinit", mapClass, JPrimitiveType.VOID, false, true,
true, AccessModifier.PRIVATE);
JBlock clinitBlock = ((JMethodBody) clinit.getBody()).getBlock();
clinitBlock.addStmt(declStmt);
}
/*
* return Enum.valueOf(Enum$Map.Map.$MAP, name);
*/
{
SourceInfo info = method.getSourceInfo();
MethodBinding valueOfBinding =
enumType.getExactMethod(VALUE_OF, new TypeBinding[]{
mapType, curCud.scope.getJavaLangString()}, curCud.scope);
assert valueOfBinding != null;
JFieldRef mapRef = new JFieldRef(info, null, mapField, type);
JParameterRef nameRef = new JParameterRef(info, method.getParams().get(0));
JMethodCall call = new JMethodCall(info, null, typeMap.get(valueOfBinding));
call.addArgs(mapRef, nameRef);
implementMethod(method, call);
}
}
private JCastOperation buildCastOperation(SourceInfo info, JType[] castTypes,
JExpression expression) {
return buildCastOperation(info, castTypes, expression, 0);
}
private JCastOperation buildCastOperation(SourceInfo info, JType[] castTypes,
JExpression expression, int idx) {
if (idx == castTypes.length - 1) {
return new JCastOperation(info, castTypes[idx], expression);
} else {
return new JCastOperation(info, castTypes[idx],
buildCastOperation(info, castTypes, expression, idx + 1));
}
}
private JReferenceType[] processIntersectionCastType(IntersectionTypeBinding18 type) {
JReferenceType[] castTypes = new JReferenceType[type.intersectingTypes.length];
int i = 0;
for (ReferenceBinding intersectingTypeBinding : type.intersectingTypes) {
JType intersectingType = typeMap.get(intersectingTypeBinding);
assert (intersectingType instanceof JReferenceType);
castTypes[i++] = ((JReferenceType) intersectingType);
}
return castTypes;
}
private JType[] processCastType(TypeBinding type) {
if (type instanceof IntersectionTypeBinding18) {
return processIntersectionCastType((IntersectionTypeBinding18) type);
} else {
return new JType[] {typeMap.get(type)};
}
}
private JInterfaceType[] processIntersectionTypeForLambda(IntersectionTypeBinding18 type,
BlockScope scope, String samSignature) {
List<JInterfaceType> interfaces = Lists.newArrayList();
for (ReferenceBinding intersectingTypeBinding : type.intersectingTypes) {
if (shouldImplements(intersectingTypeBinding, scope, samSignature)) {
JType intersectingType = typeMap.get(intersectingTypeBinding);
assert (intersectingType instanceof JInterfaceType);
interfaces.add(((JInterfaceType) intersectingType));
}
}
return Iterables.toArray(interfaces, JInterfaceType.class);
}
private boolean isFunctionalInterfaceWithMethod(ReferenceBinding referenceBinding, Scope scope,
String samSignature) {
if (!referenceBinding.isInterface()) {
return false;
}
MethodBinding abstractMethod = referenceBinding.getSingleAbstractMethod(scope, false);
return abstractMethod != null && abstractMethod.isValidBinding()
&& JdtUtil.signature(abstractMethod).equals(samSignature);
}
private boolean isInterfaceHasNoAbstractMethod(ReferenceBinding referenceBinding, Scope scope) {
List<MethodBinding> abstractMethods = getInterfaceAbstractMethods(referenceBinding, scope);
return abstractMethods != null && abstractMethods.size() == 0;
}
private boolean shouldImplements(ReferenceBinding referenceBinding, Scope scope,
String samSignature) {
return isFunctionalInterfaceWithMethod(referenceBinding, scope, samSignature)
|| isInterfaceHasNoAbstractMethod(referenceBinding, scope);
}
/**
* Collect all abstract methods in an interface and its super interfaces.
*
* In the case of multiple inheritance like this,
*
* interface I {m();}
* interface J {default m();}
* interface K extends I, J{}
*
* the abstract methods of K include m();
*/
private List<MethodBinding> getInterfaceAbstractMethods(ReferenceBinding referenceBinding,
Scope scope) {
if (!referenceBinding.isInterface() || !referenceBinding.isValidBinding()) {
return null;
}
List<MethodBinding> abstractMethods = Lists.newLinkedList();
// add all abstract methods from super interfaces.
for (ReferenceBinding superInterface : referenceBinding.superInterfaces()) {
List<MethodBinding> abstractMethodsFromSupers =
getInterfaceAbstractMethods(superInterface, scope);
if (abstractMethodsFromSupers != null && abstractMethodsFromSupers.size() > 0) {
abstractMethods.addAll(abstractMethodsFromSupers);
}
}
for (MethodBinding method : referenceBinding.methods()) {
if (method == null || method.isStatic() || method.redeclaresPublicObjectMethod(scope)) {
continue;
}
// remove the overridden methods in the super interfaces.
for (MethodBinding abstractMethodFromSupers : abstractMethods) {
if (MethodVerifier.doesMethodOverride(method, abstractMethodFromSupers,
scope.environment())) {
abstractMethods.remove(abstractMethodFromSupers);
}
}
// add method to abstract methods if it is not a default method.
if (!method.isDefaultMethod()) {
abstractMethods.add(method);
}
}
return abstractMethods;
}
}
static class ClassInfo {
public final JClassType classType;
public final ClassScope scope;
public final Map<SyntheticArgumentBinding, JField> syntheticFields =
new IdentityHashMap<SyntheticArgumentBinding, JField>();
public final JDeclaredType type;
public final TypeDeclaration typeDecl;
public ClassInfo(JDeclaredType type, TypeDeclaration x) {
this.type = type;
this.classType = (type instanceof JClassType) ? (JClassType) type : null;
this.typeDecl = x;
this.scope = x.scope;
}
public JDeclaredType getClassOrInterface() {
return classType == null ? type : classType;
}
}
static class CudInfo {
public final CompilationUnitScope scope;
public final int[] separatorPositions;
public final CompilationUnitDeclaration cud;
public CudInfo(CompilationUnitDeclaration cud) {
separatorPositions = cud.compilationResult().getLineSeparatorPositions();
scope = cud.scope;
this.cud = cud;
}
}
static class MethodInfo {
public final JMethodBody body;
public final Map<String, JLabel> labels = Maps.newHashMap();
public final Map<LocalVariableBinding, JVariable> locals =
new IdentityHashMap<LocalVariableBinding, JVariable>();
public final JMethod method;
public final MethodScope scope;
public MethodInfo(JMethod method, JMethodBody methodBody, MethodScope methodScope) {
this.method = method;
this.body = methodBody;
this.scope = methodScope;
}
}
/**
* Manually tracked version count.
*
* TODO(zundel): something much more awesome?
*/
private static final long AST_VERSION = 3;
private static final char[] _STRING = "_String".toCharArray();
private static final String ARRAY_LENGTH_FIELD = "length";
private static final int MAX_INLINEABLE_ENUM_SIZE = 10;
/**
* Reflective access to {@link ForeachStatement#collectionElementType}.
*/
private static final Field collectionElementTypeField;
private static final Field haveReceiverField;
private static final char[] CREATE_VALUE_OF_MAP = "createValueOfMap".toCharArray();
private static final char[] HAS_NEXT = "hasNext".toCharArray();
private static final char[] ITERATOR = "iterator".toCharArray();
private static final char[] NEXT = "next".toCharArray();
private static final TypeBinding[] NO_TYPES = new TypeBinding[0];
private static final char[] ORDINAL = "ordinal".toCharArray();
private static final Interner<String> stringInterner = StringInterner.get();
private static final char[] VALUE = "Value".toCharArray();
private static final char[] VALUE_OF = "valueOf".toCharArray();
private static final char[] VALUES = "values".toCharArray();
static {
InternalCompilerException.preload();
try {
collectionElementTypeField = ForeachStatement.class.getDeclaredField("collectionElementType");
collectionElementTypeField.setAccessible(true);
} catch (Exception e) {
throw new RuntimeException(
"Unexpectedly unable to access ForeachStatement.collectionElementType via reflection", e);
}
try {
haveReceiverField = ReferenceExpression.class.getDeclaredField("haveReceiver");
haveReceiverField.setAccessible(true);
} catch (Exception e) {
throw new RuntimeException(
"Unexpectedly unable to access ReferenceExpression.haveReceiver via reflection", e);
}
}
/**
* Returns a serialization version number. Used to determine if the AST
* contained within cached compilation units is compatible with the current
* version of GWT.
*/
public static long getSerializationVersion() {
// TODO(zundel): something much awesomer.
return AST_VERSION;
}
static Disposition getFieldDisposition(FieldBinding binding) {
Disposition disposition;
if (isCompileTimeConstant(binding)) {
disposition = Disposition.COMPILE_TIME_CONSTANT;
} else if (binding.isFinal()) {
disposition = Disposition.FINAL;
} else if (binding.isVolatile()) {
disposition = Disposition.VOLATILE;
} else {
disposition = Disposition.NONE;
}
return disposition;
}
static String intern(char[] cs) {
return intern(String.valueOf(cs));
}
static String intern(String s) {
return stringInterner.intern(s);
}
private boolean isOptimizableCompileTimeConstant(Binding binding) {
if (binding instanceof LocalVariableBinding &&
((LocalVariableBinding) binding).constant() != Constant.NotAConstant) {
// Propagate constants in local variables regardless whether we are optimizing compile time
// constants or not. This is necessary as the JDT will not compute an emulation path for a
// local constant referred in a nested class closure.
return true;
}
if (!(binding instanceof FieldBinding)) {
return false;
}
FieldBinding fieldBinding = (FieldBinding) binding;
return (compilerContext.getOptions().shouldJDTInlineCompileTimeConstants()
|| isBinaryBinding(fieldBinding.declaringClass)) &&
isCompileTimeConstant(fieldBinding);
}
private static boolean isCompileTimeConstant(FieldBinding binding) {
assert !binding.isFinal() || !binding.isVolatile();
boolean isCompileTimeConstant = binding.isStatic() && binding.isFinal() &&
binding.constant() != Constant.NotAConstant;
assert !isCompileTimeConstant || binding.type.isBaseType() ||
(binding.type.id == TypeIds.T_JavaLangString);
return isCompileTimeConstant;
}
private boolean isBinaryBinding(ReferenceBinding binding) {
if (binding instanceof BinaryTypeBinding) {
// Is it really a BinaryBinding? If a source resource exists, the BinaryTypeBinding is
// considered a source type binding.
return !compilerContext.getMinimalRebuildCache().isSourceCompilationUnit(
JdtUtil.getDefiningCompilationUnitType(binding));
}
return false;
}
CudInfo curCud = null;
JClassType javaLangClass = null;
JClassType javaLangObject = null;
JClassType javaLangString = null;
JClassType javaLangThrowable = null;
Map<MethodDeclaration, JsniMethod> jsniMethods;
Map<String, Binding> jsniRefs;
final ReferenceMapper typeMap = new ReferenceMapper();
private final AstVisitor astVisitor = new AstVisitor();
private List<JDeclaredType> newTypes;
private String sourceMapPath;
private CompilerContext compilerContext;
/**
* Externalized class and method form for Exceptions.safeClose() to provide support
* for try-with-resources.
*
* The externalized form will be resolved during AST stitching.
*/
static JMethod SAFE_CLOSE_METHOD = JMethod.getExternalizedMethod("com.google.gwt.lang.Exceptions",
"safeClose(Ljava/lang/AutoCloseable;Ljava/lang/Throwable;)Ljava/lang/Throwable;", true);
private List<JDeclaredType> processImpl() {
CompilationUnitDeclaration cud = curCud.cud;
if (cud.types == null) {
return Collections.emptyList();
}
for (TypeDeclaration typeDecl : cud.types) {
createTypes(typeDecl);
}
// Now that types exist, cache Object, String, etc.
javaLangObject = (JClassType) typeMap.get(cud.scope.getJavaLangObject());
javaLangString = (JClassType) typeMap.get(cud.scope.getJavaLangString());
javaLangClass = (JClassType) typeMap.get(cud.scope.getJavaLangClass());
javaLangThrowable = (JClassType) typeMap.get(cud.scope.getJavaLangThrowable());
for (TypeDeclaration typeDecl : cud.types) {
// Resolve super type / interface relationships.
resolveTypeRefs(typeDecl);
}
for (TypeDeclaration typeDecl : cud.types) {
// Create fields and empty methods.
createMembers(typeDecl);
}
for (TypeDeclaration typeDecl : cud.types) {
// Build the code.
typeDecl.traverse(astVisitor, cud.scope);
}
return newTypes;
}
private GwtAstBuilder(CompilationUnitDeclaration cud, String sourceMapPath,
Map<MethodDeclaration, JsniMethod> jsniMethods, Map<String, Binding> jsniRefs,
CompilerContext compilerContext) {
this.sourceMapPath = sourceMapPath;
this.jsniRefs = jsniRefs;
this.jsniMethods = jsniMethods;
this.compilerContext = compilerContext;
newTypes = Lists.newArrayList();
curCud = new CudInfo(cud);
}
/**
* Builds all the GWT AST nodes that correspond to one Java source file.
*
* @param cud The compiled form of the Java source from the JDT.
* @param sourceMapPath the path that should be included in a sourcemap.
* @param jsniMethods Native methods to add to the AST.
* @param jsniRefs Map from JSNI references to their JDT definitions.
* @param compilerContext the compiler context.
* @return All the types seen in this source file.
*/
public static List<JDeclaredType> process(CompilationUnitDeclaration cud, String sourceMapPath,
Map<MethodDeclaration, JsniMethod> jsniMethods, Map<String, Binding> jsniRefs,
CompilerContext compilerContext) {
return new GwtAstBuilder(cud, sourceMapPath, jsniMethods, jsniRefs, compilerContext)
.processImpl();
}
SourceInfo makeSourceInfo(AbstractMethodDeclaration x) {
int startLine =
Util.getLineNumber(x.sourceStart, curCud.separatorPositions, 0,
curCud.separatorPositions.length - 1);
return SourceOrigin.create(x.sourceStart, x.bodyEnd, startLine, sourceMapPath);
}
SourceInfo makeSourceInfo(ASTNode x) {
int startLine =
Util.getLineNumber(x.sourceStart, curCud.separatorPositions, 0,
curCud.separatorPositions.length - 1);
return SourceOrigin.create(x.sourceStart, x.sourceEnd, startLine, sourceMapPath);
}
InternalCompilerException translateException(ASTNode node, Throwable e) {
if (e instanceof VirtualMachineError) {
// Always rethrow VM errors (an attempt to wrap may fail).
throw (VirtualMachineError) e;
}
InternalCompilerException ice;
if (e instanceof InternalCompilerException) {
ice = (InternalCompilerException) e;
} else {
ice = new InternalCompilerException("Error constructing Java AST", e);
}
if (node != null) {
ice.addNode(node.getClass().getName(), node.toString(), makeSourceInfo(node));
}
return ice;
}
private void createField(FieldDeclaration x) {
if (x instanceof Initializer) {
return;
}
SourceInfo info = makeSourceInfo(x);
FieldBinding binding = x.binding;
JType type = typeMap.get(binding.type);
JDeclaredType enclosingType = (JDeclaredType) typeMap.get(binding.declaringClass);
JField field;
if (x.initialization != null && x.initialization instanceof AllocationExpression
&& ((AllocationExpression) x.initialization).enumConstant != null) {
field = new JEnumField(info, intern(binding.name), binding.original().id,
(JEnumType) enclosingType, (JClassType) type, AccessModifier.fromFieldBinding(binding));
} else {
field =
new JField(info, intern(binding.name), enclosingType, type, binding.isStatic(),
getFieldDisposition(binding), AccessModifier.fromFieldBinding(binding));
}
enclosingType.addField(field);
JsInteropUtil.maybeSetExportedField(x, field);
typeMap.setField(binding, field);
}
private void createMembers(TypeDeclaration x) {
SourceTypeBinding binding = x.binding;
JDeclaredType type = (JDeclaredType) typeMap.get(binding);
SourceInfo info = type.getSourceInfo();
try {
/**
* We emulate static initializers and instance initializers as methods. As
* in other cases, this gives us: simpler AST, easier to optimize, more
* like output JavaScript. Clinit is always in slot 0, init (if it exists)
* is always in slot 1.
*/
assert type.getMethods().size() == 0;
createSyntheticMethod(info, CLINIT_NAME, type, JPrimitiveType.VOID, false, true, true,
AccessModifier.PRIVATE);
if (type instanceof JClassType) {
assert type.getMethods().size() == 1;
createSyntheticMethod(info, INIT_NAME, type, JPrimitiveType.VOID, false, false, true,
AccessModifier.PRIVATE);
// Add a getClass() implementation for all non-Object, non-String classes.
if (isSyntheticGetClassNeeded(x, type)) {
assert type.getMethods().size() == 2;
createSyntheticMethod(info, "getClass", type, javaLangClass, false, false, false,
AccessModifier.PUBLIC);
}
}
if (type instanceof JEnumType) {
{
assert type.getMethods().size() == 3;
MethodBinding valueOfBinding =
binding.getExactMethod(VALUE_OF, new TypeBinding[]{x.scope.getJavaLangString()},
curCud.scope);
assert valueOfBinding != null;
createSyntheticMethodFromBinding(info, valueOfBinding, new String[]{"name"});
}
{
assert type.getMethods().size() == 4;
MethodBinding valuesBinding = binding.getExactMethod(VALUES, NO_TYPES, curCud.scope);
assert valuesBinding != null;
createSyntheticMethodFromBinding(info, valuesBinding, null);
}
}
if (x.fields != null) {
for (FieldDeclaration field : x.fields) {
createField(field);
}
}
if (x.methods != null) {
for (AbstractMethodDeclaration method : x.methods) {
createMethod(method);
}
}
if (x.memberTypes != null) {
for (TypeDeclaration memberType : x.memberTypes) {
createMembers(memberType);
}
}
} catch (Throwable e) {
InternalCompilerException ice = translateException(null, e);
StringBuffer sb = new StringBuffer();
x.printHeader(0, sb);
ice.addNode(x.getClass().getName(), sb.toString(), type.getSourceInfo());
throw ice;
}
}
private boolean isSyntheticGetClassNeeded(TypeDeclaration typeDeclaration, JDeclaredType type) {
// TODO(rluble): We should check whether getClass is implemented by type and only
// instead of blacklisting.
return type != javaLangObject && type != javaLangString && type.getSuperClass() != null &&
!JSORestrictionsChecker.isJsoSubclass(typeDeclaration.binding);
}
private void createMethod(AbstractMethodDeclaration x) {
if (x instanceof Clinit) {
return;
}
SourceInfo info = makeSourceInfo(x);
MethodBinding b = x.binding;
ReferenceBinding declaringClass = (ReferenceBinding) b.declaringClass.erasure();
Set<String> alreadyNamedVariables = Sets.newHashSet();
JDeclaredType enclosingType = (JDeclaredType) typeMap.get(declaringClass);
assert !enclosingType.isExternal();
JMethod method;
boolean isNested = JdtUtil.isInnerClass(declaringClass);
if (x.isConstructor()) {
method =
new JConstructor(info, (JClassType) enclosingType, AccessModifier.fromMethodBinding(b));
if (x.isDefaultConstructor()) {
((JConstructor) method).setDefaultConstructor();
}
if (x.binding.declaringClass.isEnum()) {
// Enums have hidden arguments for name and value
method.addParam(new JParameter(info, "enum$name", typeMap.get(x.scope.getJavaLangString()),
true, false, method));
method.addParam(new JParameter(info, "enum$ordinal", JPrimitiveType.INT, true, false,
method));
}
// add synthetic args for outer this
if (isNested) {
NestedTypeBinding nestedBinding = (NestedTypeBinding) declaringClass;
if (nestedBinding.enclosingInstances != null) {
for (int i = 0; i < nestedBinding.enclosingInstances.length; ++i) {
SyntheticArgumentBinding arg = nestedBinding.enclosingInstances[i];
String argName = String.valueOf(arg.name);
if (alreadyNamedVariables.contains(argName)) {
argName += "_" + i;
}
createParameter(info, arg, argName, method);
alreadyNamedVariables.add(argName);
}
}
}
} else {
method =
new JMethod(info, intern(b.selector), enclosingType, typeMap.get(b.returnType), b
.isAbstract(), b.isStatic(), b.isFinal(), AccessModifier.fromMethodBinding(b));
}
// User args.
createParameters(method, x);
if (x.isConstructor()) {
if (isNested) {
// add synthetic args for locals
NestedTypeBinding nestedBinding = (NestedTypeBinding) declaringClass;
// add synthetic args for outer this and locals
if (nestedBinding.outerLocalVariables != null) {
for (int i = 0; i < nestedBinding.outerLocalVariables.length; ++i) {
SyntheticArgumentBinding arg = nestedBinding.outerLocalVariables[i];
String argName = String.valueOf(arg.name);
if (alreadyNamedVariables.contains(argName)) {
argName += "_" + i;
}
createParameter(info, arg, argName, method);
alreadyNamedVariables.add(argName);
}
}
}
}
mapExceptions(method, b);
if (b.isSynthetic()) {
method.setSynthetic();
}
if (b.isDefaultMethod()) {
method.setDefaultMethod();
}
enclosingType.addMethod(method);
JsInteropUtil.maybeSetJsinteropMethodProperties(x, method);
processAnnotations(x, method);
typeMap.setMethod(b, method);
}
private void processAnnotations(AbstractMethodDeclaration x,
JMethod method) {
maybeAddMethodSpecialization(x, method);
maybeSetDoNotInline(x, method);
maybeSetHasNoSideEffects(x, method);
}
private void maybeSetDoNotInline(AbstractMethodDeclaration x,
JMethod method) {
if (JdtUtil.getAnnotation(x.binding, DoNotInline.class.getName()) != null) {
method.setInliningAllowed(false);
}
}
private void maybeSetHasNoSideEffects(AbstractMethodDeclaration x,
JMethod method) {
if (JdtUtil.getAnnotation(x.binding, HasNoSideEffects.class.getName()) != null) {
method.setHasSideEffects(false);
}
}
private void maybeAddMethodSpecialization(AbstractMethodDeclaration x, JMethod method) {
AnnotationBinding specializeAnnotation =
JdtUtil.getAnnotation(x.binding, SpecializeMethod.class.getName());
if (specializeAnnotation == null) {
return;
}
TypeBinding[] params =
JdtUtil.getAnnotationParameterTypeBindingArray(specializeAnnotation, "params");
assert params != null : "params is a mandatory field";
List<JType> paramTypes = new ArrayList<JType>();
for (TypeBinding pType : params) {
paramTypes.add(typeMap.get(pType));
}
TypeBinding returns =
JdtUtil.getAnnotationParameterTypeBinding(specializeAnnotation, "returns");
JType returnsType = null;
if (returns != null) {
returnsType = typeMap.get(returns);
}
String targetMethod = JdtUtil.getAnnotationParameterString(specializeAnnotation, "target");
assert targetMethod != null : "target is a mandatory parameter";
method.setSpecialization(paramTypes, returnsType, targetMethod);
}
private void createParameter(SourceInfo info, LocalVariableBinding binding, JMethod method) {
createParameter(info, binding, intern(binding.name), method);
}
private void createParameter(SourceInfo info, LocalVariableBinding binding, String name,
JMethod method) {
JParameter param =
new JParameter(info, name, typeMap.get(binding.type), binding.isFinal(), false, method);
method.addParam(param);
}
private void createParameters(JMethod method, AbstractMethodDeclaration x) {
if (x.arguments != null) {
for (Argument argument : x.arguments) {
SourceInfo info = makeSourceInfo(argument);
LocalVariableBinding binding = argument.binding;
createParameter(info, binding, method);
}
}
method.freezeParamTypes();
}
private JMethod createSyntheticMethod(SourceInfo info, String name, JDeclaredType enclosingType,
JType returnType, boolean isAbstract, boolean isStatic, boolean isFinal,
AccessModifier access, JStatement ... statements) {
JMethod method =
new JMethod(info, name, enclosingType, returnType, isAbstract, isStatic, isFinal, access);
method.freezeParamTypes();
method.setSynthetic();
JMethodBody body = new JMethodBody(info);
for (JStatement statement : statements) {
body.getBlock().addStmt(statement);
}
method.setBody(body);
enclosingType.addMethod(method);
return method;
}
private JMethod createSyntheticMethodFromBinding(SourceInfo info, MethodBinding binding,
String[] paramNames) {
JMethod method = typeMap.createMethod(info, binding, paramNames);
assert !method.isExternal();
method.setBody(new JMethodBody(info));
typeMap.setMethod(binding, method);
return method;
}
private void createTypes(TypeDeclaration x) {
SourceInfo info = makeSourceInfo(x);
try {
SourceTypeBinding binding = x.binding;
String name;
if (binding instanceof LocalTypeBinding) {
char[] localName = binding.constantPoolName();
name = new String(localName).replace('/', '.');
} else {
name = JdtUtil.asDottedString(binding.compoundName);
}
name = intern(name);
JDeclaredType type;
String jsPrototype = JsInteropUtil.maybeGetJsTypePrototype(x);
JsInteropType interopType = JsInteropUtil.maybeGetJsInteropType(x, jsPrototype);
if (binding.isClass()) {
type = new JClassType(info, name, binding.isAbstract(), binding.isFinal(), interopType);
JsInteropUtil.maybeSetJsPrototypeFlag(x, (JClassType) type);
} else if (binding.isInterface() || binding.isAnnotationType()) {
type = new JInterfaceType(info, name, interopType, jsPrototype);
} else if (binding.isEnum()) {
if (binding.isAnonymousType()) {
// Don't model an enum subclass as a JEnumType.
type = new JClassType(info, name, false, true, interopType);
} else {
type = new JEnumType(info, name, binding.isAbstract(), interopType);
}
} else {
throw new InternalCompilerException("ReferenceBinding is not a class, interface, or enum.");
}
typeMap.setSourceType(binding, type);
newTypes.add(type);
if (x.memberTypes != null) {
for (TypeDeclaration memberType : x.memberTypes) {
createTypes(memberType);
}
}
} catch (Throwable e) {
InternalCompilerException ice = translateException(null, e);
StringBuffer sb = new StringBuffer();
x.printHeader(0, sb);
ice.addNode(x.getClass().getName(), sb.toString(), info);
throw ice;
}
}
private void mapExceptions(JMethod method, MethodBinding binding) {
for (ReferenceBinding thrownBinding : binding.thrownExceptions) {
JClassType type = (JClassType) typeMap.get(thrownBinding);
method.addThrownException(type);
}
}
private void resolveTypeRefs(TypeDeclaration x) {
SourceTypeBinding binding = x.binding;
JDeclaredType type = (JDeclaredType) typeMap.get(binding);
try {
ReferenceBinding superClassBinding = binding.superclass();
if (type instanceof JClassType && superClassBinding != null) {
assert (binding.superclass().isClass() || binding.superclass().isEnum());
JClassType superClass = (JClassType) typeMap.get(superClassBinding);
((JClassType) type).setSuperClass(superClass);
}
ReferenceBinding[] superInterfaces = binding.superInterfaces();
for (ReferenceBinding superInterfaceBinding : superInterfaces) {
assert (superInterfaceBinding.isInterface());
JInterfaceType superInterface = (JInterfaceType) typeMap.get(superInterfaceBinding);
type.addImplements(superInterface);
}
ReferenceBinding enclosingBinding = binding.enclosingType();
if (enclosingBinding != null) {
type.setEnclosingType((JDeclaredType) typeMap.get(enclosingBinding));
}
if (x.memberTypes != null) {
for (TypeDeclaration memberType : x.memberTypes) {
resolveTypeRefs(memberType);
}
}
} catch (Throwable e) {
InternalCompilerException ice = translateException(null, e);
StringBuffer sb = new StringBuffer();
x.printHeader(0, sb);
ice.addNode(x.getClass().getName(), sb.toString(), type.getSourceInfo());
throw ice;
}
}
/**
* Returns the list of expressions as a single expression; returns {@code null} if the list
* is empty.
*/
private static JExpression singleExpressionFromExpressionList(SourceInfo info,
List<JExpression> incrementsExpressions) {
switch (incrementsExpressions.size()) {
case 0:
return null;
case 1:
return incrementsExpressions.get(0);
default:
return new JMultiExpression(info, incrementsExpressions);
}
}
}