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
fun constructFakeFunctionCall(project: Project, referencedFunction: FunctionDescriptor): KtCallExpression {
val fakeFunctionCall = StringBuilder("callableReferenceFakeCall(")
fakeFunctionCall.append(referencedFunction.valueParameters.joinToString(", ") { "p${it.index}" })
fakeFunctionCall.append(")")
return KtPsiFactory(project, markGenerated = false).createExpression(fakeFunctionCall.toString()) as KtCallExpression
fun constructFakeFunctionCall(project: Project, arity: Int): KtCallExpression {
val fakeFunctionCall =
(1..arity).joinToString(prefix = "callableReferenceFakeCall(", separator = ", ", postfix = ")") { "p$it" }
return KtPsiFactory(project, markGenerated = false).createExpression(fakeFunctionCall) as KtCallExpression
}
}
@@ -24,10 +24,7 @@ import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.calls.model.DelegatingResolvedCall;
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.model.*;
import org.jetbrains.kotlin.resolve.calls.util.CallMaker;
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature;
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver;
@@ -40,8 +37,24 @@ import java.util.LinkedHashMap;
import java.util.List;
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 {
private final ResolvedCall<?> resolvedCall;
private final FunctionDescriptor referencedFunction;
@@ -80,20 +93,21 @@ public class FunctionReferenceGenerationStrategy extends FunctionGenerationStrat
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();
ReceiverValue dispatchReceiver = computeAndSaveReceiver(signature, codegen, referencedFunction.getDispatchReceiverParameter());
ReceiverValue extensionReceiver = computeAndSaveReceiver(signature, codegen, referencedFunction.getExtensionReceiverParameter());
computeAndSaveArguments(fakeArguments, codegen);
computeAndSaveArguments(fakeArguments, codegen, receivers);
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;
List<ValueParameterDescriptor> parameters = functionDescriptor.getValueParameters();
List<ValueParameterDescriptor> parameters = referencedFunction.getValueParameters();
for (ValueArgument argument : fakeArguments) {
argumentMap.put(parameters.get(index), new ExpressionValueArgument(argument));
index++;
@@ -146,20 +160,14 @@ public class FunctionReferenceGenerationStrategy extends FunctionGenerationStrat
v.areturn(returnType);
}
private void computeAndSaveArguments(@NotNull List<? extends ValueArgument> fakeArguments, @NotNull ExpressionCodegen codegen) {
int receivers = (referencedFunction.getDispatchReceiverParameter() != null ? 1 : 0) +
(referencedFunction.getExtensionReceiverParameter() != null ? 1 : 0) -
(receiverType != null ? 1 : 0);
if (receivers < 0 && referencedFunction instanceof ConstructorDescriptor && isObject(referencedFunction.getContainingDeclaration().getContainingDeclaration())) {
//reference to object nested class
//TODO: seems problem should be fixed on frontend side (note that object instance are captured by generated class)
receivers = 0;
}
List<ValueParameterDescriptor> parameters = CollectionsKt.drop(functionDescriptor.getValueParameters(), receivers);
for (int i = 0; i < parameters.size(); i++) {
ValueParameterDescriptor parameter = parameters.get(i);
private void computeAndSaveArguments(
@NotNull List<? extends ValueArgument> fakeArguments, @NotNull ExpressionCodegen codegen, int receivers
) {
List<ValueParameterDescriptor> valueParameters = CollectionsKt.drop(functionDescriptor.getValueParameters(), receivers);
assert valueParameters.size() == fakeArguments.size()
: functionDescriptor + ": " + valueParameters.size() + " != " + fakeArguments.size();
for (int i = 0; i < valueParameters.size(); i++) {
ValueParameterDescriptor parameter = valueParameters.get(i);
ValueArgument fakeArgument = fakeArguments.get(i);
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.descriptors.*
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.MutableClassDescriptor
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.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils
class JvmRuntimeTypes(module: ModuleDescriptor) {
private val kotlinJvmInternalPackage = MutablePackageFragmentDescriptor(module, FqName("kotlin.jvm.internal"))
@@ -77,7 +77,7 @@ class JvmRuntimeTypes(module: ModuleDescriptor) {
descriptor.builtIns,
Annotations.EMPTY,
actualFunctionDescriptor.extensionReceiverParameter?.type,
ExpressionTypingUtils.getValueParametersTypes(actualFunctionDescriptor.valueParameters),
actualFunctionDescriptor.valueParameters.map { it.type },
null,
actualFunctionDescriptor.returnType!!
)
@@ -94,14 +94,20 @@ class JvmRuntimeTypes(module: ModuleDescriptor) {
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(
descriptor.builtIns,
referencedFunction.builtIns,
Annotations.EMPTY,
if (isBound) null else descriptor.extensionReceiverParameter?.type ?: descriptor.dispatchReceiverParameter?.type,
ExpressionTypingUtils.getValueParametersTypes(descriptor.valueParameters),
if (isBound) null else referencedFunction.extensionReceiverParameter?.type ?: referencedFunction.dispatchReceiverParameter?.type,
anonymousFunctionDescriptor.valueParameters.drop(receivers).map { it.type },
null,
descriptor.returnType!!
referencedFunction.returnType!!
)
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.descriptors.*;
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.fileClasses.FileClasses;
import org.jetbrains.kotlin.fileClasses.JvmFileClassesProvider;
@@ -341,7 +342,9 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
callableDescriptor = bindingContext.get(FUNCTION, expression);
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) {
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.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import org.jetbrains.org.objectweb.asm.Type
@@ -75,4 +78,19 @@ fun InstructionAdapter.generateClosureFieldsInitializationFromParameters(closure
else ->
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.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.resolve.BindingContext.PROCESSED;
@@ -185,15 +182,6 @@ public class ExpressionTypingUtils {
&& ((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:
*
@@ -82,7 +82,7 @@ object CallableReferenceTranslator {
receiver: JsExpression?
): JsExpression {
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 fakeResolvedCall = object : DelegatingResolvedCall<FunctionDescriptor>(realResolvedCall) {