KT-38450 Add functional interface converter to the NJ2K

- Enable custom compiler options for
`AbstractNewJavaToKotlinConverterSingleFileTest`
- ^KT-38450 Fixed
This commit is contained in:
Roman Golyshev
2020-05-25 11:03:31 +03:00
committed by Roman Golyshev
parent 50fc9d3692
commit 4436142f00
24 changed files with 224 additions and 13 deletions
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.references.readWriteAccess
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.idea.util.CommentSaver
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.application.runReadAction
@@ -42,6 +43,7 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
import org.jetbrains.kotlin.resolve.sam.getSingleAbstractMethodOrNull
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.kotlin.types.KotlinType
@@ -383,15 +385,18 @@ private class ConvertGettersAndSettersToPropertyStatefulProcessing(
val realProperty = group.firstIsInstanceOrNull<RealProperty>()
val name = realGetter?.name ?: realSetter?.name!!
val superDeclarationOwner = (realGetter?.function ?: realSetter?.function)
val functionDescriptor = (realGetter?.function ?: realSetter?.function)
?.resolveToDescriptorIfAny(resolutionFacade)
if (functionDescriptor != null && isSamDescriptor(functionDescriptor)) return@mapNotNull null
val superDeclarationOwner = functionDescriptor
?.overriddenDescriptors
?.firstOrNull()
?.findPsi()
?.safeAs<KtDeclaration>()
?.containingClassOrObject
collectingState.propertyNameToSuperType[superDeclarationOwner to name]
val type = collectingState.propertyNameToSuperType[superDeclarationOwner to name]
?: calculatePropertyType(
realGetter?.function,
@@ -408,6 +413,11 @@ private class ConvertGettersAndSettersToPropertyStatefulProcessing(
}
}
private fun KtClassOrObject.isSamDescriptor(functionDescriptor: FunctionDescriptor): Boolean {
val classDescriptor = descriptor as? ClassDescriptor ?: return false
return getSingleAbstractMethodOrNull(classDescriptor) == functionDescriptor
}
private fun List<PropertyData>.filterGettersAndSetters(
klass: KtClassOrObject,
factory: KtPsiFactory
@@ -78,6 +78,8 @@ object ConversionsRunner {
LiteralConversion(context),
StaticMemberAccessConversion(context),
RemoveRedundantQualifiersForCallsConversion(context),
FunctionalInterfacesConverter(context),
FilterImportsConversion(context),
MoveInitBlocksToTheEndConversion(context),
AddElementsInfoConversion(context)
@@ -18,7 +18,8 @@ data class NewJ2kConverterContext(
val inConversionContext: (PsiElement) -> Boolean,
val importStorage: JKImportStorage,
val elementsInfoStorage: JKElementInfoStorage,
val externalCodeProcessor: NewExternalCodeProcessing
val externalCodeProcessor: NewExternalCodeProcessing,
val functionalInterfaceConversionEnabled: Boolean
) : ConverterContext {
val project: Project
get() = converter.project
@@ -24,6 +24,7 @@ import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Computable
import com.intellij.psi.*
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.j2k.*
@@ -161,7 +162,8 @@ class NewJavaToKotlinConverter(
inConversionContext,
importStorage,
JKElementInfoStorage(),
externalCodeProcessing
externalCodeProcessing,
languageVersion.supportsFeature(LanguageFeature.FunctionalInterfaceConversion)
)
ConversionsRunner.doApply(asts.withIndex().mapNotNull { (i, ast) ->
processor.updateState(i, 1, phaseDescription)
@@ -0,0 +1,31 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.nj2k.conversions
import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext
import org.jetbrains.kotlin.nj2k.annotationByFqName
import org.jetbrains.kotlin.nj2k.tree.*
private const val FUNCTIONAL_INTERFACE = "java.lang.FunctionalInterface"
internal class FunctionalInterfacesConverter(context: NewJ2kConverterContext) : RecursiveApplicableConversionBase(context) {
override fun applyToElement(element: JKTreeElement): JKTreeElement {
if (!context.functionalInterfaceConversionEnabled) return recurse(element)
if (element !is JKClass) return recurse(element)
if (element.classKind != JKClass.ClassKind.INTERFACE) return recurse(element)
if (element.inheritance.extends.isNotEmpty()) return recurse(element)
val functionalInterfaceMarker = element.annotationList.annotationByFqName(FUNCTIONAL_INTERFACE) ?: return recurse(element)
val samMethod = element.classBody.declarations.filterIsInstance<JKMethod>().singleOrNull { it.block is JKBodyStub }
if (samMethod == null || samMethod.typeParameterList.typeParameters.isNotEmpty()) return recurse(element)
element.otherModifierElements += JKOtherModifierElement(OtherModifier.FUN)
element.annotationList.annotations -= functionalInterfaceMarker
return recurse(element)
}
}
@@ -34,6 +34,7 @@ enum class OtherModifier(override val text: String) : Modifier {
SUSPEND("suspend"),
TAILREC("tailrec"),
VARARG("vararg"),
FUN("fun"),
NATIVE("native"),
STATIC("static"),
@@ -136,8 +137,8 @@ val JKModifierElement.modifier: Modifier
inline fun JKModifiersListOwner.forEachModifier(action: (JKModifierElement) -> Unit) {
safeAs<JKVisibilityOwner>()?.visibilityElement?.let(action)
safeAs<JKOtherModifiersOwner>()?.otherModifierElements?.forEach(action)
safeAs<JKModalityOwner>()?.modalityElement?.let(action)
safeAs<JKOtherModifiersOwner>()?.otherModifierElements?.forEach(action)
safeAs<JKMutabilityOwner>()?.mutabilityElement?.let(action)
}
@@ -0,0 +1,6 @@
// RUNTIME_WITH_FULL_JDK
@FunctionalInterface
public interface MyRunnable {
int getResult();
}
@@ -0,0 +1,4 @@
// RUNTIME_WITH_FULL_JDK
fun interface MyRunnable {
fun getResult(): Int
}
@@ -0,0 +1,10 @@
// RUNTIME_WITH_FULL_JDK
@FunctionalInterface
public interface MyRunnable {
int getResult();
default int getDoubleResult() {
return getResult() * 2;
}
}
@@ -0,0 +1,6 @@
// RUNTIME_WITH_FULL_JDK
fun interface MyRunnable {
fun getResult(): Int
val doubleResult: Int
get() = getResult() * 2
}
@@ -0,0 +1,5 @@
// RUNTIME_WITH_FULL_JDK
public interface MyRunnable {
int getResult();
}
@@ -0,0 +1,4 @@
// RUNTIME_WITH_FULL_JDK
interface MyRunnable {
val result: Int
}
@@ -0,0 +1,18 @@
// RUNTIME_WITH_FULL_JDK
// we intentionally do not convert interface to Kotlin fun interface
// if it inherits from some other interface, because it is hard to deal
// with default methods which were already converted to properties
// (and in kotlin fun interface cannot have abstract property)
public interface MyRunnableBase {
default int getValue() {
return 0;
}
}
@FunctionalInterface
public interface MyRunnable extends MyRunnableBase {
@Override
int getValue();
}
@@ -0,0 +1,14 @@
// RUNTIME_WITH_FULL_JDK
// we intentionally do not convert interface to Kotlin fun interface
// if it inherits from some other interface, because it is hard to deal
// with default methods which were already converted to properties
// (and in kotlin fun interface cannot have abstract property)
interface MyRunnableBase {
val value: Int
get() = 0
}
@FunctionalInterface
interface MyRunnable : MyRunnableBase {
abstract override val value: Int
}
@@ -0,0 +1,6 @@
// RUNTIME_WITH_FULL_JDK
@FunctionalInterface
public interface MyRunnable {
void run();
}
@@ -0,0 +1,4 @@
// RUNTIME_WITH_FULL_JDK
fun interface MyRunnable {
fun run()
}
@@ -0,0 +1,3 @@
public interface MyRunnable {
void run();
}
@@ -0,0 +1,3 @@
interface MyRunnable {
fun run()
}
@@ -0,0 +1,7 @@
// RUNTIME_WITH_FULL_JDK
// COMPILER_ARGUMENTS: -XXLanguage:-FunctionalInterfaceConversion
@FunctionalInterface
public interface MyRunnable {
void run();
}
@@ -0,0 +1,6 @@
// RUNTIME_WITH_FULL_JDK
// COMPILER_ARGUMENTS: -XXLanguage:-FunctionalInterfaceConversion
@FunctionalInterface
interface MyRunnable {
fun run()
}
@@ -0,0 +1,6 @@
// RUNTIME_WITH_FULL_JDK
@FunctionalInterface
public interface MyRunnable {
<T> void process(T t);
}
@@ -0,0 +1,5 @@
// RUNTIME_WITH_FULL_JDK
@FunctionalInterface
interface MyRunnable {
fun <T> process(t: T)
}
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.nj2k
import com.intellij.openapi.project.Project
import com.intellij.psi.codeStyle.JavaCodeStyleSettings
import org.jetbrains.kotlin.idea.j2k.IdeaJavaToKotlinServices
import org.jetbrains.kotlin.idea.test.withCustomCompilerOptions
import org.jetbrains.kotlin.j2k.AbstractJavaToKotlinConverterSingleFileTest
import org.jetbrains.kotlin.j2k.ConverterSettings
import org.jetbrains.kotlin.nj2k.postProcessing.NewJ2kPostProcessor
@@ -16,15 +17,18 @@ import java.io.File
abstract class AbstractNewJavaToKotlinConverterSingleFileTest : AbstractJavaToKotlinConverterSingleFileTest() {
override fun doTest(javaPath: String) {
val directory = File(javaPath).parentFile
val expectedFileName = "${File(javaPath).nameWithoutExtension}.external"
val expectedFiles = directory.listFiles { _, name ->
name == "$expectedFileName.kt" || name == "$expectedFileName.java"
}!!.filterNotNull()
for (expectedFile in expectedFiles) {
addFile(expectedFile, dirName = null)
val javaFile = File(javaPath)
withCustomCompilerOptions(javaFile.readText(), project, module) {
val directory = javaFile.parentFile
val expectedFileName = "${javaFile.nameWithoutExtension}.external"
val expectedFiles = directory.listFiles { _, name ->
name == "$expectedFileName.kt" || name == "$expectedFileName.java"
}!!.filterNotNull()
for (expectedFile in expectedFiles) {
addFile(expectedFile, dirName = null)
}
super.doTest(javaPath)
}
super.doTest(javaPath)
}
override fun compareResults(expectedFile: File, actual: String) {
@@ -2583,6 +2583,59 @@ public class NewJavaToKotlinConverterSingleFileTestGenerated extends AbstractNew
}
}
@TestMetadata("nj2k/testData/newJ2k/functionalInterfaces")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class FunctionalInterfaces extends AbstractNewJavaToKotlinConverterSingleFileTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("AbstractGetter1.java")
public void testAbstractGetter1() throws Exception {
runTest("nj2k/testData/newJ2k/functionalInterfaces/AbstractGetter1.java");
}
@TestMetadata("AbstractGetter2.java")
public void testAbstractGetter2() throws Exception {
runTest("nj2k/testData/newJ2k/functionalInterfaces/AbstractGetter2.java");
}
@TestMetadata("AbstractGetterNoFunctionalInterfaceAnnotation.java")
public void testAbstractGetterNoFunctionalInterfaceAnnotation() throws Exception {
runTest("nj2k/testData/newJ2k/functionalInterfaces/AbstractGetterNoFunctionalInterfaceAnnotation.java");
}
public void testAllFilesPresentInFunctionalInterfaces() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("nj2k/testData/newJ2k/functionalInterfaces"), Pattern.compile("^([^\\.]+)\\.java$"), null, true);
}
@TestMetadata("InterfacesHierarchy.java")
public void testInterfacesHierarchy() throws Exception {
runTest("nj2k/testData/newJ2k/functionalInterfaces/InterfacesHierarchy.java");
}
@TestMetadata("MyRunnable.java")
public void testMyRunnable() throws Exception {
runTest("nj2k/testData/newJ2k/functionalInterfaces/MyRunnable.java");
}
@TestMetadata("NoFunctionalInterfaceAnnotation.java")
public void testNoFunctionalInterfaceAnnotation() throws Exception {
runTest("nj2k/testData/newJ2k/functionalInterfaces/NoFunctionalInterfaceAnnotation.java");
}
@TestMetadata("NoFunctionalInterfaceConversionEnabled.java")
public void testNoFunctionalInterfaceConversionEnabled() throws Exception {
runTest("nj2k/testData/newJ2k/functionalInterfaces/NoFunctionalInterfaceConversionEnabled.java");
}
@TestMetadata("SamMethodWithGenerics.java")
public void testSamMethodWithGenerics() throws Exception {
runTest("nj2k/testData/newJ2k/functionalInterfaces/SamMethodWithGenerics.java");
}
}
@TestMetadata("nj2k/testData/newJ2k/identifier")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)