Skip to content

Commit

Permalink
Rewrites TypeMethodReference and ExpressionMethodReference nodes as L…
Browse files Browse the repository at this point in the history
…ambdaExpression nodes.

	Change on 2016/05/24 by kstanger <kstanger@google.com>

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=123131528
  • Loading branch information
kstanger authored and tomball committed May 27, 2016
1 parent 378b14e commit 0c59f67
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 147 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -560,9 +560,8 @@ public boolean visit(EnhancedForStatement node) {

@Override
public boolean visit(ExpressionMethodReference node) {
assert Options
.isJava8Translator() : "ExpressionMethodReference in translator with -source less than 8.";
return printMethodReference(node);
throw new AssertionError(
"ExpressionMethodReference nodes are rewritten by MethodReferenceRewriter.");
}

@Override
Expand Down Expand Up @@ -1353,9 +1352,7 @@ public boolean visit(TypeLiteral node) {

@Override
public boolean visit(TypeMethodReference node) {
assert Options
.isJava8Translator() : "TypeMethodReference in translator with -source less than 8.";
return printMethodReference(node);
throw new AssertionError("TypeMethodReference nodes are rewritten by MethodReferenceRewriter.");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,26 @@
import com.google.devtools.j2objc.ast.ClassInstanceCreation;
import com.google.devtools.j2objc.ast.CreationReference;
import com.google.devtools.j2objc.ast.Expression;
import com.google.devtools.j2objc.ast.ExpressionMethodReference;
import com.google.devtools.j2objc.ast.LambdaExpression;
import com.google.devtools.j2objc.ast.MethodInvocation;
import com.google.devtools.j2objc.ast.Name;
import com.google.devtools.j2objc.ast.SimpleName;
import com.google.devtools.j2objc.ast.TreeUtil;
import com.google.devtools.j2objc.ast.TreeVisitor;
import com.google.devtools.j2objc.ast.Type;
import com.google.devtools.j2objc.ast.TypeMethodReference;
import com.google.devtools.j2objc.ast.VariableDeclarationFragment;
import com.google.devtools.j2objc.types.GeneratedVariableBinding;
import com.google.devtools.j2objc.util.NameTable;
import com.google.devtools.j2objc.util.BindingUtil;

import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;

import java.util.Arrays;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
Expand All @@ -44,7 +51,6 @@ public class MethodReferenceRewriter extends TreeVisitor {
public void endVisit(CreationReference node) {
ITypeBinding exprBinding = node.getTypeBinding();
ITypeBinding creationType = node.getType().getTypeBinding();
IMethodBinding functionalInterface = exprBinding.getFunctionalInterfaceMethod();
Expression invocation;
List<Expression> invocationArguments;
if (creationType.isArray()) {
Expand All @@ -60,20 +66,93 @@ public void endVisit(CreationReference node) {
LambdaExpression lambda = new LambdaExpression(
"CreationReference:" + node.getLineNumber(), exprBinding);
lambda.setBody(invocation);
addRemainingLambdaParams(
Arrays.asList(functionalInterface.getParameterTypes()), invocationArguments, lambda, null);
addParamsToInvocation(createParameters(lambda), invocationArguments);
node.replaceWith(lambda);
}

private void addRemainingLambdaParams(
Iterable<ITypeBinding> paramTypes, List<Expression> invocationArguments,
LambdaExpression lambda, char[] lastVar) {
for (ITypeBinding paramType : paramTypes) {
lastVar = NameTable.incrementVariable(lastVar);
IVariableBinding variableBinding = new GeneratedVariableBinding(
new String(lastVar), 0, paramType, false, true, null, null);
lambda.getParameters().add(new VariableDeclarationFragment(variableBinding, null));
invocationArguments.add(new SimpleName(variableBinding));
@Override
public void endVisit(ExpressionMethodReference node) {
ITypeBinding exprBinding = node.getTypeBinding();
IMethodBinding methodBinding = node.getMethodBinding();
LambdaExpression lambda = new LambdaExpression(
"ExpressionMethodReference:" + node.getLineNumber(), exprBinding);
Iterator<IVariableBinding> params = createParameters(lambda);
Expression target = TreeUtil.remove(node.getExpression());
if (!BindingUtil.isStatic(methodBinding) && target instanceof Name
&& ((Name) target).getBinding().getKind() == IBinding.TYPE) {
// The expression is actually a type name and doesn't evaluate to an invocable object.
target = new SimpleName(params.next());
}
MethodInvocation invocation = new MethodInvocation(methodBinding, target);
lambda.setBody(invocation);
addParamsToInvocation(params, invocation.getArguments());
node.replaceWith(lambda);
}

@Override
public void endVisit(TypeMethodReference node) {
ITypeBinding exprBinding = node.getTypeBinding();
IMethodBinding methodBinding = getMethodBinding(node);
LambdaExpression lambda = new LambdaExpression(
"TypeMethodReference:" + node.getLineNumber(), exprBinding);
Iterator<IVariableBinding> params = createParameters(lambda);
Expression target = null;
if (!BindingUtil.isStatic(methodBinding)) {
target = new SimpleName(params.next());
}
MethodInvocation invocation = new MethodInvocation(methodBinding, target);
lambda.setBody(invocation);
addParamsToInvocation(params, invocation.getArguments());
node.replaceWith(lambda);
}

private IMethodBinding getMethodBinding(TypeMethodReference node) {
if (node.getType().getTypeBinding().isArray()) {
// JDT does not provide the correct method binding on array types, so we find it from
// java.lang.Object.
String name = node.getName().getIdentifier();
IMethodBinding functionalInterface = node.getTypeBinding().getFunctionalInterfaceMethod();
int numParams = functionalInterface.getParameterTypes().length - 1;
for (IMethodBinding method : typeEnv.getJavaObject().getDeclaredMethods()) {
if (method.getName().equals(name) && method.getParameterTypes().length == numParams) {
return method;
}
}
throw new AssertionError("Can't find method finding for method: " + name);
}
return node.getMethodBinding();
}

private Iterator<IVariableBinding> createParameters(LambdaExpression lambda) {
IMethodBinding functionalInterface = lambda.getTypeBinding().getFunctionalInterfaceMethod();
ITypeBinding[] paramTypes = functionalInterface.getParameterTypes();
List<IVariableBinding> params = new ArrayList<>(paramTypes.length);
for (int i = 0; i < paramTypes.length; i++) {
GeneratedVariableBinding param = new GeneratedVariableBinding(
getParamName(i), 0, paramTypes[i], false, true, null, null);
params.add(param);
lambda.getParameters().add(new VariableDeclarationFragment(param, null));
}
return params.iterator();
}

private void addParamsToInvocation(
Iterator<IVariableBinding> params, List<Expression> invocationArguments) {
while (params.hasNext()) {
invocationArguments.add(new SimpleName(params.next()));
}
}

private static String getParamName(int i) {
StringBuilder sb = new StringBuilder();
while (true) {
sb.append((char) ('a' + (i % 26)));
i = i / 26;
if (i == 0) {
break;
}
i--;
}
return sb.reverse().toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import com.google.devtools.j2objc.ast.CastExpression;
import com.google.devtools.j2objc.ast.ClassInstanceCreation;
import com.google.devtools.j2objc.ast.Expression;
import com.google.devtools.j2objc.ast.ExpressionMethodReference;
import com.google.devtools.j2objc.ast.ExpressionStatement;
import com.google.devtools.j2objc.ast.FieldAccess;
import com.google.devtools.j2objc.ast.FieldDeclaration;
Expand All @@ -47,11 +46,9 @@
import com.google.devtools.j2objc.ast.TreeUtil;
import com.google.devtools.j2objc.ast.TreeVisitor;
import com.google.devtools.j2objc.ast.Type;
import com.google.devtools.j2objc.ast.TypeMethodReference;
import com.google.devtools.j2objc.ast.VariableDeclarationExpression;
import com.google.devtools.j2objc.ast.VariableDeclarationFragment;
import com.google.devtools.j2objc.ast.VariableDeclarationStatement;
import com.google.devtools.j2objc.types.GeneratedMethodBinding;
import com.google.devtools.j2objc.types.GeneratedVariableBinding;
import com.google.devtools.j2objc.util.BindingUtil;
import com.google.devtools.j2objc.util.ErrorUtil;
Expand All @@ -60,11 +57,9 @@
import com.google.j2objc.annotations.RetainedLocalRef;
import com.google.j2objc.annotations.Weak;

import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.Modifier;

import java.util.ArrayList;
import java.util.HashMap;
Expand Down Expand Up @@ -413,22 +408,6 @@ private String getLambdaUniqueName(LambdaExpression node) {
return nameTable.getFullName(owningType) + "$$Lambda$" + count;
}

@Override
public boolean visit(ExpressionMethodReference node) {
IMethodBinding methodBinding = node.getMethodBinding().getMethodDeclaration();
Expression expression = node.getExpression().copy();
MethodInvocation invocation = new MethodInvocation(methodBinding, expression);
List<Expression> invocationArguments = invocation.getArguments();
buildMethodReferenceInvocationArguments(invocationArguments, node, invocation);
IMethodBinding functionalInterface = node.getTypeBinding().getFunctionalInterfaceMethod();
if (BindingUtil.isVoid(functionalInterface.getReturnType())) {
node.setInvocation(new ExpressionStatement(invocation));
} else {
node.setInvocation(new ReturnStatement(invocation));
}
return true;
}

@Override
public boolean visit(SuperMethodReference node) {
IMethodBinding methodBinding = node.getMethodBinding().getMethodDeclaration();
Expand All @@ -446,75 +425,6 @@ public boolean visit(SuperMethodReference node) {
return true;
}

@Override
public boolean visit(TypeMethodReference node) {
IMethodBinding originalMethodBinding = node.getMethodBinding();
GeneratedMethodBinding methodBinding = GeneratedMethodBinding.newNamedMethod(
node.getName().toString(), originalMethodBinding);
methodBinding.addParameters(originalMethodBinding);
methodBinding.setModifiers(methodBinding.getModifiers() & ~Modifier.STATIC);
IMethodBinding functionalInterface = node.getTypeBinding().getFunctionalInterfaceMethod();
ITypeBinding[] functionalParams = functionalInterface.getParameterTypes();

// When node is a TypedMethodReference, the first parameter in the functional interface method
// should always be the object. Because of this, we expect this invariant to be held:
//
// functionalParams.length == originalMethodBinding.getParameterTypes().length + 1
//
// However, there are cases when JDT still puts one parameter in originalMethodBinding.
// Compare this:
//
// /* functionalParams.length == 2, originalMethodBinding.getParameterTypes().length == 1 */
// BiConsumer<Collection<T>, T> bc = Collection<T>::add;
//
// With this:
//
// /* functionalParams.length == 1, originalMethodBinding.getParameterTypes().length == 1 */
// interface H { Object copy(int[] i); }
// H h = int[]::clone;
//
// In the second case, we have to remove the first parameter in the mutable methodBinding.
//
// TODO(user): This may be a JDT quirk. Need more test cases and further investigation.
// TODO(user): This very likely won't support varargs correctly.
if (functionalParams.length == originalMethodBinding.getParameterTypes().length) {
methodBinding.getParameters().remove(0);
}
ITypeBinding[] methodParams = methodBinding.getParameterTypes();

char[] var = NameTable.incrementVariable(null);
ITypeBinding functionalParam = functionalParams[0];
IVariableBinding variableBinding = new GeneratedVariableBinding(new String(var), 0,
functionalParam, false, true, null, null);
SimpleName expression = new SimpleName(variableBinding);
MethodInvocation invocation = new MethodInvocation(methodBinding, expression);
List<Expression> invocationArguments = invocation.getArguments();
if (BindingUtil.isVoid(functionalInterface.getReturnType())) {
node.setInvocation(new ExpressionStatement(invocation));
} else {
node.setInvocation(new ReturnStatement(invocation));
}
int methodParamStopIndex = methodBinding.isVarargs() ? methodParams.length
: methodParams.length + 1;
for (int i = 1; i < methodParamStopIndex; i++) {
functionalParam = functionalParams[i];
variableBinding = new GeneratedVariableBinding(new String(var), 0, functionalParam, false,
true, null, null);
invocationArguments.add(new SimpleName(variableBinding));
var = NameTable.incrementVariable(var);
}
if (methodBinding.isVarargs()) {
for (int i = methodParamStopIndex; i < functionalInterface.getParameterTypes().length; i++) {
functionalParam = functionalParams[i];
variableBinding = new GeneratedVariableBinding(new String(var), 0,
functionalParam, false, true, null, null);
invocationArguments.add(new SimpleName(variableBinding));
var = NameTable.incrementVariable(var);
}
}
return true;
}

/**
* Fill in the arguments in a method reference invocation. The argument list must come from the
* functional interface's method, not the invoked method: this is because it is possible to
Expand All @@ -529,38 +439,11 @@ public boolean visit(TypeMethodReference node) {
public void buildMethodReferenceInvocationArguments(List<Expression> invocationArguments,
MethodReference node,
MethodInvocation invocation) {
IMethodBinding methodBinding = node.getMethodBinding();
IMethodBinding functionalInterface = node.getTypeBinding().getFunctionalInterfaceMethod();
ITypeBinding[] functionalParams = functionalInterface.getParameterTypes();
char[] var = NameTable.incrementVariable(null);
int paramIdx = 0;

// If this is an ExpressionMethodReference EXP::METHOD, and if EXP is a type binding and
// METHOD is an instance method, then EXP should be replaced with the first parameter in
// functionalParams. For example, String::compareTo matches the functional interface
// int Comparator<T>::compare(T a, T b), but the JDT method invocation node is actually
// String.compareTo() at this point. We need to fix that to a.compareTo(), so that in the
// code that follows the second functional parameter b will be filled correctly into the
// the invocation, resulting in the invocation a.compareTo(b).
if (node instanceof ExpressionMethodReference) {
Expression expression = ((ExpressionMethodReference) node).getExpression();
if (expression instanceof Name) {
IBinding binding = ((Name) expression).getBinding();
if (binding.getKind() == IBinding.TYPE) {
// Only ExpressionMethodReference needs this potential invocation fix.
assert invocation != null;
if (!BindingUtil.isStatic(methodBinding)) {
ITypeBinding functionalParam = functionalParams[paramIdx];
IVariableBinding variableBinding = new GeneratedVariableBinding(new String(var), 0,
functionalParam, false, true, null, null);
invocation.setExpression(new SimpleName(variableBinding));
var = NameTable.incrementVariable(var);
paramIdx++;
}
}
}
}

// Fill in the invocation parameters.
for (; paramIdx < functionalParams.length; paramIdx++) {
ITypeBinding functionalParam = functionalParams[paramIdx];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,12 @@ public void testExpressionReferenceStaticResolution() throws IOException {
String staticTranslation = translateSourceFile(
expressionReferenceHeader + "class Test { F fun = Q::o; }",
"Test", "Test.m");
assertTranslatedSegments(staticTranslation, "GetNonCapturingLambda", "@\"Q_oWithId_\"",
assertTranslatedSegments(staticTranslation, "GetNonCapturingLambda", "@\"Test$$Lambda$1\"",
"@selector(fWithId:)", "return Q_oWithId_(a);");
String instanceTranslation = translateSourceFile(
expressionReferenceHeader + "class Test { F fun = new Q()::o2; }",
"Test", "Test.m");
assertTranslatedSegments(instanceTranslation, "GetNonCapturingLambda", "@\"Q_o2WithId_\"",
assertTranslatedSegments(instanceTranslation, "GetNonCapturingLambda", "@\"Test$$Lambda$1\"",
"@selector(fWithId:)", "return [create_Q_init() o2WithId:a];");
}

Expand Down Expand Up @@ -106,8 +106,8 @@ public void testReferenceToInstanceMethodOfGenericType() throws IOException {
+ "}";

String impl = translateSourceFile(source, "Test", "Test.m");
assertTranslation(impl, "[((id<Collection>) nil_chk(a)) addWithId:a];");
assertNotInTranslation(impl, "return [((id<Collection>) nil_chk(a)) addWithId:a];");
assertTranslation(impl, "[((id<Collection>) nil_chk(a)) addWithId:b];");
assertNotInTranslation(impl, "return [((id<Collection>) nil_chk(a)) addWithId:b];");
}

public void testReferenceToInstanceMethodOfGenericTypeWithReturnType() throws IOException {
Expand All @@ -120,7 +120,7 @@ public void testReferenceToInstanceMethodOfGenericTypeWithReturnType() throws IO
+ "}";

String impl = translateSourceFile(source, "Test", "Test.m");
assertTranslation(impl, "return [((id<Collection>) nil_chk(a)) addWithId:a];");
assertTranslation(impl, "return [((id<Collection>) nil_chk(a)) addWithId:b];");
}

public void testVarArgs() throws IOException {
Expand All @@ -132,15 +132,14 @@ public void testVarArgs() throws IOException {
"Test", "Test.m");
assertTranslatedLines(translation,
"JreStrongAssign(&self->i_, GetNonCapturingLambda(NULL, @protocol(I), "
+ "@\"Y_mWithInt_withNSString_withNSString_\", "
+ "@selector(fooWithInt:withNSString:withNSString:),",
+ "@\"Test$$Lambda$1\", @selector(fooWithInt:withNSString:withNSString:),",
"^void(id _self, jint a, NSString * b, NSString * c) {",
"Y_mWithInt_withNSStringArray_(a, [IOSObjectArray arrayWithObjects:(id[]){ b, c } "
+ "count:2 type:NSString_class_()]);",
"}));",
"}",
"));",
"JreStrongAssign(&self->i2_, GetNonCapturingLambda(NULL, @protocol(I2), "
+ "@\"Y_mWithInt_withNSString_withNSString_withNSString_\", "
+ "@selector(fooWithInt:withNSString:withNSString:withNSString:),",
+ "@\"Test$$Lambda$2\", @selector(fooWithInt:withNSString:withNSString:withNSString:),",
"^void(id _self, jint a, NSString * b, NSString * c, NSString * d) {",
"Y_mWithInt_withNSStringArray_(a, [IOSObjectArray arrayWithObjects:(id[]){ b, c, d } "
+ "count:3 type:NSString_class_()]);");
Expand Down

0 comments on commit 0c59f67

Please sign in to comment.