Generate JvmOverloads methods as final

#KT-33240 Fixed
This commit is contained in:
Alexander Udalov
2020-01-22 18:31:05 +01:00
parent 73aa36ca59
commit dcf6a2199a
8 changed files with 133 additions and 26 deletions
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.codegen.JvmCodegenUtil.getDispatchReceiverParameterForConstructorCall
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtParameter
@@ -132,10 +133,13 @@ class DefaultParameterValueSubstitutor(val state: GenerationState) {
val remainingParametersDeclarations =
remainingParameters.map { DescriptorToSourceUtils.descriptorToDeclaration(it) as? KtParameter }
val generateAsFinal =
functionDescriptor.modality == Modality.FINAL ||
state.languageVersionSettings.supportsFeature(LanguageFeature.GenerateJvmOverloadsAsFinal)
val flags =
baseMethodFlags or
(if (isStatic) Opcodes.ACC_STATIC else 0) or
(if (functionDescriptor.modality == Modality.FINAL && functionDescriptor !is ConstructorDescriptor) Opcodes.ACC_FINAL else 0) or
(if (generateAsFinal && functionDescriptor !is ConstructorDescriptor) Opcodes.ACC_FINAL else 0) or
(if (remainingParameters.lastOrNull()?.varargElementType != null) Opcodes.ACC_VARARGS else 0)
val signature = typeMapper.mapSignatureWithCustomParameters(functionDescriptor, contextKind, remainingParameters, false)
val mv = classBuilder.newMethod(
@@ -6,11 +6,13 @@
package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.ClassLoweringPass
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.common.ir.copyTo
import org.jetbrains.kotlin.backend.common.ir.copyTypeParametersFrom
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrConstructorImpl
@@ -137,13 +139,16 @@ private class JvmOverloadsAnnotationLowering(val context: JvmBackendContext) : C
}
is IrSimpleFunction -> {
val descriptor = WrappedSimpleFunctionDescriptor(oldFunction.descriptor.annotations)
val modality =
if (context.state.languageVersionSettings.supportsFeature(LanguageFeature.GenerateJvmOverloadsAsFinal)) Modality.FINAL
else oldFunction.modality
IrFunctionImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
JvmLoweredDeclarationOrigin.JVM_OVERLOADS_WRAPPER,
IrSimpleFunctionSymbolImpl(descriptor),
oldFunction.name,
oldFunction.visibility,
oldFunction.modality,
modality,
returnType = oldFunction.returnType,
isInline = oldFunction.isInline,
isExternal = false,
@@ -151,7 +156,7 @@ private class JvmOverloadsAnnotationLowering(val context: JvmBackendContext) : C
isSuspend = oldFunction.isSuspend,
isExpect = false,
isFakeOverride = false,
isOperator = false
isOperator = false,
).apply {
descriptor.bind(this)
}
@@ -0,0 +1,18 @@
// WITH_RUNTIME
open class Foo {
@JvmOverloads
open fun bar(x: Int = 42, y: Int = -1): Int = x + y
}
// TESTED_OBJECT_KIND: function
// TESTED_OBJECTS: Foo, bar, (II)I
// FLAGS: ACC_PUBLIC
// TESTED_OBJECT_KIND: function
// TESTED_OBJECTS: Foo, bar, (I)I
// FLAGS: ACC_PUBLIC, ACC_FINAL
// TESTED_OBJECT_KIND: function
// TESTED_OBJECTS: Foo, bar, ()I
// FLAGS: ACC_PUBLIC, ACC_FINAL
@@ -0,0 +1,19 @@
// !LANGUAGE: -GenerateJvmOverloadsAsFinal
// WITH_RUNTIME
open class Foo {
@JvmOverloads
open fun bar(x: Int = 42, y: Int = -1): Int = x + y
}
// TESTED_OBJECT_KIND: function
// TESTED_OBJECTS: Foo, bar, (II)I
// FLAGS: ACC_PUBLIC
// TESTED_OBJECT_KIND: function
// TESTED_OBJECTS: Foo, bar, (I)I
// FLAGS: ACC_PUBLIC
// TESTED_OBJECT_KIND: function
// TESTED_OBJECTS: Foo, bar, ()I
// FLAGS: ACC_PUBLIC
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.codegen.flags;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.kotlin.codegen.CodegenTestCase;
import org.jetbrains.org.objectweb.asm.*;
@@ -34,7 +35,7 @@ import static org.jetbrains.kotlin.test.InTextDirectivesUtils.findStringWithPref
* Test correctness of written flags in class file
*
* TESTED_OBJECT_KIND - maybe class, function or property
* TESTED_OBJECTS - className, [function/property name]
* TESTED_OBJECTS - className, [function/property name], [function/property signature]
* FLAGS - only flags which must be true (could be skipped if ABSENT is TRUE)
* ABSENT - true or false, optional (false by default)
*
@@ -46,6 +47,10 @@ import static org.jetbrains.kotlin.test.InTextDirectivesUtils.findStringWithPref
* TESTED_OBJECT_KIND: property
* TESTED_OBJECTS: Test, prop$delegate
* FLAGS: ACC_STATIC, ACC_FINAL, ACC_PRIVATE
*
* TESTED_OBJECT_KIND: function
* TESTED_OBJECTS: Test, function, (ILjava/lang/String;)[Ljava/lang/Object;
* FLAGS: ACC_PUBLIC, ACC_SYNTHETIC
*/
public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
@@ -72,11 +77,11 @@ public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
assertNotNull(outputFile);
ClassReader cr = new ClassReader(outputFile.asByteArray());
TestClassVisitor classVisitor = getClassVisitor(testedObject.kind, testedObject.name, false);
TestClassVisitor classVisitor = getClassVisitor(testedObject, false);
cr.accept(classVisitor, ClassReader.SKIP_CODE);
if (!classVisitor.isExists()) {
classVisitor = getClassVisitor(testedObject.kind, testedObject.name, true);
classVisitor = getClassVisitor(testedObject, true);
cr.accept(classVisitor, ClassReader.SKIP_CODE);
}
@@ -100,7 +105,7 @@ public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
TestedObject testObject = new TestedObject();
testObject.textData = testData;
List<String> testedObjects = findListWithPrefixes(testData, "// TESTED_OBJECTS: ");
assertTrue("Cannot find TESTED_OBJECTS instruction", !testedObjects.isEmpty());
assertFalse("Cannot find TESTED_OBJECTS instruction", testedObjects.isEmpty());
testObject.containingClass = testedObjects.get(0);
if (testedObjects.size() == 1) {
testObject.name = testedObjects.get(0);
@@ -108,9 +113,13 @@ public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
else if (testedObjects.size() == 2) {
testObject.name = testedObjects.get(1);
}
else if (testedObjects.size() == 3) {
testObject.name = testedObjects.get(1);
testObject.signature = testedObjects.get(2);
}
else {
throw new IllegalArgumentException(
"TESTED_OBJECTS instruction must contain one (for class) or two (for function and property) values");
"TESTED_OBJECTS instruction must contain one (for class), two or three (for function and property) values");
}
testObject.kind = findStringWithPrefixes(testData, "// TESTED_OBJECT_KIND: ");
@@ -120,7 +129,7 @@ public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
}
objects.add(testObject);
}
assertTrue("Test description not present!", !objects.isEmpty());
assertFalse("Test description not present!", objects.isEmpty());
return objects;
}
@@ -130,27 +139,28 @@ public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
public boolean isFullContainingClassName = true;
public String kind;
public String textData;
public String signature;
@Override
public String toString() {
return "Class = " + containingClass + ", name = " + name + ", kind = " + kind;
return "Class = " + containingClass + ", name = " + name + ", kind = " + kind +
(signature != null ? ", signature = " + signature : "");
}
}
private static TestClassVisitor getClassVisitor(String visitorKind, String testedObjectName, boolean allowSynthetic) {
switch (visitorKind) {
private static TestClassVisitor getClassVisitor(@NotNull TestedObject object, boolean allowSynthetic) {
switch (object.kind) {
case "class":
return new ClassFlagsVisitor();
case "function":
return new FunctionFlagsVisitor(testedObjectName, allowSynthetic);
return new FunctionFlagsVisitor(object.name, object.signature, allowSynthetic);
case "property":
return new PropertyFlagsVisitor(testedObjectName);
return new PropertyFlagsVisitor(object.name, object.signature);
case "innerClass":
return new InnerClassFlagsVisitor(testedObjectName);
default:
return new InnerClassFlagsVisitor(object.name);
default:
throw new IllegalArgumentException("Value of TESTED_OBJECT_KIND is incorrect: " + object.kind);
}
throw new IllegalArgumentException("Value of TESTED_OBJECT_KIND is incorrect: " + visitorKind);
}
protected static abstract class TestClassVisitor extends ClassVisitor {
@@ -170,7 +180,7 @@ public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
private static int getExpectedFlags(String text) {
int expectedAccess = 0;
Class klass = Opcodes.class;
Class<?> klass = Opcodes.class;
List<String> flags = findListWithPrefixes(text, "// FLAGS: ");
for (String flag : flags) {
try {
@@ -202,16 +212,18 @@ public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
private static class FunctionFlagsVisitor extends TestClassVisitor {
private int access = 0;
private final String funName;
private final String funSignature;
private final boolean allowSynthetic;
public FunctionFlagsVisitor(String name, boolean allowSynthetic) {
funName = name;
public FunctionFlagsVisitor(@NotNull String name, @Nullable String signature, boolean allowSynthetic) {
this.funName = name;
this.funSignature = signature;
this.allowSynthetic = allowSynthetic;
}
@Override
public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) {
if (name.equals(funName)) {
if (name.equals(funName) && (funSignature == null || funSignature.equals(desc))) {
if (!allowSynthetic && (access & Opcodes.ACC_SYNTHETIC) != 0) return null;
this.access = access;
isExists = true;
@@ -228,14 +240,16 @@ public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
private static class PropertyFlagsVisitor extends TestClassVisitor {
private int access = 0;
private final String propertyName;
private final String propertySignature;
public PropertyFlagsVisitor(String name) {
propertyName = name;
public PropertyFlagsVisitor(@NotNull String name, @Nullable String signature) {
this.propertyName = name;
this.propertySignature = signature;
}
@Override
public FieldVisitor visitField(int access, @NotNull String name, @NotNull String desc, String signature, Object value) {
if (name.equals(propertyName)) {
if (name.equals(propertyName) || (propertySignature == null || propertySignature.equals(desc))) {
this.access = access;
isExists = true;
}
@@ -821,6 +821,29 @@ public class WriteFlagsTestGenerated extends AbstractWriteFlagsTest {
}
}
@TestMetadata("compiler/testData/writeFlags/jvmOverloads")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class JvmOverloads extends AbstractWriteFlagsTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
}
public void testAllFilesPresentInJvmOverloads() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/writeFlags/jvmOverloads"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
}
@TestMetadata("openFunction.kt")
public void testOpenFunction() throws Exception {
runTest("compiler/testData/writeFlags/jvmOverloads/openFunction.kt");
}
@TestMetadata("openFunction_1_3.kt")
public void testOpenFunction_1_3() throws Exception {
runTest("compiler/testData/writeFlags/jvmOverloads/openFunction_1_3.kt");
}
}
@TestMetadata("compiler/testData/writeFlags/lambda")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -821,6 +821,29 @@ public class IrWriteFlagsTestGenerated extends AbstractIrWriteFlagsTest {
}
}
@TestMetadata("compiler/testData/writeFlags/jvmOverloads")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class JvmOverloads extends AbstractIrWriteFlagsTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath);
}
public void testAllFilesPresentInJvmOverloads() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/writeFlags/jvmOverloads"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@TestMetadata("openFunction.kt")
public void testOpenFunction() throws Exception {
runTest("compiler/testData/writeFlags/jvmOverloads/openFunction.kt");
}
@TestMetadata("openFunction_1_3.kt")
public void testOpenFunction_1_3() throws Exception {
runTest("compiler/testData/writeFlags/jvmOverloads/openFunction_1_3.kt");
}
}
@TestMetadata("compiler/testData/writeFlags/lambda")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -124,6 +124,7 @@ enum class LanguageFeature(
DoNotGenerateThrowsForDelegatedKotlinMembers(KOTLIN_1_4),
ProperIeee754Comparisons(KOTLIN_1_4, kind = BUG_FIX),
FunctionalInterfaceConversion(KOTLIN_1_4, kind = UNSTABLE_FEATURE),
GenerateJvmOverloadsAsFinal(KOTLIN_1_4),
ProhibitSpreadOnSignaturePolymorphicCall(KOTLIN_1_5, kind = BUG_FIX),
ProhibitInvisibleAbstractMethodsInSuperclasses(KOTLIN_1_5, kind = BUG_FIX),