From a92c5ebe61bee3b9439983b3518597a95e03b25d Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Thu, 17 Jan 2019 14:37:32 +0300 Subject: [PATCH] New J2K: Add builtIn operators conversion --- .../jetbrains/kotlin/j2k/JKSymbolProvider.kt | 13 +- .../conversions/BuiltinMembersConversion.kt | 193 ++++++++++++++++++ .../CollectionOperationsConversion.kt | 32 --- .../conversions/StringMethodsConversion.kt | 47 ----- .../jetbrains/kotlin/j2k/tree/impl/symbols.kt | 12 ++ .../org/jetbrains/kotlin/j2k/tree/types.kt | 7 + 6 files changed, 223 insertions(+), 81 deletions(-) create mode 100644 j2k/newSrc/org/jetbrains/kotlin/j2k/conversions/BuiltinMembersConversion.kt delete mode 100644 j2k/newSrc/org/jetbrains/kotlin/j2k/conversions/CollectionOperationsConversion.kt delete mode 100644 j2k/newSrc/org/jetbrains/kotlin/j2k/conversions/StringMethodsConversion.kt diff --git a/j2k/newSrc/org/jetbrains/kotlin/j2k/JKSymbolProvider.kt b/j2k/newSrc/org/jetbrains/kotlin/j2k/JKSymbolProvider.kt index 574d77d6686..76313135aba 100644 --- a/j2k/newSrc/org/jetbrains/kotlin/j2k/JKSymbolProvider.kt +++ b/j2k/newSrc/org/jetbrains/kotlin/j2k/JKSymbolProvider.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.j2k import com.intellij.psi.* +import org.jetbrains.kotlin.j2k.conversions.multiResolveFqName import org.jetbrains.kotlin.j2k.conversions.resolveFqName import org.jetbrains.kotlin.j2k.tree.* import org.jetbrains.kotlin.j2k.tree.impl.* @@ -45,7 +46,7 @@ class JKSymbolProvider { internal inline fun provideSymbol(reference: PsiReference): T { val target = reference.resolve() if (target != null) return provideDirectSymbol(target) as T - return (if (isAssignable()) JKUnresolvedField(reference, this) else JKUnresolvedMethod(reference)) as T + return (if (isAssignable()) JKUnresolvedField(reference.canonicalText, this) else JKUnresolvedMethod(reference)) as T } fun provideUniverseSymbol(psi: PsiElement, jk: JKDeclaration): JKSymbol = provideUniverseSymbol(psi).also { @@ -87,11 +88,19 @@ class JKSymbolProvider { internal inline fun provideByFqName(classId: ClassId, context: PsiElement = symbolsByPsi.keys.first()): T { return resolveFqName(classId, context)?.let(::provideDirectSymbol).safeAs() ?: when { isAssignable() -> JKUnresolvedMethod(classId.asSingleFqName().asString().replace('/', '.')) -// isAssignable() -> JKUnresolvedField(classId.asSingleFqName().asString().replace('/', '.')) + isAssignable() -> JKUnresolvedField(classId.asSingleFqName().asString().replace('/', '.'), this) else -> JKUnresolvedClassSymbol(classId.asSingleFqName().asString().replace('/', '.')) } as T } + internal inline fun provideByFqNameMulti(fqName: String, context: PsiElement = symbolsByPsi.keys.first()): T { + return multiResolveFqName(ClassId.fromString(fqName), context).firstOrNull()?.let(::provideDirectSymbol).safeAs() ?: when { + isAssignable() -> JKUnresolvedMethod(fqName.replace('/', '.')) + isAssignable() -> JKUnresolvedField(fqName.replace('/', '.'), this) + else -> JKUnresolvedClassSymbol(fqName.replace('/', '.')) + } as T + } + internal inline fun provideByFqName(fqName: String, context: PsiElement = symbolsByPsi.keys.first()): T = provideByFqName(ClassId.fromString(fqName), context) diff --git a/j2k/newSrc/org/jetbrains/kotlin/j2k/conversions/BuiltinMembersConversion.kt b/j2k/newSrc/org/jetbrains/kotlin/j2k/conversions/BuiltinMembersConversion.kt new file mode 100644 index 00000000000..1265b448bc7 --- /dev/null +++ b/j2k/newSrc/org/jetbrains/kotlin/j2k/conversions/BuiltinMembersConversion.kt @@ -0,0 +1,193 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.j2k.conversions + +import org.jetbrains.kotlin.j2k.ConversionContext +import org.jetbrains.kotlin.j2k.tree.* +import org.jetbrains.kotlin.j2k.tree.impl.* + +class BuiltinMembersConversion(private val context: ConversionContext) : RecursiveApplicableConversionBase() { + override fun applyToElement(element: JKTreeElement): JKTreeElement { + if (element !is JKExpression) return recurse(element) + return recurse(element.convert() ?: element) + } + + private fun JKExpression.convert(): JKExpression? { + val selector = when (this) { + is JKQualifiedExpression -> selector + else -> this + } + + val conversion = selector.getConversion() ?: return null + val newSelector = conversion.createBuilder().build(selector) + + return when (conversion.replaceType) { + ReplaceType.REPLACE_SELECTOR -> { + if (this is JKQualifiedExpression) { + this.selector = newSelector + this + } else newSelector + } + ReplaceType.FULL_REPLACE -> newSelector + } + } + + private fun JKExpression.getConversion(): Conversion? = when (this) { + is JKMethodCallExpression -> + conversions[identifier.deepestFqName()]?.firstOrNull() { conversion -> + conversion.from is Method && conversion.byArgumentsFilter?.invoke(arguments.expressions) ?: true + } + is JKFieldAccessExpression -> + conversions[identifier.deepestFqName()]?.firstOrNull { conversion -> conversion.from is Field } + else -> null + } + + + private interface ResultBuilder { + fun build(from: JKExpression): JKExpression + } + + private inner class MethodBuilder( + private val fqName: String, + private val argumentsProvider: (JKExpressionList) -> JKExpressionList + ) : ResultBuilder { + override fun build(from: JKExpression): JKExpression = + when (from) { + is JKMethodCallExpression -> + JKKtCallExpressionImpl( + context.symbolProvider.provideByFqNameMulti(fqName), + argumentsProvider(from::arguments.detached()), + from::typeArgumentList.detached() + ) + is JKFieldAccessExpression -> + JKKtCallExpressionImpl( + context.symbolProvider.provideByFqNameMulti(fqName), + JKExpressionListImpl(), + JKTypeArgumentListImpl() + ) + else -> error("Bad conversion") + } + } + + private inner class FieldBuilder( + private val fqName: String + ) : ResultBuilder { + override fun build(from: JKExpression): JKExpression = + when (from) { + is JKMethodCallExpression -> + JKFieldAccessExpressionImpl( + context.symbolProvider.provideByFqNameMulti(fqName) + ) + is JKFieldAccessExpression -> + JKFieldAccessExpressionImpl( + context.symbolProvider.provideByFqNameMulti(fqName) + ) + else -> error("Bad conversion") + } + } + + private inner class ExtensionMethodBuilder( + private val fqName: String + ) : ResultBuilder { + override fun build(from: JKExpression): JKExpression = + when (from) { + is JKMethodCallExpression -> { + val arguments = from.arguments::expressions.detached() + JKQualifiedExpressionImpl( + arguments.first(), + JKKtQualifierImpl.DOT, + JKKtCallExpressionImpl( + context.symbolProvider.provideByFqNameMulti(fqName), + JKExpressionListImpl(arguments.drop(1)), + from::typeArgumentList.detached() + ) + ) + } + else -> error("Bad conversion") + } + } + + private fun Conversion.createBuilder(): ResultBuilder = + when (to) { + is Method -> MethodBuilder(to.fqName, argumentsProvider ?: { it }) + is Field -> FieldBuilder(to.fqName) + is ExtensionMethod -> ExtensionMethodBuilder(to.fqName) + else -> error("Bad conversion") + } + + + private enum class ReplaceType { + REPLACE_SELECTOR, FULL_REPLACE + } + + private interface Info { + val fqName: String + } + + private data class Method(override val fqName: String) : Info + private data class Field(override val fqName: String) : Info + private data class ExtensionMethod(override val fqName: String) : Info + + private data class Conversion( + val from: Info, + val to: Info, + val replaceType: ReplaceType = ReplaceType.REPLACE_SELECTOR, + val byArgumentsFilter: ((List) -> Boolean)? = null, + val argumentsProvider: ((JKExpressionList) -> JKExpressionList)? = null + ) + + private infix fun Info.convertTo(to: Info) = + Conversion(this, to) + + private infix fun Conversion.withReplaceType(replaceType: ReplaceType) = + copy(replaceType = replaceType) + + private infix fun Conversion.withByArgumentsFilter(filter: (List) -> Boolean) = + copy(byArgumentsFilter = filter) + + private infix fun Conversion.withArgumentsProvider(argumentsProvider: (JKExpressionList) -> JKExpressionList) = + copy(argumentsProvider = argumentsProvider) + + private val conversions: Map> = + listOf( + Method("java.lang.Integer.intValue") convertTo Method("kotlin.Int.toInt"),//TODO do not list all variants + + Method("java.lang.Object.getClass") convertTo Field("kotlin.jvm.javaClass"), + + Method("java.util.Map.entrySet") convertTo Field("kotlin.collections.Map.entries"), + Method("java.util.Map.keySet") convertTo Field("kotlin.collections.Map.keys"), + Method("java.util.Map.size") convertTo Field("kotlin.collections.Map.size"), + Method("java.util.Map.values") convertTo Field("kotlin.collections.Map.values"), + Method("java.util.Collection.size") convertTo Field("kotlin.collections.Collection.size"), + Method("java.util.Map.Entry.getKey") convertTo Field("kotlin.collections.Map.Entry.key"), + Method("java.util.Map.Entry.getValue") convertTo Field("kotlin.collections.Map.Entry.value"), + + Method("java.lang.Enum.name") convertTo Field("kotlin.Enum.name"), + Method("java.lang.Enum.ordinal") convertTo Field("kotlin.Enum.ordinal"), + + Method("java.lang.Throwable.getCause") convertTo Field("kotlin.Throwable.cause"), + Method("java.lang.Throwable.getMessage") convertTo Field("kotlin.Throwable.message"), + + Method("java.lang.CharSequence.length") convertTo Field("kotlin.String.length"), + Method("java.lang.CharSequence.charAt") convertTo Method("kotlin.String.get"), + Method("java.lang.String.valueOf") + convertTo ExtensionMethod("kotlin.Any.toString") + withReplaceType ReplaceType.FULL_REPLACE + withByArgumentsFilter { it -> it.isNotEmpty() && it.first().type(context.symbolProvider)?.isArrayType() == false }, + + + Method("java.util.Collections.singletonList") convertTo Method("kotlin.collections.listOf") + withReplaceType ReplaceType.FULL_REPLACE, + Method("java.util.Collections.singleton") convertTo Method("kotlin.collections.setOf") + withReplaceType ReplaceType.FULL_REPLACE, + Method("java.util.Collections.emptyList") + convertTo Method("kotlin.collections.emptyList") withReplaceType ReplaceType.FULL_REPLACE, + Method("java.util.Collections.emptySet") + convertTo Method("kotlin.collections.emptySet") withReplaceType ReplaceType.FULL_REPLACE, + Method("java.util.Collections.emptyMap") + convertTo Method("kotlin.collections.emptyMap") withReplaceType ReplaceType.FULL_REPLACE + ).groupBy { it.from.fqName } +} diff --git a/j2k/newSrc/org/jetbrains/kotlin/j2k/conversions/CollectionOperationsConversion.kt b/j2k/newSrc/org/jetbrains/kotlin/j2k/conversions/CollectionOperationsConversion.kt deleted file mode 100644 index 08379e26bfd..00000000000 --- a/j2k/newSrc/org/jetbrains/kotlin/j2k/conversions/CollectionOperationsConversion.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2010-2018 JetBrains s.r.o. 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.j2k.conversions - -import org.jetbrains.kotlin.j2k.ConversionContext -import org.jetbrains.kotlin.j2k.tree.* -import org.jetbrains.kotlin.j2k.tree.impl.JKFieldAccessExpressionImpl - - -class CollectionOperationsConversion(private val context: ConversionContext) : RecursiveApplicableConversionBase() { - override fun applyToElement(element: JKTreeElement): JKTreeElement { - if (element !is JKQualifiedExpression) return recurse(element) - val receiverType = element.receiver.type(context.symbolProvider) ?: return recurse(element) - if (!receiverType.isCollectionType(context.symbolProvider)) return recurse(element) - - convertSizeCall(element.selector)?.also { - element.selector = it - } - - return recurse(element) - } - - private fun convertSizeCall(selector: JKExpression): JKFieldAccessExpressionImpl? { - if (selector !is JKMethodCallExpression) return null - return if (selector.identifier.name == "size") { - JKFieldAccessExpressionImpl(context.symbolProvider.provideByFqName("kotlin.collections.Collection.size")) - } else null - } -} \ No newline at end of file diff --git a/j2k/newSrc/org/jetbrains/kotlin/j2k/conversions/StringMethodsConversion.kt b/j2k/newSrc/org/jetbrains/kotlin/j2k/conversions/StringMethodsConversion.kt deleted file mode 100644 index 7c562778124..00000000000 --- a/j2k/newSrc/org/jetbrains/kotlin/j2k/conversions/StringMethodsConversion.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2010-2018 JetBrains s.r.o. 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.j2k.conversions - -import org.jetbrains.kotlin.j2k.ConversionContext -import org.jetbrains.kotlin.j2k.tree.* -import org.jetbrains.kotlin.j2k.tree.impl.JKFieldAccessExpressionImpl -import org.jetbrains.kotlin.j2k.tree.impl.JKKtCallExpressionImpl - - -class StringMethodsConversion(private val context: ConversionContext) : RecursiveApplicableConversionBase() { - override fun applyToElement(element: JKTreeElement): JKTreeElement { - if (element !is JKQualifiedExpression) return recurse(element) - val receiverType = element.receiver.type(context.symbolProvider) ?: return recurse(element) - if (!receiverType.isStringType()) return recurse(element) - - convertLengthCall(element.selector)?.also { - element.selector = it - } - - convertCharAtCall(element.selector)?.also { - element.selector = it - } - - return recurse(element) - } - - private fun convertLengthCall(selector: JKExpression): JKFieldAccessExpressionImpl? { - if (selector !is JKMethodCallExpression) return null - return if (selector.identifier.name == "length") { - JKFieldAccessExpressionImpl(context.symbolProvider.provideByFqName("kotlin.String.length")) - } else null - } - - private fun convertCharAtCall(selector: JKExpression): JKExpression? { - if (selector !is JKMethodCallExpression) return null - return if (selector.identifier.name == "charAt") { - JKKtCallExpressionImpl( - context.symbolProvider.provideByFqName("kotlin.String.get"), - selector::arguments.detached() - ) - } else null - } -} \ No newline at end of file diff --git a/j2k/newSrc/org/jetbrains/kotlin/j2k/tree/impl/symbols.kt b/j2k/newSrc/org/jetbrains/kotlin/j2k/tree/impl/symbols.kt index 9e5622e4894..bb3bf95780a 100644 --- a/j2k/newSrc/org/jetbrains/kotlin/j2k/tree/impl/symbols.kt +++ b/j2k/newSrc/org/jetbrains/kotlin/j2k/tree/impl/symbols.kt @@ -264,3 +264,15 @@ class JKUnresolvedClassSymbol(override val target: String) : JKClassSymbol, JKUn override val name: String get() = target.substringAfterLast('.') } + + +fun JKSymbol.deepestFqName(): String? { + fun Any.deepestFqNameForTarget(): String? = + when (this) { + is PsiMethod -> (findDeepestSuperMethods().firstOrNull() ?: this).getKotlinFqName()?.asString() + is KtNamedFunction -> findDeepestSuperMethodsNoWrapping(this).firstOrNull()?.getKotlinFqName()?.asString() + is JKMethod -> psi()?.deepestFqNameForTarget() + else -> null + } + return target.deepestFqNameForTarget() ?: fqName +} \ No newline at end of file diff --git a/j2k/newSrc/org/jetbrains/kotlin/j2k/tree/types.kt b/j2k/newSrc/org/jetbrains/kotlin/j2k/tree/types.kt index 2448256fde8..0826c92d749 100644 --- a/j2k/newSrc/org/jetbrains/kotlin/j2k/tree/types.kt +++ b/j2k/newSrc/org/jetbrains/kotlin/j2k/tree/types.kt @@ -284,6 +284,13 @@ fun JKClassSymbol.isArrayType(): Boolean = .map { PrimitiveType.valueOf(it.jvmPrimitiveType.name).arrayTypeFqName.asString() } + KotlinBuiltIns.FQ_NAMES.array.asString() +fun JKType.isArrayType() = + when (this) { + is JKClassType -> classReference.isArrayType() + is JKJavaArrayType -> true + else -> false + } + fun JKType.arrayInnerType(): JKType? = when (this) {