Generate JvmOverloads methods as final
#KT-33240 Fixed
This commit is contained in:
+5
-1
@@ -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(
|
||||
|
||||
+8
-3
@@ -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
|
||||
+36
-22
@@ -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;
|
||||
}
|
||||
|
||||
+23
@@ -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)
|
||||
|
||||
+23
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user