Implement kotlin.jvm.overloads annotation for generating all overloads of a method that has default parameter values.

#KT-2095 Fixed
This commit is contained in:
Dmitry Jemerov
2015-03-27 18:48:03 +01:00
parent c1c9d3b090
commit e15b984232
14 changed files with 321 additions and 46 deletions
@@ -136,15 +136,14 @@ public class CallableMethod implements Callable {
}
Method method = getAsmMethod();
String desc = JetTypeMapper.getDefaultDescriptor(method, receiverParameterType != null);
String desc = JetTypeMapper.getDefaultDescriptor(method,
getInvokeOpcode() == INVOKESTATIC ? null : defaultImplParam.getDescriptor(),
receiverParameterType != null);
if ("<init>".equals(method.getName())) {
v.aconst(null);
v.visitMethodInsn(INVOKESPECIAL, defaultImplOwner.getInternalName(), "<init>", desc, false);
}
else {
if (getInvokeOpcode() != INVOKESTATIC) {
desc = desc.replace("(", "(" + defaultImplParam.getDescriptor());
}
v.visitMethodInsn(INVOKESTATIC, defaultImplOwner.getInternalName(),
method.getName() + JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX, desc, false);
}
@@ -17,48 +17,144 @@
package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.context.CodegenContext
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.state.JetTypeMapper
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.JetClass
import org.jetbrains.kotlin.psi.JetClassOrObject
import org.jetbrains.kotlin.psi.JetElement
import org.jetbrains.kotlin.psi.JetNamedFunction
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
/**
* Generates Java overloads for functions and constructors that have the default
* parameter values substituted.
*/
public class DefaultParameterValueSubstitutor(val state: GenerationState) {
fun generateDefaultConstructorIfNeeded(method: CallableMethod,
constructorDescriptor: ConstructorDescriptor,
/**
* If all of the parameters of the specified constructor declare default values,
* generates a no-argument constructor that passes default values for all arguments.
*/
fun generateDefaultConstructorIfNeeded(constructorDescriptor: ConstructorDescriptor,
classBuilder: ClassBuilder,
context: CodegenContext<*>,
classOrObject: JetClassOrObject) {
if (!isEmptyConstructorNeeded(constructorDescriptor, classOrObject)) {
return
}
val flags = AsmUtil.getVisibilityAccessFlag(constructorDescriptor)
val mv = classBuilder.newMethod(OtherOrigin(constructorDescriptor), flags, "<init>", "()V", null,
FunctionCodegen.getThrownExceptions(constructorDescriptor, state.getTypeMapper()))
generateOverloadWithSubstitutedParameters(constructorDescriptor, constructorDescriptor, classBuilder, classOrObject,
context,
constructorDescriptor.countDefaultParameters())
}
/**
* If the function is annotated with [kotlin.jvm.overloads], generates Java methods that
* have the default parameter values substituted. If a method has N parameters and M of which
* have default values, M overloads are generated: the first one takes N-1 parameters (all but
* the last one that takes a default value), the second takes N-2 parameters, and so on.
*
* @param functionDescriptor the method for which the overloads are generated
* @param delegateFunctionDescriptor the method descriptor for the implementation that we need to call
* (same as [functionDescriptor] in all cases except for companion object methods annotated with [platformStatic],
* where [functionDescriptor] is the static method in the main class and [delegateFunctionDescriptor] is the
* implementation in the companion object class)
*/
fun generateOverloadsIfNeeded(function: JetNamedFunction,
functionDescriptor: FunctionDescriptor,
delegateFunctionDescriptor: FunctionDescriptor,
owner: CodegenContext<*>,
classBuilder: ClassBuilder) {
val overloadsFqName = FqName.fromSegments(listOf("kotlin", "jvm", "overloads"))
if (functionDescriptor.getAnnotations().findAnnotation(overloadsFqName) == null) return
val count = functionDescriptor.countDefaultParameters()
val context = owner.intoFunction(functionDescriptor)
for (i in 1..count) {
generateOverloadWithSubstitutedParameters(functionDescriptor, delegateFunctionDescriptor, classBuilder, function, context, i)
}
}
private fun FunctionDescriptor.countDefaultParameters() =
getValueParameters().count { it.hasDefaultValue() }
/**
* Generates an overload for [functionDescriptor] that substitutes default values for the last
* [substituteCount] parameters that have default values.
*
* @param functionDescriptor the method for which the overloads are generated
* @param delegateFunctionDescriptor the method descriptor for the implementation that we need to call
* (same as [functionDescriptor] in all cases except for companion object methods annotated with [platformStatic],
* where [functionDescriptor] is the static method in the main class and [delegateFunctionDescriptor] is the
* implementation in the companion object class)
* @param methodElement the PSI element for the method implementation (used in diagnostic messages only)
*/
fun generateOverloadWithSubstitutedParameters(functionDescriptor: FunctionDescriptor,
delegateFunctionDescriptor: FunctionDescriptor,
classBuilder: ClassBuilder,
methodElement: JetElement?,
context: CodegenContext<*>,
substituteCount: Int) {
val isStatic = AsmUtil.isStaticMethod(context.getContextKind(), functionDescriptor)
val flags = AsmUtil.getVisibilityAccessFlag(functionDescriptor) or (if (isStatic) Opcodes.ACC_STATIC else 0)
val remainingParameters = getRemainingParameters(functionDescriptor.getOriginal(), substituteCount)
val signature = state.getTypeMapper().mapSignature(functionDescriptor, context.getContextKind(),
remainingParameters)
val mv = classBuilder.newMethod(OtherOrigin(functionDescriptor), flags,
signature.getAsmMethod().getName(),
signature.getAsmMethod().getDescriptor(), null,
FunctionCodegen.getThrownExceptions(functionDescriptor, state.getTypeMapper()))
if (state.getClassBuilderMode() == ClassBuilderMode.LIGHT_CLASSES) return
val frameMap = FrameMap()
val v = InstructionAdapter(mv)
mv.visitCode()
val methodOwner = method.getOwner()
v.load(0, methodOwner) // Load this on stack
val methodOwner = state.getTypeMapper().mapToCallableMethod(delegateFunctionDescriptor, false, context).getOwner()
if (!isStatic) {
frameMap.enterTemp(AsmTypes.OBJECT_TYPE)
v.load(0, methodOwner) // Load this on stack
} else {
val delegateOwner = delegateFunctionDescriptor.getContainingDeclaration()
if (delegateOwner is ClassDescriptor && delegateOwner.isCompanionObject()) {
val singletonValue = StackValue.singleton(delegateOwner, state.getTypeMapper())
singletonValue.put(singletonValue.type, v);
}
}
val receiver = functionDescriptor.getExtensionReceiverParameter()
if (receiver != null) {
val receiverType = state.getTypeMapper().mapType(receiver)
val receiverIndex = frameMap.enter(receiver, receiverType)
StackValue.local(receiverIndex, receiverType).put(receiverType, v)
}
remainingParameters.forEach {
frameMap.enter(it, state.getTypeMapper().mapType(it))
}
var mask = 0
val masks = arrayListOf<Int>()
for (parameterDescriptor in constructorDescriptor.getValueParameters()) {
for (parameterDescriptor in functionDescriptor.getValueParameters()) {
val paramType = state.getTypeMapper().mapType(parameterDescriptor.getType())
AsmUtil.pushDefaultValueOnStack(paramType, v)
val i = parameterDescriptor.getIndex()
if (i != 0 && i % Integer.SIZE == 0) {
masks.add(mask)
mask = 0
if (parameterDescriptor in remainingParameters) {
val index = frameMap.getIndex(parameterDescriptor)
val type = state.getTypeMapper().mapType(parameterDescriptor)
StackValue.local(index, type).put(type, v)
} else {
AsmUtil.pushDefaultValueOnStack(paramType, v)
val i = parameterDescriptor.getIndex()
if (i != 0 && i % Integer.SIZE == 0) {
masks.add(mask)
mask = 0
}
mask = mask or (1 shl (i % Integer.SIZE))
}
mask = mask or (1 shl (i % Integer.SIZE))
}
masks.add(mask)
for (m in masks) {
@@ -66,12 +162,24 @@ public class DefaultParameterValueSubstitutor(val state: GenerationState) {
}
// constructors with default arguments has last synthetic argument of specific type
v.aconst(null)
if (functionDescriptor is ConstructorDescriptor) {
v.aconst(null)
}
val desc = JetTypeMapper.getDefaultDescriptor(method.getAsmMethod(), false)
v.invokespecial(methodOwner.getInternalName(), "<init>", desc, false)
v.areturn(Type.VOID_TYPE)
FunctionCodegen.endVisit(mv, "default constructor for " + methodOwner.getInternalName(), classOrObject)
val defaultMethod = state.getTypeMapper().mapDefaultMethod(delegateFunctionDescriptor, context.getContextKind(), context)
if (functionDescriptor is ConstructorDescriptor) {
v.invokespecial(methodOwner.getInternalName(), defaultMethod.getName(), defaultMethod.getDescriptor(), false)
} else {
v.invokestatic(methodOwner.getInternalName(), defaultMethod.getName(), defaultMethod.getDescriptor(), false)
}
v.areturn(signature.getReturnType())
FunctionCodegen.endVisit(mv, null, methodElement)
}
private fun getRemainingParameters(functionDescriptor: FunctionDescriptor,
substituteCount: Int): List<ValueParameterDescriptor> {
var remainingCount = functionDescriptor.countDefaultParameters() - substituteCount
return functionDescriptor.getValueParameters().filter { !it.declaresDefaultValue() || --remainingCount >= 0 }
}
private fun isEmptyConstructorNeeded(constructorDescriptor: ConstructorDescriptor, classOrObject: JetClassOrObject): Boolean {
@@ -113,6 +113,16 @@ public class FunctionCodegen {
generateDefaultIfNeeded(owner.intoFunction(functionDescriptor), functionDescriptor, owner.getContextKind(),
DefaultParameterValueLoader.DEFAULT, function);
generateOverloadsWithDefaultValues(function, functionDescriptor, functionDescriptor);
}
public void generateOverloadsWithDefaultValues(@NotNull JetNamedFunction function,
FunctionDescriptor functionDescriptor, FunctionDescriptor delegateFunctionDescriptor) {
new DefaultParameterValueSubstitutor(state).generateOverloadsIfNeeded(function,
functionDescriptor,
delegateFunctionDescriptor,
owner, v);
}
public void generateMethod(
@@ -1076,8 +1076,8 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
functionCodegen.generateDefaultIfNeeded(constructorContext, constructorDescriptor, OwnerKind.IMPLEMENTATION,
DefaultParameterValueLoader.DEFAULT, null);
CallableMethod callableMethod = typeMapper.mapToCallableMethod(constructorDescriptor);
new DefaultParameterValueSubstitutor(state).generateDefaultConstructorIfNeeded(callableMethod, constructorDescriptor, v, myClass);
new DefaultParameterValueSubstitutor(state).generateDefaultConstructorIfNeeded(constructorDescriptor, v,
constructorContext, myClass);
if (isCompanionObject(descriptor)) {
context.recordSyntheticAccessorIfNeeded(constructorDescriptor, bindingContext);
@@ -16,19 +16,20 @@
package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.backend.common.CodegenUtil
import org.jetbrains.kotlin.codegen.context.MethodContext
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor
import org.jetbrains.kotlin.psi.JetElement
import org.jetbrains.kotlin.psi.JetNamedFunction
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.resolve.jvm.diagnostics.Synthetic
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.codegen.context.MethodContext
import org.jetbrains.kotlin.psi.JetElement
import org.jetbrains.kotlin.backend.common.CodegenUtil
import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import kotlin.platform.platformStatic
class PlatformStaticGenerator(
@@ -40,8 +41,9 @@ class PlatformStaticGenerator(
override fun invoke(codegen: ImplementationBodyCodegen, classBuilder: ClassBuilder) {
val staticFunctionDescriptor = createStaticFunctionDescriptor(descriptor)
val originElement = declarationOrigin.element
codegen.functionCodegen.generateMethod(
Synthetic(declarationOrigin.element, staticFunctionDescriptor),
Synthetic(originElement, staticFunctionDescriptor),
staticFunctionDescriptor,
object : FunctionGenerationStrategy() {
override fun generateBody(
@@ -74,6 +76,11 @@ class PlatformStaticGenerator(
}
}
)
if (originElement is JetNamedFunction) {
codegen.functionCodegen.generateOverloadsWithDefaultValues(originElement, staticFunctionDescriptor, descriptor)
}
}
companion object {
@@ -663,13 +663,22 @@ public class JetTypeMapper {
@NotNull
public JvmMethodSignature mapSignature(@NotNull FunctionDescriptor f, @NotNull OwnerKind kind) {
if (f instanceof ConstructorDescriptor) {
return mapSignature(f, kind, f.getOriginal().getValueParameters());
}
return mapSignature(f, kind, f.getValueParameters());
}
@NotNull
public JvmMethodSignature mapSignature(@NotNull FunctionDescriptor f, @NotNull OwnerKind kind,
List<ValueParameterDescriptor> valueParameters) {
BothSignatureWriter sw = new BothSignatureWriter(BothSignatureWriter.Mode.METHOD);
if (f instanceof ConstructorDescriptor) {
sw.writeParametersStart();
writeAdditionalConstructorParameters((ConstructorDescriptor) f, sw);
for (ValueParameterDescriptor parameter : f.getOriginal().getValueParameters()) {
for (ValueParameterDescriptor parameter : valueParameters) {
writeParameter(sw, parameter.getType());
}
@@ -686,7 +695,7 @@ public class JetTypeMapper {
writeParameter(sw, JvmMethodParameterKind.RECEIVER, receiverParameter.getType());
}
for (ValueParameterDescriptor parameter : f.getValueParameters()) {
for (ValueParameterDescriptor parameter : valueParameters) {
writeParameter(sw, parameter.getType());
}
@@ -706,7 +715,7 @@ public class JetTypeMapper {
}
@NotNull
public static String getDefaultDescriptor(@NotNull Method method, boolean isExtension) {
public static String getDefaultDescriptor(@NotNull Method method, @Nullable String dispatchReceiverDescriptor, boolean isExtension) {
String descriptor = method.getDescriptor();
int argumentsCount = Type.getArgumentTypes(descriptor).length;
if (isExtension) {
@@ -717,7 +726,11 @@ public class JetTypeMapper {
if (isConstructor(method)) {
additionalArgs += Type.getObjectType(DEFAULT_CONSTRUCTOR_MARKER_INTERNAL_CLASS_NAME).getDescriptor();
}
return descriptor.replace(")", additionalArgs + ")");
String result = descriptor.replace(")", additionalArgs + ")");
if (dispatchReceiverDescriptor != null && !isConstructor(method)) {
return result.replace("(", "(" + dispatchReceiverDescriptor);
}
return result;
}
private static boolean isConstructor(@NotNull Method method) {
@@ -728,11 +741,10 @@ public class JetTypeMapper {
public Method mapDefaultMethod(@NotNull FunctionDescriptor functionDescriptor, @NotNull OwnerKind kind, @NotNull CodegenContext<?> context) {
Method jvmSignature = mapSignature(functionDescriptor, kind).getAsmMethod();
Type ownerType = mapOwner(functionDescriptor, isCallInsideSameModuleAsDeclared(functionDescriptor, context, getOutDirectory()));
String descriptor = getDefaultDescriptor(jvmSignature, functionDescriptor.getExtensionReceiverParameter() != null);
boolean isConstructor = isConstructor(jvmSignature);
if (!isStaticMethod(kind, functionDescriptor) && !isConstructor) {
descriptor = descriptor.replace("(", "(" + ownerType.getDescriptor());
}
String descriptor = getDefaultDescriptor(jvmSignature,
isStaticMethod(kind, functionDescriptor) || isConstructor ? null : ownerType.getDescriptor(),
functionDescriptor.getExtensionReceiverParameter() != null);
return new Method(isConstructor ? "<init>" : jvmSignature.getName() + JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX, descriptor);
}
@@ -0,0 +1,12 @@
class C {
companion object {
[kotlin.platform.platformStatic] [kotlin.jvm.overloads] public fun foo(o: String, k: String = "K"): String {
return o + k
}
}
}
fun box(): String {
val m = javaClass<C>().getMethod("foo", javaClass<String>())
return m.invoke(null, "O") as String
}
@@ -0,0 +1,11 @@
class C {
[kotlin.jvm.overloads] public fun foo(d1: Double, d2: Double, status: String = "OK"): String {
return if (d1 + d2 == 3.0) status else "fail"
}
}
fun box(): String {
val c = C()
val m = c.javaClass.getMethod("foo", javaClass<Double>(), javaClass<Double>())
return m.invoke(c, 1.0, 2.0) as String
}
@@ -0,0 +1,11 @@
class C {
}
[kotlin.jvm.overloads] fun C.foo(o: String, k: String = "K"): String {
return o + k
}
fun box(): String {
val m = javaClass<C>().getClassLoader().loadClass("_DefaultPackage").getMethod("foo", javaClass<C>(), javaClass<String>())
return m.invoke(null, C(), "O") as String
}
@@ -0,0 +1,11 @@
class C {
[kotlin.jvm.overloads] public fun foo(o: String = "O", k: String = "K"): String {
return o + k
}
}
fun box(): String {
val c = C()
val m = c.javaClass.getMethod("foo")
return m.invoke(c) as String
}
@@ -0,0 +1,11 @@
class C {
[kotlin.jvm.overloads] public fun foo(o: String, k: String = "K"): String {
return o + k
}
}
fun box(): String {
val c = C()
val m = c.javaClass.getMethod("foo", javaClass<String>())
return m.invoke(c, "O") as String
}
@@ -0,0 +1,11 @@
class C {
[kotlin.jvm.overloads] public fun foo(s: String = "OK"): String {
return s
}
}
fun box(): String {
val c = C()
val m = c.javaClass.getMethod("foo")
return m.invoke(c) as String
}
@@ -46,6 +46,7 @@ import java.util.regex.Pattern;
BlackBoxWithStdlibCodegenTestGenerated.HashPMap.class,
BlackBoxWithStdlibCodegenTestGenerated.Intrinsics.class,
BlackBoxWithStdlibCodegenTestGenerated.JdkAnnotations.class,
BlackBoxWithStdlibCodegenTestGenerated.JvmOverloads.class,
BlackBoxWithStdlibCodegenTestGenerated.LazyCodegen.class,
BlackBoxWithStdlibCodegenTestGenerated.LocalFunInLambda.class,
BlackBoxWithStdlibCodegenTestGenerated.MultiDeclForArray.class,
@@ -1640,6 +1641,51 @@ public class BlackBoxWithStdlibCodegenTestGenerated extends AbstractBlackBoxCode
}
}
@TestMetadata("compiler/testData/codegen/boxWithStdlib/jvmOverloads")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class JvmOverloads extends AbstractBlackBoxCodegenTest {
public void testAllFilesPresentInJvmOverloads() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxWithStdlib/jvmOverloads"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("companionObject.kt")
public void testCompanionObject() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/jvmOverloads/companionObject.kt");
doTestWithStdlib(fileName);
}
@TestMetadata("doubleParameters.kt")
public void testDoubleParameters() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/jvmOverloads/doubleParameters.kt");
doTestWithStdlib(fileName);
}
@TestMetadata("extensionMethod.kt")
public void testExtensionMethod() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/jvmOverloads/extensionMethod.kt");
doTestWithStdlib(fileName);
}
@TestMetadata("multipleDefaultParameters.kt")
public void testMultipleDefaultParameters() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/jvmOverloads/multipleDefaultParameters.kt");
doTestWithStdlib(fileName);
}
@TestMetadata("nonDefaultParameter.kt")
public void testNonDefaultParameter() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/jvmOverloads/nonDefaultParameter.kt");
doTestWithStdlib(fileName);
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/jvmOverloads/simple.kt");
doTestWithStdlib(fileName);
}
}
@TestMetadata("compiler/testData/codegen/boxWithStdlib/lazyCodegen")
@TestDataPath("$PROJECT_ROOT")
@InnerTestClasses({
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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 kotlin.jvm
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
/**
* Instructs the Kotlin compiler to generate overloads for this function that substitute default parameter values.
*/
Retention(RetentionPolicy.SOURCE)
public annotation class overloads