New J2K: Add builtIn operators conversion

This commit is contained in:
Ilya Kirillov
2019-01-17 14:37:32 +03:00
committed by Ilya Kirillov
parent fbcf2e1bcd
commit a92c5ebe61
6 changed files with 223 additions and 81 deletions
@@ -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 <reified T : JKSymbol> provideSymbol(reference: PsiReference): T {
val target = reference.resolve()
if (target != null) return provideDirectSymbol(target) as T
return (if (isAssignable<T, JKUnresolvedField>()) JKUnresolvedField(reference, this) else JKUnresolvedMethod(reference)) as T
return (if (isAssignable<T, JKUnresolvedField>()) 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 <reified T : JKSymbol> provideByFqName(classId: ClassId, context: PsiElement = symbolsByPsi.keys.first()): T {
return resolveFqName(classId, context)?.let(::provideDirectSymbol).safeAs<T>() ?: when {
isAssignable<T, JKUnresolvedMethod>() -> JKUnresolvedMethod(classId.asSingleFqName().asString().replace('/', '.'))
// isAssignable<T, JKUnresolvedField>() -> JKUnresolvedField(classId.asSingleFqName().asString().replace('/', '.'))
isAssignable<T, JKUnresolvedField>() -> JKUnresolvedField(classId.asSingleFqName().asString().replace('/', '.'), this)
else -> JKUnresolvedClassSymbol(classId.asSingleFqName().asString().replace('/', '.'))
} as T
}
internal inline fun <reified T : JKSymbol> provideByFqNameMulti(fqName: String, context: PsiElement = symbolsByPsi.keys.first()): T {
return multiResolveFqName(ClassId.fromString(fqName), context).firstOrNull()?.let(::provideDirectSymbol).safeAs<T>() ?: when {
isAssignable<T, JKUnresolvedMethod>() -> JKUnresolvedMethod(fqName.replace('/', '.'))
isAssignable<T, JKUnresolvedField>() -> JKUnresolvedField(fqName.replace('/', '.'), this)
else -> JKUnresolvedClassSymbol(fqName.replace('/', '.'))
} as T
}
internal inline fun <reified T : JKSymbol> provideByFqName(fqName: String, context: PsiElement = symbolsByPsi.keys.first()): T =
provideByFqName(ClassId.fromString(fqName), context)
@@ -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<JKExpression>) -> 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<JKExpression>) -> Boolean) =
copy(byArgumentsFilter = filter)
private infix fun Conversion.withArgumentsProvider(argumentsProvider: (JKExpressionList) -> JKExpressionList) =
copy(argumentsProvider = argumentsProvider)
private val conversions: Map<String, List<Conversion>> =
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 }
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
@@ -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) {