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.JvmCodegenUtil.getDispatchReceiverParameterForConstructorCall
import org.jetbrains.kotlin.codegen.binding.CodegenBinding import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.psi.KtParameter
@@ -132,10 +133,13 @@ class DefaultParameterValueSubstitutor(val state: GenerationState) {
val remainingParametersDeclarations = val remainingParametersDeclarations =
remainingParameters.map { DescriptorToSourceUtils.descriptorToDeclaration(it) as? KtParameter } remainingParameters.map { DescriptorToSourceUtils.descriptorToDeclaration(it) as? KtParameter }
val generateAsFinal =
functionDescriptor.modality == Modality.FINAL ||
state.languageVersionSettings.supportsFeature(LanguageFeature.GenerateJvmOverloadsAsFinal)
val flags = val flags =
baseMethodFlags or baseMethodFlags or
(if (isStatic) Opcodes.ACC_STATIC else 0) 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) (if (remainingParameters.lastOrNull()?.varargElementType != null) Opcodes.ACC_VARARGS else 0)
val signature = typeMapper.mapSignatureWithCustomParameters(functionDescriptor, contextKind, remainingParameters, false) val signature = typeMapper.mapSignatureWithCustomParameters(functionDescriptor, contextKind, remainingParameters, false)
val mv = classBuilder.newMethod( val mv = classBuilder.newMethod(
@@ -6,11 +6,13 @@
package org.jetbrains.kotlin.backend.jvm.lower package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.ClassLoweringPass 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.copyTo
import org.jetbrains.kotlin.backend.common.ir.copyTypeParametersFrom 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.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin 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.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrConstructorImpl import org.jetbrains.kotlin.ir.declarations.impl.IrConstructorImpl
@@ -137,13 +139,16 @@ private class JvmOverloadsAnnotationLowering(val context: JvmBackendContext) : C
} }
is IrSimpleFunction -> { is IrSimpleFunction -> {
val descriptor = WrappedSimpleFunctionDescriptor(oldFunction.descriptor.annotations) val descriptor = WrappedSimpleFunctionDescriptor(oldFunction.descriptor.annotations)
val modality =
if (context.state.languageVersionSettings.supportsFeature(LanguageFeature.GenerateJvmOverloadsAsFinal)) Modality.FINAL
else oldFunction.modality
IrFunctionImpl( IrFunctionImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, UNDEFINED_OFFSET, UNDEFINED_OFFSET,
JvmLoweredDeclarationOrigin.JVM_OVERLOADS_WRAPPER, JvmLoweredDeclarationOrigin.JVM_OVERLOADS_WRAPPER,
IrSimpleFunctionSymbolImpl(descriptor), IrSimpleFunctionSymbolImpl(descriptor),
oldFunction.name, oldFunction.name,
oldFunction.visibility, oldFunction.visibility,
oldFunction.modality, modality,
returnType = oldFunction.returnType, returnType = oldFunction.returnType,
isInline = oldFunction.isInline, isInline = oldFunction.isInline,
isExternal = false, isExternal = false,
@@ -151,7 +156,7 @@ private class JvmOverloadsAnnotationLowering(val context: JvmBackendContext) : C
isSuspend = oldFunction.isSuspend, isSuspend = oldFunction.isSuspend,
isExpect = false, isExpect = false,
isFakeOverride = false, isFakeOverride = false,
isOperator = false isOperator = false,
).apply { ).apply {
descriptor.bind(this) 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 com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.backend.common.output.OutputFile; import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.kotlin.codegen.CodegenTestCase; import org.jetbrains.kotlin.codegen.CodegenTestCase;
import org.jetbrains.org.objectweb.asm.*; 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 * Test correctness of written flags in class file
* *
* TESTED_OBJECT_KIND - maybe class, function or property * 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) * FLAGS - only flags which must be true (could be skipped if ABSENT is TRUE)
* ABSENT - true or false, optional (false by default) * 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_OBJECT_KIND: property
* TESTED_OBJECTS: Test, prop$delegate * TESTED_OBJECTS: Test, prop$delegate
* FLAGS: ACC_STATIC, ACC_FINAL, ACC_PRIVATE * 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 { public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
@@ -72,11 +77,11 @@ public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
assertNotNull(outputFile); assertNotNull(outputFile);
ClassReader cr = new ClassReader(outputFile.asByteArray()); 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); cr.accept(classVisitor, ClassReader.SKIP_CODE);
if (!classVisitor.isExists()) { if (!classVisitor.isExists()) {
classVisitor = getClassVisitor(testedObject.kind, testedObject.name, true); classVisitor = getClassVisitor(testedObject, true);
cr.accept(classVisitor, ClassReader.SKIP_CODE); cr.accept(classVisitor, ClassReader.SKIP_CODE);
} }
@@ -100,7 +105,7 @@ public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
TestedObject testObject = new TestedObject(); TestedObject testObject = new TestedObject();
testObject.textData = testData; testObject.textData = testData;
List<String> testedObjects = findListWithPrefixes(testData, "// TESTED_OBJECTS: "); 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); testObject.containingClass = testedObjects.get(0);
if (testedObjects.size() == 1) { if (testedObjects.size() == 1) {
testObject.name = testedObjects.get(0); testObject.name = testedObjects.get(0);
@@ -108,9 +113,13 @@ public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
else if (testedObjects.size() == 2) { else if (testedObjects.size() == 2) {
testObject.name = testedObjects.get(1); testObject.name = testedObjects.get(1);
} }
else if (testedObjects.size() == 3) {
testObject.name = testedObjects.get(1);
testObject.signature = testedObjects.get(2);
}
else { else {
throw new IllegalArgumentException( 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: "); testObject.kind = findStringWithPrefixes(testData, "// TESTED_OBJECT_KIND: ");
@@ -120,7 +129,7 @@ public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
} }
objects.add(testObject); objects.add(testObject);
} }
assertTrue("Test description not present!", !objects.isEmpty()); assertFalse("Test description not present!", objects.isEmpty());
return objects; return objects;
} }
@@ -130,27 +139,28 @@ public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
public boolean isFullContainingClassName = true; public boolean isFullContainingClassName = true;
public String kind; public String kind;
public String textData; public String textData;
public String signature;
@Override @Override
public String toString() { 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) { private static TestClassVisitor getClassVisitor(@NotNull TestedObject object, boolean allowSynthetic) {
switch (visitorKind) { switch (object.kind) {
case "class": case "class":
return new ClassFlagsVisitor(); return new ClassFlagsVisitor();
case "function": case "function":
return new FunctionFlagsVisitor(testedObjectName, allowSynthetic); return new FunctionFlagsVisitor(object.name, object.signature, allowSynthetic);
case "property": case "property":
return new PropertyFlagsVisitor(testedObjectName); return new PropertyFlagsVisitor(object.name, object.signature);
case "innerClass": case "innerClass":
return new InnerClassFlagsVisitor(testedObjectName); return new InnerClassFlagsVisitor(object.name);
default: 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 { protected static abstract class TestClassVisitor extends ClassVisitor {
@@ -170,7 +180,7 @@ public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
private static int getExpectedFlags(String text) { private static int getExpectedFlags(String text) {
int expectedAccess = 0; int expectedAccess = 0;
Class klass = Opcodes.class; Class<?> klass = Opcodes.class;
List<String> flags = findListWithPrefixes(text, "// FLAGS: "); List<String> flags = findListWithPrefixes(text, "// FLAGS: ");
for (String flag : flags) { for (String flag : flags) {
try { try {
@@ -202,16 +212,18 @@ public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
private static class FunctionFlagsVisitor extends TestClassVisitor { private static class FunctionFlagsVisitor extends TestClassVisitor {
private int access = 0; private int access = 0;
private final String funName; private final String funName;
private final String funSignature;
private final boolean allowSynthetic; private final boolean allowSynthetic;
public FunctionFlagsVisitor(String name, boolean allowSynthetic) { public FunctionFlagsVisitor(@NotNull String name, @Nullable String signature, boolean allowSynthetic) {
funName = name; this.funName = name;
this.funSignature = signature;
this.allowSynthetic = allowSynthetic; this.allowSynthetic = allowSynthetic;
} }
@Override @Override
public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) { 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; if (!allowSynthetic && (access & Opcodes.ACC_SYNTHETIC) != 0) return null;
this.access = access; this.access = access;
isExists = true; isExists = true;
@@ -228,14 +240,16 @@ public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
private static class PropertyFlagsVisitor extends TestClassVisitor { private static class PropertyFlagsVisitor extends TestClassVisitor {
private int access = 0; private int access = 0;
private final String propertyName; private final String propertyName;
private final String propertySignature;
public PropertyFlagsVisitor(String name) { public PropertyFlagsVisitor(@NotNull String name, @Nullable String signature) {
propertyName = name; this.propertyName = name;
this.propertySignature = signature;
} }
@Override @Override
public FieldVisitor visitField(int access, @NotNull String name, @NotNull String desc, String signature, Object value) { 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; this.access = access;
isExists = true; 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") @TestMetadata("compiler/testData/writeFlags/lambda")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @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") @TestMetadata("compiler/testData/writeFlags/lambda")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -124,6 +124,7 @@ enum class LanguageFeature(
DoNotGenerateThrowsForDelegatedKotlinMembers(KOTLIN_1_4), DoNotGenerateThrowsForDelegatedKotlinMembers(KOTLIN_1_4),
ProperIeee754Comparisons(KOTLIN_1_4, kind = BUG_FIX), ProperIeee754Comparisons(KOTLIN_1_4, kind = BUG_FIX),
FunctionalInterfaceConversion(KOTLIN_1_4, kind = UNSTABLE_FEATURE), FunctionalInterfaceConversion(KOTLIN_1_4, kind = UNSTABLE_FEATURE),
GenerateJvmOverloadsAsFinal(KOTLIN_1_4),
ProhibitSpreadOnSignaturePolymorphicCall(KOTLIN_1_5, kind = BUG_FIX), ProhibitSpreadOnSignaturePolymorphicCall(KOTLIN_1_5, kind = BUG_FIX),
ProhibitInvisibleAbstractMethodsInSuperclasses(KOTLIN_1_5, kind = BUG_FIX), ProhibitInvisibleAbstractMethodsInSuperclasses(KOTLIN_1_5, kind = BUG_FIX),