From f3a6c541ec0278f99a9bc3036bf855bd6108ad07 Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Tue, 15 Dec 2015 12:30:16 +0300 Subject: [PATCH] J2K: Convert usages of kotlin functions from library correctly #KT-6115 Fixed --- .../kotlin/idea/j2k/J2kPostProcessings.kt | 1 + .../kotlin/j2k/ExpressionConverter.kt | 51 +++++++++++++++++-- j2k/src/org/jetbrains/kotlin/j2k/Utils.kt | 4 ++ .../org/jetbrains/kotlin/j2k/ast/Function.kt | 11 ++++ .../org/jetbrains/kotlin/j2k/ast/Imports.kt | 20 ++++++-- .../toKotlinClasses/LibraryFunctions.java | 33 ++++++++++++ .../toKotlinClasses/LibraryFunctions.kt | 18 +++++++ ...otlinConverterForWebDemoTestGenerated.java | 6 +++ ...otlinConverterSingleFileTestGenerated.java | 6 +++ 9 files changed, 141 insertions(+), 9 deletions(-) create mode 100644 j2k/testData/fileOrElement/toKotlinClasses/LibraryFunctions.java create mode 100644 j2k/testData/fileOrElement/toKotlinClasses/LibraryFunctions.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessings.kt b/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessings.kt index 5ed39bf61b9..d087ae3eb8c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessings.kt +++ b/idea/src/org/jetbrains/kotlin/idea/j2k/J2kPostProcessings.kt @@ -63,6 +63,7 @@ object J2KPostProcessingRegistrar { registerIntentionBasedProcessing(ReplaceGetOrSetIntention(), additionalChecker = ReplaceGetOrSetInspection.additionalChecker) { applyTo(it) } registerIntentionBasedProcessing(AddOperatorModifierIntention()) { applyTo(it) } registerIntentionBasedProcessing(ObjectLiteralToLambdaIntention()) { applyTo(it) } + registerIntentionBasedProcessing(AnonymousFunctionToLambdaIntention()) { applyTo(it) } registerDiagnosticBasedProcessing(Errors.USELESS_CAST) { element, diagnostic -> val expression = RemoveRightPartOfBinaryExpressionFix(element, "").invoke() diff --git a/j2k/src/org/jetbrains/kotlin/j2k/ExpressionConverter.kt b/j2k/src/org/jetbrains/kotlin/j2k/ExpressionConverter.kt index 3b9c2d987f5..7d6644f80ff 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/ExpressionConverter.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/ExpressionConverter.kt @@ -27,6 +27,7 @@ import org.jetbrains.kotlin.asJava.KtLightField import org.jetbrains.kotlin.asJava.KtLightMethod import org.jetbrains.kotlin.builtins.PrimitiveType import org.jetbrains.kotlin.j2k.ast.* +import org.jetbrains.kotlin.j2k.ast.Function import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* @@ -174,7 +175,7 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter { override fun visitClassObjectAccessExpression(expression: PsiClassObjectAccessExpression) { val operand = expression.operand val typeName = operand.type.canonicalText - val primitiveType = JvmPrimitiveType.values.firstOrNull { it.javaKeywordName == typeName } + val primitiveType = JvmPrimitiveType.values().firstOrNull { it.javaKeywordName == typeName } val wrapperTypeName = if (primitiveType != null) { primitiveType.wrapperFqName } @@ -309,6 +310,27 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter { return } } + else if (origin == null){ + val resolvedQualifier = (methodExpr.qualifier as? PsiReferenceExpression)?.resolve() + if (isFacadeClassFromLibrary(resolvedQualifier)) { + result = if (target.isKotlinExtensionFunction()) { + val qualifier = codeConverter.convertExpression(arguments.firstOrNull()) + MethodCallExpression.build(qualifier, + methodExpr.referenceName!!, + convertArguments(expression, isExtension = true), + typeArguments, + isNullable) + } + else { + MethodCallExpression.build(null, + methodExpr.referenceName!!, + convertArguments(expression), + typeArguments, + isNullable) + } + return + } + } } if (target is PsiMethod) { @@ -328,6 +350,14 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter { isNullable) } + private fun KtLightMethod.isKotlinExtensionFunction(): Boolean { + val origin = this.getOrigin() + if (origin != null) return origin.isExtensionDeclaration() + + val parameters = parameterList.parameters + return parameters.size > 0 && parameters[0].name == "\$receiver" + } + private fun convertTypeArguments(call: PsiCallExpression): List { var typeArgs = call.typeArguments.toList() @@ -371,10 +401,19 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter { val qualifier = expression.qualifier val classRef = expression.classOrAnonymousClassReference val classRefConverted = if (classRef != null) converter.convertCodeReferenceElement(classRef, hasExternalQualifier = qualifier != null) else null + + val anonymousClass = expression.anonymousClass?.let { converter.convertAnonymousClassBody(it) } + if (isFunctionType(expression.type)) { + val function = anonymousClass?.body?.members?.singleOrNull() as? Function + if (function != null) { + result = AnonymousFunction(function.returnType.toNotNullType(), function.typeParameterList, function.parameterList, function.body) + return + } + } result = NewClassExpression(classRefConverted, - convertArguments(expression), - codeConverter.convertExpression(qualifier), - expression.anonymousClass?.let { converter.convertAnonymousClassBody(it) }) + convertArguments(expression), + codeConverter.convertExpression(qualifier), + anonymousClass) } } @@ -602,7 +641,7 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter { val functionalType = expression.functionalInterfaceType val isTypeInQualifier = (qualifier as? PsiReference)?.resolve() is PsiClass - val isKotlinFunctionType = functionalType?.canonicalText?.startsWith("kotlin.jvm.functions.Function") ?: false + val isKotlinFunctionType = isFunctionType(functionalType) // method can be null in case of default constructor val method = expression.resolve() as? PsiMethod @@ -659,6 +698,8 @@ class DefaultExpressionConverter : JavaElementVisitor(), ExpressionConverter { } } + private fun isFunctionType(functionalType: PsiType?) = functionalType?.canonicalText?.startsWith("kotlin.jvm.functions.Function") ?: false + private fun convertMethodReferenceQualifier(qualifier: PsiElement): String { return when(qualifier) { is PsiExpression -> codeConverter.convertExpression(qualifier).canonicalCode() diff --git a/j2k/src/org/jetbrains/kotlin/j2k/Utils.kt b/j2k/src/org/jetbrains/kotlin/j2k/Utils.kt index f9aba441322..31c858351a5 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/Utils.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/Utils.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.j2k import com.intellij.psi.* import com.intellij.psi.util.PsiMethodUtil +import org.jetbrains.kotlin.asJava.KtLightClass import org.jetbrains.kotlin.j2k.ast.* import org.jetbrains.kotlin.types.expressions.OperatorConventions @@ -114,3 +115,6 @@ fun PsiMember.isImported(file: PsiJavaFile): Boolean { } fun PsiExpression.isNullLiteral() = this is PsiLiteralExpression && type == PsiType.NULL + +// TODO: set origin for facade classes in library +fun isFacadeClassFromLibrary(element: PsiElement?) = element is KtLightClass && element.getOrigin() == null diff --git a/j2k/src/org/jetbrains/kotlin/j2k/ast/Function.kt b/j2k/src/org/jetbrains/kotlin/j2k/ast/Function.kt index 9917ad4ed79..ede89a6cbcb 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/ast/Function.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/ast/Function.kt @@ -76,3 +76,14 @@ class Function( } } } + +class AnonymousFunction( + returnType: Type, + typeParameterList: TypeParameterList, + parameterList: ParameterList, + body: DeferredElement? +): Expression() { + private val function = Function(Identifier.Empty, Annotations.Empty, Modifiers.Empty, returnType, typeParameterList, parameterList, body, false) + + override fun generateCode(builder: CodeBuilder) = function.generateCode(builder) +} diff --git a/j2k/src/org/jetbrains/kotlin/j2k/ast/Imports.kt b/j2k/src/org/jetbrains/kotlin/j2k/ast/Imports.kt index 5b162e5097a..42629fa8f07 100644 --- a/j2k/src/org/jetbrains/kotlin/j2k/ast/Imports.kt +++ b/j2k/src/org/jetbrains/kotlin/j2k/ast/Imports.kt @@ -19,11 +19,9 @@ package org.jetbrains.kotlin.j2k.ast import com.intellij.psi.PsiImportList import com.intellij.psi.PsiImportStatementBase import com.intellij.psi.PsiJavaCodeReferenceElement +import org.jetbrains.kotlin.asJava.KtLightClass import org.jetbrains.kotlin.asJava.KtLightClassForFacade -import org.jetbrains.kotlin.j2k.CodeBuilder -import org.jetbrains.kotlin.j2k.Converter -import org.jetbrains.kotlin.j2k.append -import org.jetbrains.kotlin.j2k.quoteKeywords +import org.jetbrains.kotlin.j2k.* import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.platform.JavaToKotlinClassMap @@ -76,6 +74,20 @@ private fun Converter.filterImport(name: String, ref: PsiJavaCodeReferenceElemen if (target is KtLightClassForFacade) { return quoteKeywords(target.getFqName().parent().toString()) + ".*" } + else if (target is KtLightClass) { + if (isFacadeClassFromLibrary(target)) return null + + if (isUnderDefaultImports(target)) return null + } return name } + +val DEFAULT_IMPORTS = setOf("java.lang", + "kotlin", "kotlin.annotation", "kotlin.jvm", + "kotlin.collections", "kotlin.ranges", "kotlin.sequences", + "kotlin.text", "kotlin.io") + +fun isUnderDefaultImports(c: KtLightClass): Boolean { + return c.getFqName().parent().asString() in DEFAULT_IMPORTS +} diff --git a/j2k/testData/fileOrElement/toKotlinClasses/LibraryFunctions.java b/j2k/testData/fileOrElement/toKotlinClasses/LibraryFunctions.java new file mode 100644 index 00000000000..83da246130a --- /dev/null +++ b/j2k/testData/fileOrElement/toKotlinClasses/LibraryFunctions.java @@ -0,0 +1,33 @@ +package demo; + +import kotlin.Lazy; +import kotlin.LazyKt; +import kotlin.LazyThreadSafetyMode; +import kotlin.collections.CollectionsKt; +import kotlin.jvm.functions.Function0; +import kotlin.jvm.functions.Function1; + +import java.util.ArrayList; +import java.util.List; + +public class TestJava { + public static void main(String[] args) { + List x = new ArrayList(); + CollectionsKt.filter(x, new Function1() { + @Override + public Boolean invoke(String o) { + return o.equals("a"); + } + }); + Lazy lazy = LazyKt.lazy(LazyThreadSafetyMode.NONE, new Function0() { + @Override + public String invoke() { + return "aaa"; + } + }); + } + + public void f(Function1 result) { + result.invoke("a") + } +} diff --git a/j2k/testData/fileOrElement/toKotlinClasses/LibraryFunctions.kt b/j2k/testData/fileOrElement/toKotlinClasses/LibraryFunctions.kt new file mode 100644 index 00000000000..49550a90885 --- /dev/null +++ b/j2k/testData/fileOrElement/toKotlinClasses/LibraryFunctions.kt @@ -0,0 +1,18 @@ +package demo + +import java.util.ArrayList + +class TestJava { + + fun f(result: Function1) { + result.invoke("a") + } + + companion object { + @JvmStatic fun main(args: Array) { + val x = ArrayList() + x.filter { o -> o == "a" } + val lazy = lazy(LazyThreadSafetyMode.NONE) { "aaa" } + } + } +} diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java index 6ee4895dd60..eb4abc607c4 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java +++ b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterForWebDemoTestGenerated.java @@ -4135,6 +4135,12 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo doTest(fileName); } + @TestMetadata("LibraryFunctions.java") + public void testLibraryFunctions() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/toKotlinClasses/LibraryFunctions.java"); + doTest(fileName); + } + @TestMetadata("TypeParameterBound.java") public void testTypeParameterBound() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/toKotlinClasses/TypeParameterBound.java"); diff --git a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java index fe3eefaa6c6..db9bc76fa94 100644 --- a/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java +++ b/j2k/tests/org/jetbrains/kotlin/j2k/JavaToKotlinConverterSingleFileTestGenerated.java @@ -4135,6 +4135,12 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo doTest(fileName); } + @TestMetadata("LibraryFunctions.java") + public void testLibraryFunctions() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/toKotlinClasses/LibraryFunctions.java"); + doTest(fileName); + } + @TestMetadata("TypeParameterBound.java") public void testTypeParameterBound() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("j2k/testData/fileOrElement/toKotlinClasses/TypeParameterBound.java");