/*
* Copyright 2009-2010 MBTE Sweden AB.
*
* 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 org.mbte.groovypp.compiler.transformers;
import groovy.lang.TypePolicy;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.stmt.ReturnStatement;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.classgen.BytecodeHelper;
import org.mbte.groovypp.compiler.*;
import org.mbte.groovypp.compiler.bytecode.*;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.util.*;
public class MethodCallExpressionTransformer extends ExprTransformer<MethodCallExpression> {
public Expression transform(final MethodCallExpression exp, final CompilerTransformer compiler) {
Object method = exp.getMethod();
String methodName;
if (!(method instanceof ConstantExpression) || !(((ConstantExpression) method).getValue() instanceof String)) {
if (compiler.policy == TypePolicy.STATIC) {
compiler.addError("Non-static method name", exp);
return null;
} else {
return createDynamicCall(exp, compiler);
}
} else {
methodName = (String) ((ConstantExpression) method).getValue();
}
if (methodName.equals("call") && exp.getObjectExpression() instanceof AttributeExpression) {
// special case of concurrent call x.@w(a,b,c)
}
if (exp.isSpreadSafe()) {
Parameter param = new Parameter(ClassHelper.OBJECT_TYPE, "$it");
VariableExpression ve = new VariableExpression(param);
Expression originalMethod = exp.getMethod();
ve.setSourcePosition(originalMethod);
MethodCallExpression prop = new MethodCallExpression(ve, originalMethod, exp.getArguments());
prop.setSourcePosition(originalMethod);
ReturnStatement retStat = new ReturnStatement(prop);
retStat.setSourcePosition(originalMethod);
ClosureExpression ce = new ClosureExpression(new Parameter[]{param}, retStat);
ce.setVariableScope(new VariableScope(compiler.compileStack.getScope()));
MethodCallExpression mce = new MethodCallExpression(exp.getObjectExpression(), "map", new ArgumentListExpression(ce));
mce.setSourcePosition(exp);
return compiler.transform(mce);
}
final Expression originalArgs = exp.getArguments();
Expression args = compiler.transform(originalArgs);
try {
exp.setArguments(args);
if (exp.isSafe()) {
return transformSafe(exp, compiler);
}
BytecodeExpr object;
ClassNode type;
MethodNode foundMethod = null;
final ClassNode[] argTypes = compiler.exprToTypeArray(args);
if (exp.getObjectExpression() instanceof ClassExpression) {
type = TypeUtil.wrapSafely(exp.getObjectExpression().getType());
foundMethod = findMethodWithClosureCoercion(type, methodName, argTypes, compiler, true);
if (foundMethod == null || !foundMethod.isStatic()) {
// Try methods from java.lang.Class
ClassNode clazz = TypeUtil.withGenericTypes(ClassHelper.CLASS_Type, type);
foundMethod = findMethodWithClosureCoercion(clazz, methodName, argTypes, compiler, false);
if (foundMethod == null) {
// Try some property with 'call' method.
final Object prop = resolveCallableProperty(compiler, methodName, type, true);
if (prop != null) {
final MethodNode callMethod = resolveCallMethod(compiler, argTypes, prop);
if (callMethod != null) {
return createCallMethodCall(exp, compiler, methodName, args, null, prop, callMethod, type);
}
}
return dynamicOrError(exp, compiler, methodName, type, argTypes, "Cannot find static method ");
}
object = (BytecodeExpr) compiler.transform(exp.getObjectExpression());
return createCall(exp, compiler, args, object, foundMethod);
}
if (!AccessibilityCheck.isAccessible(foundMethod.getModifiers(),
foundMethod.getDeclaringClass(), compiler.classNode, type)) {
return dynamicOrError(exp, compiler, methodName, type, argTypes, "Cannot access method ");
}
return createCall(exp, compiler, args, null, foundMethod);
} else {
if (exp.getObjectExpression() instanceof VariableExpression &&
(((VariableExpression) exp.getObjectExpression()).isThisExpression() ||
((VariableExpression) exp.getObjectExpression()).isSuperExpression())) {
ClassNode thisType = compiler.methodNode.getDeclaringClass();
if (compiler.methodNode.isStatic() && !(foundMethod instanceof ClassNodeCache.DGM)) {
foundMethod = findMethodWithClosureCoercion(thisType, methodName, argTypes, compiler, true);
if (foundMethod != null) {
object = null;
if (!AccessibilityCheck.isAccessible(foundMethod.getModifiers(),
foundMethod.getDeclaringClass(), compiler.classNode, thisType)) {
return dynamicOrError(exp, compiler, methodName, thisType, argTypes, "Cannot access method ");
}
return createCall(exp, compiler, args, object, foundMethod);
}
else {
final Object prop = resolveCallableProperty(compiler, methodName, thisType, false);
if (prop != null) {
final MethodNode callMethod = resolveCallMethod(compiler, argTypes, prop);
if (callMethod != null) {
return createCallMethodCall(exp, compiler, methodName, args, createThisFetchingObject(exp, compiler, thisType), prop, callMethod, thisType);
}
}
foundMethod = findMethodWithClosureCoercion(ClassHelper.CLASS_Type, methodName, argTypes, compiler, false);
if (foundMethod != null) {
object = (BytecodeExpr) compiler.transform(new ClassExpression(compiler.classNode));
return createCall(exp, compiler, args, object, foundMethod);
}
}
} else {
if (thisType instanceof ClosureClassNode &&
thisType.isDerivedFrom(ClassHelper.CLOSURE_TYPE) &&
methodName.equals("call"))
// We have a closure recursive call,
// let's forget about 'call' and deal with 'doCall' instead.
methodName = "doCall";
boolean isSuper = ((VariableExpression) exp.getObjectExpression()).isSuperExpression();
while (thisType != null) {
ClassNode declaringType = isSuper ? thisType.getSuperClass() : thisType;
foundMethod = findMethodWithClosureCoercion(declaringType, methodName, argTypes, compiler, false);
if (foundMethod == null) {
// Groovy does not allow to call 'this.closure()' for closure fields: issue 143.
if (exp.isImplicitThis() || !(thisType instanceof ClosureClassNode) || isSuper) {
// Try some property with 'call' method.
final Object prop = resolveCallableProperty(compiler, methodName, declaringType, false);
if (prop != null) {
final MethodNode callMethod = resolveCallMethod(compiler, argTypes, prop);
if (callMethod != null) {
return createCallMethodCall(exp, compiler, methodName, args, createThisFetchingObject(exp, compiler, thisType), prop, callMethod, declaringType);
}
}
}
if (thisType.implementsInterface(TypeUtil.DELEGATING)) {
final MethodNode gd = compiler.findMethod(thisType, "getDelegate", ClassNode.EMPTY_ARRAY, false);
if (gd != null) {
final InnerThisBytecodeExpr innerThis = new InnerThisBytecodeExpr(exp, thisType, compiler);
final ResolvedMethodBytecodeExpr delegate = ResolvedMethodBytecodeExpr.create(exp, gd, innerThis, ArgumentListExpression.EMPTY_ARGUMENTS, compiler);
foundMethod = findMethodWithClosureCoercion(delegate.getType(), methodName, argTypes, compiler, false);
if (foundMethod != null) {
if (!AccessibilityCheck.isAccessible(foundMethod.getModifiers(),
foundMethod.getDeclaringClass(), compiler.classNode, delegate.getType())) {
return dynamicOrError(exp, compiler, methodName, delegate.getType(), argTypes, "Cannot access method ");
}
return createCall(exp, compiler, args, delegate, foundMethod);
}
}
}
}
if (foundMethod != null) {
// 'super' access is always permitted.
final ClassNode accessType = isSuper ? null : declaringType;
if (!AccessibilityCheck.isAccessible(foundMethod.getModifiers(),
foundMethod.getDeclaringClass(), compiler.classNode, accessType)) {
return dynamicOrError(exp, compiler, methodName, declaringType, argTypes, "Cannot access method ");
}
if (foundMethod.isStatic())
object = null;
else {
if (compiler.methodNode.isStatic() && !(foundMethod instanceof ClassNodeCache.DGM)) {
compiler.addError("Cannot reference an instance method from static context", exp);
return null;
}
if (isSuper) {
if (thisType != compiler.classNode) {
// super call to outer class' super method needs to be proxied.
foundMethod = compiler.context.getSuperMethodDelegate(foundMethod, thisType);
object = createThisFetchingObject(exp, compiler, thisType);
} else {
object = (BytecodeExpr) compiler.transform(exp.getObjectExpression());
}
} else {
if (thisType != compiler.classNode && thisType != foundMethod.getDeclaringClass() &&
!foundMethod.isPublic()) {
// super call to outer class' super method needs to be proxied.
foundMethod = compiler.context.getSuperMethodDelegate(foundMethod, thisType);
}
object = createThisFetchingObject(exp, compiler, thisType);
}
}
return createCall(exp, compiler, args, object, foundMethod);
}
compiler.context.setOuterClassInstanceUsed(thisType);
FieldNode ownerField = thisType.getField("this$0");
thisType = ownerField == null ? null : ownerField.getType();
}
}
return dynamicOrError(exp, compiler, methodName, compiler.classNode, argTypes, "Cannot find method ");
} else {
object = (BytecodeExpr) compiler.transformToGround(exp.getObjectExpression());
type = object.getType();
if (type instanceof ClosureClassNode && methodName.equals("call"))
// Since ClosureClassNode can't (at least currently) escape its scope we have a typed closure,
// so let's forget about 'call' and deal with 'doCall' instead.
methodName = "doCall";
if (foundMethod == null)
foundMethod = findMethodWithClosureCoercion(type, methodName, argTypes, compiler, false);
if (foundMethod == null) {
// Try some property with 'call' method.
Object prop = resolveCallableProperty(compiler, methodName, type, false);
if (prop != null) {
final MethodNode callMethod = resolveCallMethod(compiler, argTypes, prop);
if (callMethod != null) {
return createCallMethodCall(exp, compiler, methodName, args, object, prop, callMethod, type);
}
}
}
if (foundMethod == null) {
if (TypeUtil.isAssignableFrom(TypeUtil.TCLOSURE, object.getType())) {
foundMethod = findMethodWithClosureCoercion(ClassHelper.CLOSURE_TYPE, methodName, argTypes, compiler, false);
if (foundMethod != null) {
ClosureUtil.improveClosureType(object.getType(), ClassHelper.CLOSURE_TYPE);
return createCall(exp, compiler, args, object, foundMethod);
}
} else {
MethodNode unboxing = TypeUtil.getReferenceUnboxingMethod(type);
if (unboxing != null) {
ClassNode t = TypeUtil.getSubstitutedType(unboxing.getReturnType(), unboxing.getDeclaringClass(), type);
foundMethod = findMethodWithClosureCoercion(t, methodName, argTypes, compiler, false);
if (foundMethod != null) {
object = ResolvedMethodBytecodeExpr.create(exp, unboxing, object,
new ArgumentListExpression(), compiler);
return createCall(exp, compiler, args, object, foundMethod);
}
}
}
if (object instanceof ResolvedFieldBytecodeExpr) {
ResolvedFieldBytecodeExpr obj = (ResolvedFieldBytecodeExpr) object;
FieldNode fieldNode = obj.getFieldNode();
if ((fieldNode.getModifiers() & Opcodes.ACC_VOLATILE) != 0) {
FieldNode updater = fieldNode.getDeclaringClass().getDeclaredField(fieldNode.getName() + "$updater");
if (updater != null) {
ClassNode [] newArgs = new ClassNode [argTypes.length+1];
System.arraycopy(argTypes, 0, newArgs, 1, argTypes.length);
newArgs [0] = obj.getObject().getType();
MethodNode updaterMethod = findMethodWithClosureCoercion(updater.getType(), methodName, newArgs, compiler, false);
if (updaterMethod != null) {
ResolvedFieldBytecodeExpr updaterInstance = new ResolvedFieldBytecodeExpr(exp, updater, null, null, compiler);
((TupleExpression)args).getExpressions().add(0, obj.getObject());
return createCall(exp, compiler, args, updaterInstance, updaterMethod);
}
}
}
} else if (object instanceof ResolvedGetterBytecodeExpr) {
ResolvedGetterBytecodeExpr obj = (ResolvedGetterBytecodeExpr) object;
FieldNode fieldNode = obj.getFieldNode();
if (fieldNode != null && (fieldNode.getModifiers() & Opcodes.ACC_VOLATILE) != 0) {
FieldNode updater = fieldNode.getDeclaringClass().getDeclaredField(fieldNode.getName() + "$updater");
if (updater != null) {
ClassNode[] newArgs = new ClassNode [argTypes.length+1];
System.arraycopy(argTypes, 0, newArgs, 1, argTypes.length);
newArgs [0] = obj.getObject().getType();
MethodNode updaterMethod = compiler.findMethod(updater.getType(), methodName, newArgs, false);
if (updaterMethod != null) {
ResolvedFieldBytecodeExpr updaterInstance = new ResolvedFieldBytecodeExpr(exp, updater, null, null, compiler);
((TupleExpression)args).getExpressions().add(0, obj.getObject());
return createCall(exp, compiler, args, updaterInstance, updaterMethod);
}
}
}
}
return dynamicOrError(exp, compiler, methodName, type, argTypes, "Cannot find method ");
}
// 'super' access is always permitted.
final ClassNode accessType = object instanceof VariableExpressionTransformer.Super ? null : type;
if (!AccessibilityCheck.isAccessible(foundMethod.getModifiers(),
foundMethod.getDeclaringClass(), compiler.classNode, accessType)) {
return dynamicOrError(exp, compiler, methodName, type, argTypes, "Cannot access method ");
}
return createCall(exp, compiler, args, object, foundMethod);
}
}
}
finally {
exp.setArguments(originalArgs);
}
}
private Expression createCallMethodCall(MethodCallExpression exp, CompilerTransformer compiler, String methodName, Expression args, BytecodeExpr object, Object prop, MethodNode callMethod, ClassNode type) {
PropertyExpression propertyExpression = new PropertyExpression(
exp.getObjectExpression(), methodName);
object = PropertyUtil.createGetProperty(propertyExpression, compiler, methodName,
type, object, prop);
return createCall(exp, compiler, args, object, callMethod);
}
private MethodNode resolveCallMethod(CompilerTransformer compiler, ClassNode[] argTypes, Object prop) {
final ClassNode propType = PropertyUtil.getPropertyType(prop);
return findMethodWithClosureCoercion(propType, "call", argTypes, compiler, false);
}
private Object resolveCallableProperty(CompilerTransformer compiler, String methodName, ClassNode thisType,
boolean onlyStatic) {
return PropertyUtil.resolveGetProperty(thisType, methodName, compiler, onlyStatic, false);
}
private BytecodeExpr createThisFetchingObject(final MethodCallExpression exp, final CompilerTransformer compiler, final ClassNode thisTypeFinal) {
return new BytecodeExpr(exp.getObjectExpression(), thisTypeFinal) {
protected void compile(MethodVisitor mv) {
mv.visitVarInsn(ALOAD, 0);
ClassNode curThis = compiler.methodNode.getDeclaringClass();
while (curThis != thisTypeFinal) {
ClassNode next = curThis.getField("this$0").getType();
mv.visitFieldInsn(GETFIELD, BytecodeHelper.getClassInternalName(curThis), "this$0", BytecodeHelper.getTypeDescription(next));
curThis = next;
}
}
@Override
public boolean isThis() {
return thisTypeFinal.equals(compiler.classNode);
}
};
}
private Expression createCall(MethodCallExpression exp, CompilerTransformer compiler, Expression args, BytecodeExpr object, MethodNode foundMethod) {
if (foundMethod.getReturnType().equals(ClassHelper.VOID_TYPE)) {
final ResolvedMethodBytecodeExpr call = ResolvedMethodBytecodeExpr.create(exp, foundMethod, object, (TupleExpression) args, compiler);
return new BytecodeExpr(exp, TypeUtil.NULL_TYPE) {
protected void compile(MethodVisitor mv) {
call.visit(mv);
mv.visitInsn(ACONST_NULL);
}
};
}
else
return ResolvedMethodBytecodeExpr.create(exp, foundMethod, object, (TupleExpression) args, compiler);
}
private Expression transformSafe(MethodCallExpression exp, CompilerTransformer compiler) {
final BytecodeExpr object = (BytecodeExpr) compiler.transform(exp.getObjectExpression());
ClassNode type = TypeUtil.wrapSafely(object.getType());
MethodCallExpression callExpression = new MethodCallExpression(new BytecodeExpr(object, type) {
protected void compile(MethodVisitor mv) {
}
}, exp.getMethod(), exp.getArguments());
callExpression.setSourcePosition(exp);
final BytecodeExpr call = (BytecodeExpr) compiler.transform(callExpression);
if (ClassHelper.isPrimitiveType(call.getType())) {
return new BytecodeExpr(exp,call.getType()) {
protected void compile(MethodVisitor mv) {
Label nullLabel = new Label(), endLabel = new Label ();
object.visit(mv);
mv.visitInsn(DUP);
mv.visitJumpInsn(IFNULL, nullLabel);
call.visit(mv);
mv.visitJumpInsn(GOTO, endLabel);
mv.visitLabel(nullLabel);
mv.visitInsn(POP);
if (call.getType() == ClassHelper.long_TYPE) {
mv.visitInsn(LCONST_0);
} else
if (call.getType() == ClassHelper.float_TYPE) {
mv.visitInsn(FCONST_0);
} else
if (call.getType() == ClassHelper.double_TYPE) {
mv.visitInsn(DCONST_0);
} else
mv.visitInsn(ICONST_0);
mv.visitLabel(endLabel);
}
};
}
else {
return new BytecodeExpr(exp, call.getType()) {
protected void compile(MethodVisitor mv) {
object.visit(mv);
Label nullLabel = new Label();
mv.visitInsn(DUP);
mv.visitJumpInsn(IFNULL, nullLabel);
call.visit(mv);
box(call.getType(), mv);
mv.visitLabel(nullLabel);
checkCast(getType(), mv);
}
};
}
}
private Expression dynamicOrError(MethodCallExpression exp, CompilerTransformer compiler, String methodName,
ClassNode type, ClassNode[] argTypes, final String msg) {
if (compiler.policy == TypePolicy.STATIC) {
final Expression anchor = exp.getMethod().getLineNumber() >= 0 ? exp.getMethod() : exp;
compiler.addError(msg + getMethodDescr(type, methodName, argTypes), anchor);
return null;
} else
return createDynamicCall(exp, compiler);
}
private static String getMethodDescr(ClassNode type, String methodName, ClassNode[] argTypes) {
StringBuilder sb = new StringBuilder();
sb.append(PresentationUtil.getText(type));
sb.append(".");
sb.append(methodName);
sb.append("(");
for (int i = 0; i != argTypes.length; i++) {
if (i != 0)
sb.append(", ");
if (argTypes[i] != null)
sb.append(PresentationUtil.getText(argTypes[i]));
else
sb.append("null");
}
sb.append(")");
return sb.toString();
}
private Expression createDynamicCall(final MethodCallExpression exp, CompilerTransformer compiler) {
final List<Expression> args = ((TupleExpression) exp.getArguments()).getExpressions();
for (int i = 0; i != args.size(); ++i) {
BytecodeExpr arg = compiler.transformSynthetic((BytecodeExpr) args.get(i));
if (arg instanceof CompiledClosureBytecodeExpr) {
compiler.processPendingClosure((CompiledClosureBytecodeExpr) arg);
}
args.set(i, arg);
}
final BytecodeExpr methodExpr = (BytecodeExpr) compiler.transform(exp.getMethod());
final BytecodeExpr object = (BytecodeExpr) compiler.transform(exp.getObjectExpression());
return new BytecodeExpr(exp, ClassHelper.OBJECT_TYPE) {
protected void compile(MethodVisitor mv) {
object.visit(mv);
box(object.getType(), mv);
methodExpr.visit(mv);
mv.visitLdcInsn(args.size());
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
for (int j = 0; j != args.size(); ++j) {
mv.visitInsn(DUP);
mv.visitLdcInsn(j);
BytecodeExpr arg = (BytecodeExpr) args.get(j);
arg.visit(mv);
box(arg.getType(), mv);
mv.visitInsn(AASTORE);
}
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/codehaus/groovy/runtime/InvokerHelper", "invokeMethod", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;");
}
};
}
private static class Changed {
int index;
ClassNode original;
List<MethodNode> oneMethodAbstract;
}
private MethodNode findMethodVariatingArgs(ClassNode type, String methodName, ClassNode[] argTypes, CompilerTransformer compiler, boolean staticOnly) {
MethodNode foundMethod;
List<Changed> changed = null;
ClassNode[] argTypesCopy = new ClassNode[argTypes.length];
System.arraycopy(argTypes, 0, argTypesCopy, 0, argTypes.length);
for (int i = 0; i < argTypesCopy.length+1; ++i) {
foundMethod = compiler.findMethod(type, methodName, argTypesCopy, staticOnly);
if (foundMethod != null) {
// infered lists and maps does not participate in generics calculations
for(int j = 0; j != argTypesCopy.length; ++j) {
if (argTypesCopy[j] != null && (TypeUtil.TLIST_NULL.equals(argTypesCopy[j]) || TypeUtil.TMAP_NULL.equals(argTypesCopy[j])))
argTypesCopy[j] = null;
}
return foundMethodInference(type, foundMethod, changed, argTypesCopy, compiler);
}
if (i == argTypesCopy.length)
return null;
final ClassNode oarg = argTypesCopy[i];
if (oarg == null)
continue;
if (oarg.implementsInterface(TypeUtil.TCLOSURE) ||
oarg.implementsInterface(TypeUtil.TLIST) ||
oarg.implementsInterface(TypeUtil.TMAP) ||
oarg.implementsInterface(TypeUtil.TTERNARY) ||
oarg.implementsInterface(TypeUtil.TTHIS)) {
if (changed == null)
changed = new ArrayList<Changed> ();
Changed change = new Changed();
change.index = i;
change.original = argTypesCopy[i];
changed.add(change);
argTypesCopy[i] = oarg.implementsInterface(TypeUtil.TCLOSURE) ?
TypeUtil.withGenericTypes(TypeUtil.TCLOSURE_NULL,change.original)
: oarg.implementsInterface(TypeUtil.TMAP) ?
TypeUtil.withGenericTypes(TypeUtil.TMAP_NULL,change.original)
: oarg.implementsInterface(TypeUtil.TLIST) ?
TypeUtil.withGenericTypes(TypeUtil.TLIST_NULL,change.original)
: null;
}
}
return null;
}
private MethodNode foundMethodInference(ClassNode type, MethodNode foundMethod, List<Changed> changed, ClassNode [] argTypes, CompilerTransformer compiler) {
if (changed == null)
return foundMethod;
Parameter parameters[] = foundMethod.getParameters();
ClassNode[] paramTypes = new ClassNode[argTypes.length];
for (int i = 0; i < parameters.length - 1; i++) {
paramTypes[i] = parameters[i].getType();
}
ClassNode lastType = parameters[parameters.length - 1].getType();
if (parameters.length == argTypes.length) {
paramTypes[paramTypes.length -1] = lastType;
} else {
if (!lastType.isArray()) return null;
for (int i = parameters.length -1 ; i < paramTypes.length; i++) {
paramTypes[i] = lastType.getComponentType();
}
}
boolean hasGenerics = TypeUtil.hasGenericsTypes(foundMethod);
GenericsType[] typeVars = foundMethod.getGenericsTypes();
if (typeVars == null) typeVars = new GenericsType[0];
Map<String, Integer> indices = new HashMap<String, Integer>();
for (int i = 0; i < typeVars.length; ++i) {
GenericsType typeVar = typeVars[i];
indices.put(typeVar.getType().getUnresolvedName(), i);
}
Map<Changed, boolean[]> inTypeVars = new HashMap<Changed, boolean[]>();
for (Iterator<Changed> it = changed.iterator(); it.hasNext(); ) {
Changed change = it.next();
ClassNode paramType = paramTypes[change.index];
if (!change.original.implementsInterface(TypeUtil.TCLOSURE)) {
it.remove();
// nothing special needs to be done for list & maps
}
else {
if (paramType.equals(ClassHelper.CLOSURE_TYPE)) {
ClosureUtil.improveClosureType(change.original, ClassHelper.CLOSURE_TYPE);
StaticMethodBytecode.replaceMethodCode(compiler.su, compiler.context, ((ClosureClassNode)change.original).getDoCallMethod(), compiler.compileStack, compiler.debug == -1 ? -1 : compiler.debug+1, compiler.policy, change.original.getName());
argTypes [change.index] = change.original;
it.remove();
}
else {
List<MethodNode> one = ClosureUtil.isOneMethodAbstract(paramType);
if (one == null) {
return null;
}
change.oneMethodAbstract = one;
if (!hasGenerics) {
it.remove();
MethodNode doCall = ClosureUtil.isMatch(one, (ClosureClassNode) change.original, paramType, compiler);
if (doCall == null) {
return null;
}
} else {
boolean[] used = new boolean[typeVars.length];
extractUsedVariables(one.get(0), indices, used, paramType);
inTypeVars.put(change, used);
}
}
}
}
if (changed.size() == 0) {
return foundMethod;
}
if (changed.size() == 1) {
ClassNode[] bindings = obtainInitialBindings(type, foundMethod, argTypes, paramTypes, typeVars);
return inferTypesForClosure(type, foundMethod, compiler, paramTypes, changed.get(0), bindings, typeVars) ? foundMethod : null;
}
ClassNode[] bindings = obtainInitialBindings(type, foundMethod, argTypes, paramTypes, typeVars);
Next:
while (true) {
if (changed.isEmpty()) return foundMethod;
for (Iterator<Changed> it = changed.iterator(); it.hasNext();) {
Changed change = it.next();
if (isBound(bindings, inTypeVars.get(change))) {
if (!inferTypesForClosure(type, foundMethod, compiler, paramTypes, change, bindings, typeVars)) return null;
it.remove();
continue Next;
}
}
return null;
}
}
private boolean isBound(ClassNode[] bindings, boolean[] used) {
for (int i = 0; i < used.length; i++) {
if (used[i] && bindings[i] == null) return false;
}
return true;
}
private void extractUsedVariables(MethodNode methodNode, Map<String, Integer> indices, boolean[] used, ClassNode type) {
for (Parameter parameter : methodNode.getParameters()) {
ClassNode t = parameter.getType();
t = TypeUtil.getSubstitutedType(t, methodNode.getDeclaringClass(), type);
extractUsedVariables(t, indices, used);
}
}
private void extractUsedVariables(ClassNode type, Map<String, Integer> indices, boolean[] used) {
if (type.isGenericsPlaceHolder()) {
Integer idx = indices.get(type.getUnresolvedName());
if (idx != null) {
used[idx] = true;
}
} else {
GenericsType[] generics = type.getGenericsTypes();
if (generics != null) {
for (GenericsType generic : generics) {
extractUsedVariables(generic.getType(), indices, used);
}
}
}
}
private boolean inferTypesForClosure(ClassNode type, MethodNode foundMethod,
CompilerTransformer compiler, ClassNode[] paramTypes,
Changed info, ClassNode[] bindings, GenericsType[] typeVars) {
ClassNode origParamType = paramTypes[info.index];
ClassNode paramType = TypeUtil.getSubstitutedType(origParamType, foundMethod, bindings);
if (type != null) {
Set<String> ignoreTypeVariables = new HashSet<String>();
GenericsType[] methodVars = foundMethod.getGenericsTypes();
if (methodVars != null) {
for (GenericsType methodVar : methodVars) {
ignoreTypeVariables.add(methodVar.getType().getUnresolvedName());
}
}
paramType = TypeUtil.getSubstitutedType(paramType, foundMethod.getDeclaringClass(), type, ignoreTypeVariables);
}
List<MethodNode> one = info.oneMethodAbstract;
MethodNode doCall = ClosureUtil.isMatch(one, (ClosureClassNode) info.original, paramType, compiler);
if (doCall == null) {
return false;
} else {
ClassNode formal = one.get(0).getReturnType();
formal = TypeUtil.getSubstitutedType(formal, one.get(0).getDeclaringClass(), origParamType);
ClassNode instantiated = doCall.getReturnType();
ClassNode[] addition = TypeUnification.inferTypeArguments(typeVars, new ClassNode[]{formal},
new ClassNode[]{instantiated});
for (int i = 0; i < bindings.length; i++) {
if (bindings[i] == null) bindings[i] = addition[i];
}
return true;
}
}
private ClassNode[] obtainInitialBindings(ClassNode type, MethodNode foundMethod, ClassNode[] argTypes, ClassNode[] paramTypes, GenericsType[] methodTypeVars) {
ArrayList<ClassNode> formals = new ArrayList<ClassNode> (2);
ArrayList<ClassNode> instantiateds = new ArrayList<ClassNode> (2);
if (!foundMethod.isStatic()) {
formals.add(foundMethod.getDeclaringClass());
instantiateds.add(type);
}
for (int j = 0; j != argTypes.length; j++) {
formals.add(paramTypes[j]);
instantiateds.add(argTypes[j]);
}
return TypeUnification.inferTypeArguments(methodTypeVars, formals.toArray(new ClassNode[formals.size()]),
instantiateds.toArray(new ClassNode[instantiateds.size()]));
}
private MethodNode findMethodWithClosureCoercion(ClassNode type, String methodName, ClassNode[] argTypes, CompilerTransformer compiler, boolean staticOnly) {
return findMethodVariatingArgs(type, methodName, argTypes, compiler, staticOnly);
}
}