J2K: Convert usages of kotlin functions from library correctly

#KT-6115 Fixed
This commit is contained in:
Natalia Ukhorskaya
2015-12-15 12:30:16 +03:00
parent 693e158759
commit f3a6c541ec
9 changed files with 141 additions and 9 deletions
@@ -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<KtBinaryExpressionWithTypeRHS>(Errors.USELESS_CAST) { element, diagnostic ->
val expression = RemoveRightPartOfBinaryExpressionFix(element, "").invoke()
@@ -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<Type> {
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()
@@ -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
@@ -76,3 +76,14 @@ class Function(
}
}
}
class AnonymousFunction(
returnType: Type,
typeParameterList: TypeParameterList,
parameterList: ParameterList,
body: DeferredElement<Block>?
): Expression() {
private val function = Function(Identifier.Empty, Annotations.Empty, Modifiers.Empty, returnType, typeParameterList, parameterList, body, false)
override fun generateCode(builder: CodeBuilder) = function.generateCode(builder)
}
@@ -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
}
@@ -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<String> x = new ArrayList<String>();
CollectionsKt.filter(x, new Function1<String, Boolean>() {
@Override
public Boolean invoke(String o) {
return o.equals("a");
}
});
Lazy<String> lazy = LazyKt.lazy(LazyThreadSafetyMode.NONE, new Function0<String>() {
@Override
public String invoke() {
return "aaa";
}
});
}
public void f(Function1<String, Unit> result) {
result.invoke("a")
}
}
@@ -0,0 +1,18 @@
package demo
import java.util.ArrayList
class TestJava {
fun f(result: Function1<String, Unit>) {
result.invoke("a")
}
companion object {
@JvmStatic fun main(args: Array<String>) {
val x = ArrayList<String>()
x.filter { o -> o == "a" }
val lazy = lazy(LazyThreadSafetyMode.NONE) { "aaa" }
}
}
}
@@ -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");
@@ -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");