Refactor FunctionReferenceGenerationStrategy

Draw a clear distinction between the referenced function's parameters
and the anonymous synthetic function's parameters (see the comment).
This will be useful in supporting advanced callable reference features
like KT-8834
This commit is contained in:
Alexander Udalov
2017-08-23 13:31:04 +03:00
parent 3d8b15d9db
commit c420e2bfa5
7 changed files with 74 additions and 52 deletions
@@ -156,10 +156,9 @@ object CodegenUtil {
} }
@JvmStatic @JvmStatic
fun constructFakeFunctionCall(project: Project, referencedFunction: FunctionDescriptor): KtCallExpression { fun constructFakeFunctionCall(project: Project, arity: Int): KtCallExpression {
val fakeFunctionCall = StringBuilder("callableReferenceFakeCall(") val fakeFunctionCall =
fakeFunctionCall.append(referencedFunction.valueParameters.joinToString(", ") { "p${it.index}" }) (1..arity).joinToString(prefix = "callableReferenceFakeCall(", separator = ", ", postfix = ")") { "p$it" }
fakeFunctionCall.append(")") return KtPsiFactory(project, markGenerated = false).createExpression(fakeFunctionCall) as KtCallExpression
return KtPsiFactory(project, markGenerated = false).createExpression(fakeFunctionCall.toString()) as KtCallExpression
} }
} }
@@ -24,10 +24,7 @@ import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.calls.model.DelegatingResolvedCall; import org.jetbrains.kotlin.resolve.calls.model.*;
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument;
import org.jetbrains.kotlin.resolve.calls.util.CallMaker; import org.jetbrains.kotlin.resolve.calls.util.CallMaker;
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature; import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature;
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver; import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver;
@@ -40,8 +37,24 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isObject; /*
* Notice the difference between two function descriptors in this class.
* - [referencedFunction] is the function declaration which is referenced by the "::" expression. This is a real function present in code.
* - [functionDescriptor] is a synthetically created function which has the same signature as the "invoke" of the generated callable
* reference subclass. Its parameters include dispatch/extension receiver parameters of the referenced function, and those value
* parameters of the referenced function which are required by the expected function type where the callable reference is passed to.
* In simple cases, these value parameters are all of the referenced function's value parameters. But in cases when the referenced
* function has parameters with default values, or a vararg parameter, functionDescriptor can take fewer parameters than
* referencedFunction if the expected function type takes fewer parameters as well. For example:
*
* fun foo(a: A, b: B = ..., c: C = ..., vararg d: D) {}
*
* fun bar(f: (A, B) -> Unit) {}
*
* // referencedFunction: foo(A, B, C, vararg D)
* // functionDescriptor: invoke(A, B)
* bar(::foo)
*/
public class FunctionReferenceGenerationStrategy extends FunctionGenerationStrategy.CodegenBased { public class FunctionReferenceGenerationStrategy extends FunctionGenerationStrategy.CodegenBased {
private final ResolvedCall<?> resolvedCall; private final ResolvedCall<?> resolvedCall;
private final FunctionDescriptor referencedFunction; private final FunctionDescriptor referencedFunction;
@@ -80,20 +93,21 @@ public class FunctionReferenceGenerationStrategy extends FunctionGenerationStrat
every argument boils down to calling LOAD with the corresponding index every argument boils down to calling LOAD with the corresponding index
*/ */
KtCallExpression fakeExpression = CodegenUtil.constructFakeFunctionCall(state.getProject(), referencedFunction); int receivers = CallableReferenceUtilKt.computeExpectedNumberOfReceivers(referencedFunction, receiverType != null);
KtCallExpression fakeExpression =
CodegenUtil.constructFakeFunctionCall(state.getProject(), functionDescriptor.getValueParameters().size() - receivers);
List<? extends ValueArgument> fakeArguments = fakeExpression.getValueArguments(); List<? extends ValueArgument> fakeArguments = fakeExpression.getValueArguments();
ReceiverValue dispatchReceiver = computeAndSaveReceiver(signature, codegen, referencedFunction.getDispatchReceiverParameter()); ReceiverValue dispatchReceiver = computeAndSaveReceiver(signature, codegen, referencedFunction.getDispatchReceiverParameter());
ReceiverValue extensionReceiver = computeAndSaveReceiver(signature, codegen, referencedFunction.getExtensionReceiverParameter()); ReceiverValue extensionReceiver = computeAndSaveReceiver(signature, codegen, referencedFunction.getExtensionReceiverParameter());
computeAndSaveArguments(fakeArguments, codegen); computeAndSaveArguments(fakeArguments, codegen, receivers);
ResolvedCall<CallableDescriptor> fakeResolvedCall = new DelegatingResolvedCall<CallableDescriptor>(resolvedCall) { ResolvedCall<CallableDescriptor> fakeResolvedCall = new DelegatingResolvedCall<CallableDescriptor>(resolvedCall) {
private final Map<ValueParameterDescriptor, ResolvedValueArgument> argumentMap; private final Map<ValueParameterDescriptor, ResolvedValueArgument> argumentMap = new LinkedHashMap<>();
{ {
argumentMap = new LinkedHashMap<>(fakeArguments.size());
int index = 0; int index = 0;
List<ValueParameterDescriptor> parameters = functionDescriptor.getValueParameters(); List<ValueParameterDescriptor> parameters = referencedFunction.getValueParameters();
for (ValueArgument argument : fakeArguments) { for (ValueArgument argument : fakeArguments) {
argumentMap.put(parameters.get(index), new ExpressionValueArgument(argument)); argumentMap.put(parameters.get(index), new ExpressionValueArgument(argument));
index++; index++;
@@ -146,20 +160,14 @@ public class FunctionReferenceGenerationStrategy extends FunctionGenerationStrat
v.areturn(returnType); v.areturn(returnType);
} }
private void computeAndSaveArguments(@NotNull List<? extends ValueArgument> fakeArguments, @NotNull ExpressionCodegen codegen) { private void computeAndSaveArguments(
int receivers = (referencedFunction.getDispatchReceiverParameter() != null ? 1 : 0) + @NotNull List<? extends ValueArgument> fakeArguments, @NotNull ExpressionCodegen codegen, int receivers
(referencedFunction.getExtensionReceiverParameter() != null ? 1 : 0) - ) {
(receiverType != null ? 1 : 0); List<ValueParameterDescriptor> valueParameters = CollectionsKt.drop(functionDescriptor.getValueParameters(), receivers);
assert valueParameters.size() == fakeArguments.size()
if (receivers < 0 && referencedFunction instanceof ConstructorDescriptor && isObject(referencedFunction.getContainingDeclaration().getContainingDeclaration())) { : functionDescriptor + ": " + valueParameters.size() + " != " + fakeArguments.size();
//reference to object nested class for (int i = 0; i < valueParameters.size(); i++) {
//TODO: seems problem should be fixed on frontend side (note that object instance are captured by generated class) ValueParameterDescriptor parameter = valueParameters.get(i);
receivers = 0;
}
List<ValueParameterDescriptor> parameters = CollectionsKt.drop(functionDescriptor.getValueParameters(), receivers);
for (int i = 0; i < parameters.size(); i++) {
ValueParameterDescriptor parameter = parameters.get(i);
ValueArgument fakeArgument = fakeArguments.get(i); ValueArgument fakeArgument = fakeArguments.get(i);
Type type = state.getTypeMapper().mapType(parameter); Type type = state.getTypeMapper().mapType(parameter);
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.codegen.coroutines.getOrCreateJvmSuspendFunctionView
import org.jetbrains.kotlin.coroutines.isSuspendLambda import org.jetbrains.kotlin.coroutines.isSuspendLambda
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.descriptors.impl.MutableClassDescriptor import org.jetbrains.kotlin.descriptors.impl.MutableClassDescriptor
import org.jetbrains.kotlin.descriptors.impl.MutablePackageFragmentDescriptor import org.jetbrains.kotlin.descriptors.impl.MutablePackageFragmentDescriptor
@@ -29,7 +30,6 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils
class JvmRuntimeTypes(module: ModuleDescriptor) { class JvmRuntimeTypes(module: ModuleDescriptor) {
private val kotlinJvmInternalPackage = MutablePackageFragmentDescriptor(module, FqName("kotlin.jvm.internal")) private val kotlinJvmInternalPackage = MutablePackageFragmentDescriptor(module, FqName("kotlin.jvm.internal"))
@@ -77,7 +77,7 @@ class JvmRuntimeTypes(module: ModuleDescriptor) {
descriptor.builtIns, descriptor.builtIns,
Annotations.EMPTY, Annotations.EMPTY,
actualFunctionDescriptor.extensionReceiverParameter?.type, actualFunctionDescriptor.extensionReceiverParameter?.type,
ExpressionTypingUtils.getValueParametersTypes(actualFunctionDescriptor.valueParameters), actualFunctionDescriptor.valueParameters.map { it.type },
null, null,
actualFunctionDescriptor.returnType!! actualFunctionDescriptor.returnType!!
) )
@@ -94,14 +94,20 @@ class JvmRuntimeTypes(module: ModuleDescriptor) {
return listOf(lambda.defaultType, functionType) return listOf(lambda.defaultType, functionType)
} }
fun getSupertypesForFunctionReference(descriptor: FunctionDescriptor, isBound: Boolean): Collection<KotlinType> { fun getSupertypesForFunctionReference(
referencedFunction: FunctionDescriptor,
anonymousFunctionDescriptor: AnonymousFunctionDescriptor,
isBound: Boolean
): Collection<KotlinType> {
val receivers = computeExpectedNumberOfReceivers(referencedFunction, isBound)
val functionType = createFunctionType( val functionType = createFunctionType(
descriptor.builtIns, referencedFunction.builtIns,
Annotations.EMPTY, Annotations.EMPTY,
if (isBound) null else descriptor.extensionReceiverParameter?.type ?: descriptor.dispatchReceiverParameter?.type, if (isBound) null else referencedFunction.extensionReceiverParameter?.type ?: referencedFunction.dispatchReceiverParameter?.type,
ExpressionTypingUtils.getValueParametersTypes(descriptor.valueParameters), anonymousFunctionDescriptor.valueParameters.drop(receivers).map { it.type },
null, null,
descriptor.returnType!! referencedFunction.returnType!!
) )
return listOf(functionReference.defaultType, functionType) return listOf(functionReference.defaultType, functionType)
@@ -35,6 +35,7 @@ import org.jetbrains.kotlin.codegen.when.WhenByEnumsMapping;
import org.jetbrains.kotlin.coroutines.CoroutineUtilKt; import org.jetbrains.kotlin.coroutines.CoroutineUtilKt;
import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.Annotations; import org.jetbrains.kotlin.descriptors.annotations.Annotations;
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor;
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor; import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor;
import org.jetbrains.kotlin.fileClasses.FileClasses; import org.jetbrains.kotlin.fileClasses.FileClasses;
import org.jetbrains.kotlin.fileClasses.JvmFileClassesProvider; import org.jetbrains.kotlin.fileClasses.JvmFileClassesProvider;
@@ -341,7 +342,9 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
callableDescriptor = bindingContext.get(FUNCTION, expression); callableDescriptor = bindingContext.get(FUNCTION, expression);
if (callableDescriptor == null) return; if (callableDescriptor == null) return;
supertypes = runtimeTypes.getSupertypesForFunctionReference((FunctionDescriptor) target, receiverType != null); supertypes = runtimeTypes.getSupertypesForFunctionReference(
(FunctionDescriptor) target, (AnonymousFunctionDescriptor) callableDescriptor, receiverType != null
);
} }
else if (target instanceof PropertyDescriptor) { else if (target instanceof PropertyDescriptor) {
callableDescriptor = bindingContext.get(VARIABLE, expression); callableDescriptor = bindingContext.get(VARIABLE, expression);
@@ -18,7 +18,10 @@ package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.codegen.binding.CalculatedClosure import org.jetbrains.kotlin.codegen.binding.CalculatedClosure
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.Type
@@ -75,4 +78,19 @@ fun InstructionAdapter.generateClosureFieldsInitializationFromParameters(closure
else -> else ->
null null
} }
} }
fun computeExpectedNumberOfReceivers(referencedFunction: FunctionDescriptor, isBound: Boolean): Int {
val receivers = (if (referencedFunction.dispatchReceiverParameter != null) 1 else 0) +
(if (referencedFunction.extensionReceiverParameter != null) 1 else 0) -
(if (isBound) 1 else 0)
if (receivers < 0 && referencedFunction is ConstructorDescriptor &&
DescriptorUtils.isObject(referencedFunction.containingDeclaration.containingDeclaration)) {
//reference to object nested class
//TODO: seems problem should be fixed on frontend side (note that object instance are captured by generated class)
return 0
}
return receivers
}
@@ -44,9 +44,6 @@ import org.jetbrains.kotlin.resolve.scopes.utils.ScopeUtilsKt;
import org.jetbrains.kotlin.types.KotlinType; import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.expressions.typeInfoFactory.TypeInfoFactoryKt; import org.jetbrains.kotlin.types.expressions.typeInfoFactory.TypeInfoFactoryKt;
import java.util.ArrayList;
import java.util.List;
import static org.jetbrains.kotlin.diagnostics.Errors.TYPE_INFERENCE_ERRORS; import static org.jetbrains.kotlin.diagnostics.Errors.TYPE_INFERENCE_ERRORS;
import static org.jetbrains.kotlin.resolve.BindingContext.PROCESSED; import static org.jetbrains.kotlin.resolve.BindingContext.PROCESSED;
@@ -185,15 +182,6 @@ public class ExpressionTypingUtils {
&& ((KtUnaryExpression) expression).getOperationReference().getReferencedNameElementType() == KtTokens.EXCLEXCL; && ((KtUnaryExpression) expression).getOperationReference().getReferencedNameElementType() == KtTokens.EXCLEXCL;
} }
@NotNull
public static List<KotlinType> getValueParametersTypes(@NotNull List<ValueParameterDescriptor> valueParameters) {
List<KotlinType> parameterTypes = new ArrayList<>(valueParameters.size());
for (ValueParameterDescriptor parameter : valueParameters) {
parameterTypes.add(parameter.getType());
}
return parameterTypes;
}
/** /**
* The primary case for local extensions is the following: * The primary case for local extensions is the following:
* *
@@ -82,7 +82,7 @@ object CallableReferenceTranslator {
receiver: JsExpression? receiver: JsExpression?
): JsExpression { ): JsExpression {
val realResolvedCall = expression.callableReference.getFunctionResolvedCallWithAssert(context.bindingContext()) val realResolvedCall = expression.callableReference.getFunctionResolvedCallWithAssert(context.bindingContext())
val fakeExpression = CodegenUtil.constructFakeFunctionCall(expression.project, descriptor) val fakeExpression = CodegenUtil.constructFakeFunctionCall(expression.project, descriptor.valueParameters.size)
val fakeCall = CallMaker.makeCall(fakeExpression, null, null, fakeExpression, fakeExpression.valueArguments) val fakeCall = CallMaker.makeCall(fakeExpression, null, null, fakeExpression, fakeExpression.valueArguments)
val fakeResolvedCall = object : DelegatingResolvedCall<FunctionDescriptor>(realResolvedCall) { val fakeResolvedCall = object : DelegatingResolvedCall<FunctionDescriptor>(realResolvedCall) {