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

#KT-2095 Fixed

fix backend-side issues with kotlin.jvm.overloads: support the annotation on constructors, generate nullablity annotations on parameters, generate generic signatures, add various tests
This commit is contained in:
Dmitry Jemerov
2015-03-27 18:48:03 +01:00
parent e15b984232
commit 39828bfd32
19 changed files with 231 additions and 35 deletions
@@ -39,10 +39,15 @@ public class DefaultParameterValueSubstitutor(val state: GenerationState) {
* 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) {
fun generateConstructorOverloadsIfNeeded(constructorDescriptor: ConstructorDescriptor,
classBuilder: ClassBuilder,
context: CodegenContext<*>,
classOrObject: JetClassOrObject) {
if (generateOverloadsIfNeeded(classOrObject, constructorDescriptor, constructorDescriptor,
context, classBuilder)) {
return
}
if (!isEmptyConstructorNeeded(constructorDescriptor, classOrObject)) {
return
}
@@ -63,21 +68,24 @@ public class DefaultParameterValueSubstitutor(val state: GenerationState) {
* (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)
* @return true if the overloads annotation was found on the element, false otherwise
*/
fun generateOverloadsIfNeeded(function: JetNamedFunction,
fun generateOverloadsIfNeeded(methodElement: JetElement,
functionDescriptor: FunctionDescriptor,
delegateFunctionDescriptor: FunctionDescriptor,
owner: CodegenContext<*>,
classBuilder: ClassBuilder) {
val overloadsFqName = FqName.fromSegments(listOf("kotlin", "jvm", "overloads"))
if (functionDescriptor.getAnnotations().findAnnotation(overloadsFqName) == null) return
classBuilder: ClassBuilder): Boolean {
if (functionDescriptor.getAnnotations().findAnnotation(FqName("kotlin.jvm.overloads")) == null) {
return false
}
val count = functionDescriptor.countDefaultParameters()
val context = owner.intoFunction(functionDescriptor)
for (i in 1..count) {
generateOverloadWithSubstitutedParameters(functionDescriptor, delegateFunctionDescriptor, classBuilder, function, context, i)
generateOverloadWithSubstitutedParameters(functionDescriptor, delegateFunctionDescriptor, classBuilder, methodElement, context, i)
}
return true
}
private fun FunctionDescriptor.countDefaultParameters() =
@@ -100,53 +108,66 @@ public class DefaultParameterValueSubstitutor(val state: GenerationState) {
methodElement: JetElement?,
context: CodegenContext<*>,
substituteCount: Int) {
val typeMapper = state.getTypeMapper()
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 signature = typeMapper.mapSignature(functionDescriptor, context.getContextKind(), remainingParameters)
val mv = classBuilder.newMethod(OtherOrigin(functionDescriptor), flags,
signature.getAsmMethod().getName(),
signature.getAsmMethod().getDescriptor(), null,
FunctionCodegen.getThrownExceptions(functionDescriptor, state.getTypeMapper()))
signature.getAsmMethod().getDescriptor(),
signature.getGenericsSignature(),
FunctionCodegen.getThrownExceptions(functionDescriptor, typeMapper))
if (state.getClassBuilderMode() == ClassBuilderMode.LIGHT_CLASSES) return
AnnotationCodegen.forMethod(mv, typeMapper).genAnnotations(functionDescriptor, signature.getReturnType())
remainingParameters.withIndex().forEach {
val annotationCodegen = AnnotationCodegen.forParameter(it.index, mv, typeMapper)
annotationCodegen.genAnnotations(it.value, signature.getValueParameters()[it.index].getAsmType())
}
if (state.getClassBuilderMode() == ClassBuilderMode.LIGHT_CLASSES) {
mv.visitEnd()
return
}
val frameMap = FrameMap()
val v = InstructionAdapter(mv)
mv.visitCode()
val methodOwner = state.getTypeMapper().mapToCallableMethod(delegateFunctionDescriptor, false, context).getOwner()
val methodOwner = typeMapper.mapToCallableMethod(delegateFunctionDescriptor, false, context).getOwner()
if (!isStatic) {
frameMap.enterTemp(AsmTypes.OBJECT_TYPE)
v.load(0, methodOwner) // Load this on stack
} else {
val thisIndex = frameMap.enterTemp(AsmTypes.OBJECT_TYPE)
v.load(thisIndex, methodOwner) // Load this on stack
}
else {
val delegateOwner = delegateFunctionDescriptor.getContainingDeclaration()
if (delegateOwner is ClassDescriptor && delegateOwner.isCompanionObject()) {
val singletonValue = StackValue.singleton(delegateOwner, state.getTypeMapper())
val singletonValue = StackValue.singleton(delegateOwner, typeMapper)
singletonValue.put(singletonValue.type, v);
}
}
val receiver = functionDescriptor.getExtensionReceiverParameter()
if (receiver != null) {
val receiverType = state.getTypeMapper().mapType(receiver)
val receiverType = typeMapper.mapType(receiver)
val receiverIndex = frameMap.enter(receiver, receiverType)
StackValue.local(receiverIndex, receiverType).put(receiverType, v)
}
remainingParameters.forEach {
frameMap.enter(it, state.getTypeMapper().mapType(it))
frameMap.enter(it, typeMapper.mapType(it))
}
var mask = 0
val masks = arrayListOf<Int>()
for (parameterDescriptor in functionDescriptor.getValueParameters()) {
val paramType = state.getTypeMapper().mapType(parameterDescriptor.getType())
val paramType = typeMapper.mapType(parameterDescriptor.getType())
if (parameterDescriptor in remainingParameters) {
val index = frameMap.getIndex(parameterDescriptor)
val type = state.getTypeMapper().mapType(parameterDescriptor)
StackValue.local(index, type).put(type, v)
} else {
StackValue.local(index, paramType).put(paramType, v)
}
else {
AsmUtil.pushDefaultValueOnStack(paramType, v)
val i = parameterDescriptor.getIndex()
if (i != 0 && i % Integer.SIZE == 0) {
@@ -166,10 +187,11 @@ public class DefaultParameterValueSubstitutor(val state: GenerationState) {
v.aconst(null)
}
val defaultMethod = state.getTypeMapper().mapDefaultMethod(delegateFunctionDescriptor, context.getContextKind(), context)
val defaultMethod = typeMapper.mapDefaultMethod(delegateFunctionDescriptor, context.getContextKind(), context)
if (functionDescriptor is ConstructorDescriptor) {
v.invokespecial(methodOwner.getInternalName(), defaultMethod.getName(), defaultMethod.getDescriptor(), false)
} else {
}
else {
v.invokestatic(methodOwner.getInternalName(), defaultMethod.getName(), defaultMethod.getDescriptor(), false)
}
v.areturn(signature.getReturnType())
@@ -1076,8 +1076,8 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
functionCodegen.generateDefaultIfNeeded(constructorContext, constructorDescriptor, OwnerKind.IMPLEMENTATION,
DefaultParameterValueLoader.DEFAULT, null);
new DefaultParameterValueSubstitutor(state).generateDefaultConstructorIfNeeded(constructorDescriptor, v,
constructorContext, myClass);
new DefaultParameterValueSubstitutor(state).generateConstructorOverloadsIfNeeded(constructorDescriptor, v,
constructorContext, myClass);
if (isCompanionObject(descriptor)) {
context.recordSyntheticAccessorIfNeeded(constructorDescriptor, bindingContext);
@@ -1099,6 +1099,9 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
functionCodegen.generateDefaultIfNeeded(constructorContext, constructorDescriptor, OwnerKind.IMPLEMENTATION,
DefaultParameterValueLoader.DEFAULT, null);
new DefaultParameterValueSubstitutor(state).generateOverloadsIfNeeded(myClass, constructorDescriptor, constructorDescriptor,
constructorContext, v);
}
private void generatePrimaryConstructorImpl(
@@ -0,0 +1,14 @@
public final class C {
@org.jetbrains.annotations.NotNull
public final java.lang.String foo(@org.jetbrains.annotations.NotNull java.lang.String o, @org.jetbrains.annotations.NotNull java.lang.String s1, @org.jetbrains.annotations.NotNull java.lang.String k, @org.jetbrains.annotations.Nullable java.lang.String s2) { /* compiled code */ }
public static java.lang.String foo$default(C p, java.lang.String p1, java.lang.String p2, java.lang.String p3, java.lang.String p4, int p5) { /* compiled code */ }
@org.jetbrains.annotations.NotNull
public java.lang.String foo(@org.jetbrains.annotations.NotNull java.lang.String p, @org.jetbrains.annotations.NotNull java.lang.String p1, @org.jetbrains.annotations.Nullable java.lang.String p2) { /* compiled code */ }
@org.jetbrains.annotations.NotNull
public java.lang.String foo(@org.jetbrains.annotations.NotNull java.lang.String p, @org.jetbrains.annotations.Nullable java.lang.String p1) { /* compiled code */ }
public C() { /* compiled code */ }
}
@@ -0,0 +1,7 @@
// C
class C {
[kotlin.jvm.overloads] public fun foo(o: String = "O", s1: String, k: String = "K", s2: String?): String {
return o + k
}
}
@@ -0,0 +1,6 @@
public class Test {
public static String invokeMethodWithOverloads() {
C<String> c = new C<String>();
return c.foo("O");
}
}
@@ -0,0 +1,7 @@
class C<T> {
[kotlin.jvm.overloads] public fun foo(o: T, k: String = "K"): String = o.toString() + k
}
fun box(): String {
return Test.invokeMethodWithOverloads()
}
@@ -0,0 +1,6 @@
public class Test {
public static String invokeMethodWithOverloads() {
C c = new C();
return c.foo();
}
}
@@ -0,0 +1,7 @@
class C {
[kotlin.jvm.overloads] public fun foo(o: String = "O", k: String = "K"): String = o + k
}
fun box(): String {
return Test.invokeMethodWithOverloads()
}
@@ -0,0 +1,11 @@
class C {
[kotlin.jvm.overloads] public fun foo(o: String = "O", i1: Int, k: String = "K", i2: Int): String {
return o + k
}
}
fun box(): String {
val c = C()
val m = c.javaClass.getMethod("foo", javaClass<Int>(), javaClass<Int>())
return m.invoke(c, 1, 2) as String
}
@@ -0,0 +1,8 @@
class C [kotlin.jvm.overloads] (s1: String, s2: String = "K") {
public val status: String = s1 + s2
}
fun box(): String {
val c = (javaClass<C>().getConstructor(javaClass<String>()).newInstance("O"))
return c.status
}
@@ -0,0 +1,12 @@
class C(val i: Int) {
var status = "fail"
[kotlin.jvm.overloads] constructor(o: String, k: String = "K"): this(-1) {
status = o + k
}
}
fun box(): String {
val c = (javaClass<C>().getConstructor(javaClass<String>()).newInstance("O"))
return c.status
}
@@ -0,0 +1,8 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
class <!CONFLICTING_JVM_DECLARATIONS!>A<!> {
[kotlin.jvm.overloads] fun foo(s: String = "") {
}
<!CONFLICTING_JVM_DECLARATIONS!>fun foo()<!> {
}
}
@@ -0,0 +1,10 @@
package
internal final class A {
public constructor A()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
internal final fun foo(): kotlin.Unit
kotlin.jvm.overloads() internal final fun foo(/*0*/ s: kotlin.String = ...): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -23,10 +23,14 @@ import com.intellij.psi.search.GlobalSearchScope;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.checkers.KotlinMultiFileTestWithWithJava;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.config.CompilerConfiguration;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.test.JetTestUtils;
import org.jetbrains.kotlin.test.TestJdkKind;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
@@ -35,6 +39,17 @@ import java.util.regex.Pattern;
public abstract class AbstractKotlinLightClassTest extends KotlinMultiFileTestWithWithJava<Void, Void> {
private static final Pattern SUBJECT_FQ_NAME_PATTERN = Pattern.compile("^//\\s*(.*)$", Pattern.MULTILINE);
@Override
@NotNull
protected CompilerConfiguration createCompilerConfiguration(File javaFilesDir) {
return JetTestUtils.compilerConfigurationForTests(
ConfigurationKind.ALL,
TestJdkKind.MOCK_JDK,
Arrays.asList(JetTestUtils.getAnnotationsJar()),
Arrays.asList(javaFilesDir)
);
}
@NotNull
@Override
protected File getKotlinSourceRoot() {
@@ -117,6 +117,12 @@ public class KotlinLightClassTestGenerated extends AbstractKotlinLightClassTest
doTest(fileName);
}
@TestMetadata("JvmOverloads.kt")
public void testJvmOverloads() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/nullabilityAnnotations/JvmOverloads.kt");
doTest(fileName);
}
@TestMetadata("NullableUnitReturn.kt")
public void testNullableUnitReturn() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/asJava/lightClasses/nullabilityAnnotations/NullableUnitReturn.kt");
@@ -658,6 +658,12 @@ public class JetDiagnosticsTestWithStdLibGenerated extends AbstractJetDiagnostic
doTest(fileName);
}
@TestMetadata("jvmOverloads.kt")
public void testJvmOverloads() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/duplicateJvmSignature/jvmOverloads.kt");
doTest(fileName);
}
@TestMetadata("platformNames.kt")
public void testPlatformNames() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/duplicateJvmSignature/platformNames.kt");
@@ -66,12 +66,7 @@ public abstract class KotlinMultiFileTestWithWithJava<M, F> extends JetLiteFixtu
@Override
protected KotlinCoreEnvironment createEnvironment() {
File javaFilesDir = createJavaFilesDir();
CompilerConfiguration configuration = JetTestUtils.compilerConfigurationForTests(
ConfigurationKind.JDK_AND_ANNOTATIONS,
TestJdkKind.MOCK_JDK,
Arrays.asList(JetTestUtils.getAnnotationsJar()),
Arrays.asList(javaFilesDir)
);
CompilerConfiguration configuration = createCompilerConfiguration(javaFilesDir);
File kotlinSourceRoot = getKotlinSourceRoot();
if (kotlinSourceRoot != null) {
addKotlinSourceRoot(configuration, kotlinSourceRoot.getPath());
@@ -79,6 +74,16 @@ public abstract class KotlinMultiFileTestWithWithJava<M, F> extends JetLiteFixtu
return createEnvironment(getTestRootDisposable(), configuration);
}
@NotNull
protected CompilerConfiguration createCompilerConfiguration(File javaFilesDir) {
return JetTestUtils.compilerConfigurationForTests(
ConfigurationKind.JDK_AND_ANNOTATIONS,
TestJdkKind.MOCK_JDK,
Arrays.asList(JetTestUtils.getAnnotationsJar()),
Arrays.asList(javaFilesDir)
);
}
@NotNull
protected KotlinCoreEnvironment createEnvironment(@NotNull Disposable disposable, @NotNull CompilerConfiguration configuration) {
return KotlinCoreEnvironment.createForTests(disposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES);
@@ -32,6 +32,7 @@ import java.util.regex.Pattern;
@TestDataPath("$PROJECT_ROOT")
@InnerTestClasses({
BlackBoxWithJavaCodegenTestGenerated.BuiltinStubMethods.class,
BlackBoxWithJavaCodegenTestGenerated.JvmOverloads.class,
BlackBoxWithJavaCodegenTestGenerated.NotNullAssertions.class,
BlackBoxWithJavaCodegenTestGenerated.PlatformStatic.class,
BlackBoxWithJavaCodegenTestGenerated.Properties.class,
@@ -98,6 +99,30 @@ public class BlackBoxWithJavaCodegenTestGenerated extends AbstractBlackBoxCodege
}
@TestMetadata("compiler/testData/codegen/boxWithJava/jvmOverloads")
@TestDataPath("$PROJECT_ROOT")
@InnerTestClasses({
})
@RunWith(JUnit3RunnerWithInners.class)
public static class JvmOverloads extends AbstractBlackBoxCodegenTest {
public void testAllFilesPresentInJvmOverloads() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxWithJava/jvmOverloads"), Pattern.compile("^([^\\.]+)$"), true);
}
@TestMetadata("generics")
public void testGenerics() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithJava/jvmOverloads/generics/");
doTestWithJava(fileName);
}
@TestMetadata("simple")
public void testSimple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithJava/jvmOverloads/simple/");
doTestWithJava(fileName);
}
}
@TestMetadata("compiler/testData/codegen/boxWithJava/notNullAssertions")
@TestDataPath("$PROJECT_ROOT")
@InnerTestClasses({
@@ -1655,6 +1655,12 @@ public class BlackBoxWithStdlibCodegenTestGenerated extends AbstractBlackBoxCode
doTestWithStdlib(fileName);
}
@TestMetadata("defaultsNotAtEnd.kt")
public void testDefaultsNotAtEnd() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/jvmOverloads/defaultsNotAtEnd.kt");
doTestWithStdlib(fileName);
}
@TestMetadata("doubleParameters.kt")
public void testDoubleParameters() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/jvmOverloads/doubleParameters.kt");
@@ -1679,6 +1685,18 @@ public class BlackBoxWithStdlibCodegenTestGenerated extends AbstractBlackBoxCode
doTestWithStdlib(fileName);
}
@TestMetadata("primaryConstructor.kt")
public void testPrimaryConstructor() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/jvmOverloads/primaryConstructor.kt");
doTestWithStdlib(fileName);
}
@TestMetadata("secondaryConstructor.kt")
public void testSecondaryConstructor() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/jvmOverloads/secondaryConstructor.kt");
doTestWithStdlib(fileName);
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/jvmOverloads/simple.kt");