New J2K: Add builtIn operators conversion
This commit is contained in:
committed by
Ilya Kirillov
parent
fbcf2e1bcd
commit
a92c5ebe61
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user