New J2K: Add support of more operations on string conversion

This commit is contained in:
Ilya Kirillov
2019-02-13 12:44:25 +03:00
committed by Ilya Kirillov
parent e39fc53f25
commit 8caf45820c
2 changed files with 215 additions and 14 deletions
@@ -5,10 +5,12 @@
package org.jetbrains.kotlin.nj2k.conversions
import org.jetbrains.kotlin.nj2k.ConversionContext
import org.jetbrains.kotlin.nj2k.parenthesizeIfBinaryExpression
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.nj2k.*
import org.jetbrains.kotlin.nj2k.tree.*
import org.jetbrains.kotlin.nj2k.tree.impl.*
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class BuiltinMembersConversion(private val context: ConversionContext) : RecursiveApplicableConversionBase() {
override fun applyToElement(element: JKTreeElement): JKTreeElement {
@@ -113,11 +115,18 @@ class BuiltinMembersConversion(private val context: ConversionContext) : Recursi
}
}
private inner class CustomExpressionBuilder(
val builder: (JKExpression) -> JKExpression
) : ResultBuilder {
override fun build(from: JKExpression): JKExpression = builder(from)
}
private fun Conversion.createBuilder(): ResultBuilder =
when (to) {
is Method -> MethodBuilder(to.fqName, argumentsProvider ?: { it })
is Field -> FieldBuilder(to.fqName)
is ExtensionMethod -> ExtensionMethodBuilder(to.fqName)
is CustomExpression -> CustomExpressionBuilder(to.expressionBuilder)
else -> error("Bad conversion")
}
@@ -126,23 +135,27 @@ class BuiltinMembersConversion(private val context: ConversionContext) : Recursi
REPLACE_SELECTOR, FULL_REPLACE
}
private interface Info {
private interface Info
private interface SymbolInfo : 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 Method(override val fqName: String) : SymbolInfo
private data class Field(override val fqName: String) : SymbolInfo
private data class ExtensionMethod(override val fqName: String) : SymbolInfo
private data class CustomExpression(val expressionBuilder: (JKExpression) -> JKExpression) : Info
private data class Conversion(
val from: Info,
val from: SymbolInfo,
val to: Info,
val replaceType: ReplaceType = ReplaceType.REPLACE_SELECTOR,
val byArgumentsFilter: ((List<JKExpression>) -> Boolean)? = null,
val argumentsProvider: ((JKExpressionList) -> JKExpressionList)? = null
val argumentsProvider: ((JKArgumentList) -> JKArgumentList)? = null,
val actionAfter: ((JKExpression) -> JKExpression)? = null
)
private infix fun Info.convertTo(to: Info) =
private infix fun SymbolInfo.convertTo(to: Info) =
Conversion(this, to)
private infix fun Conversion.withReplaceType(replaceType: ReplaceType) =
@@ -151,9 +164,12 @@ class BuiltinMembersConversion(private val context: ConversionContext) : Recursi
private infix fun Conversion.withByArgumentsFilter(filter: (List<JKExpression>) -> Boolean) =
copy(byArgumentsFilter = filter)
private infix fun Conversion.withArgumentsProvider(argumentsProvider: (JKExpressionList) -> JKExpressionList) =
private infix fun Conversion.withArgumentsProvider(argumentsProvider: (JKArgumentList) -> JKArgumentList) =
copy(argumentsProvider = argumentsProvider)
private infix fun Conversion.andAfter(actionAfter: (JKExpression) -> JKExpression) =
copy(actionAfter = actionAfter)
private val conversions: Map<String, List<Conversion>> =
listOf(
Method("java.lang.Integer.intValue") convertTo Method("kotlin.Int.toInt"),//TODO do not list all variants
@@ -166,6 +182,17 @@ class BuiltinMembersConversion(private val context: ConversionContext) : Recursi
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.Collection.remove") convertTo Method("kotlin.collections.MutableCollection.remove"),
Method("java.util.Collection.toArray")
convertTo Method("kotlin.collections.toTypedArray")
withByArgumentsFilter { it.isEmpty() },
Method("java.util.Collection.toArray")
convertTo Method("kotlin.collections.toTypedArray")
withByArgumentsFilter {
it.singleOrNull()?.let { parameter ->
parameter.safeAs<JKMethodCallExpression>()?.identifier?.fqName == "kotlin.arrayOfNulls"
} == true
} withArgumentsProvider { JKArgumentListImpl() },
Method("java.util.List.remove") convertTo Method("kotlin.collections.MutableCollection.removeAt"),
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"),
@@ -178,11 +205,148 @@ class BuiltinMembersConversion(private val context: ConversionContext) : Recursi
Method("java.lang.CharSequence.length") convertTo Field("kotlin.String.length"),
Method("java.lang.CharSequence.charAt") convertTo Method("kotlin.String.get"),
Method("java.lang.String.indexOf") convertTo Method("kotlin.text.indexOf"),
Method("java.lang.String.lastIndexOf") convertTo Method("kotlin.text.lastIndexOf"),
Method("java.lang.String.getBytes") convertTo Method("kotlin.text.toByteArray"),
Method("java.lang.String.valueOf")
convertTo ExtensionMethod("kotlin.Any.toString")
withReplaceType ReplaceType.FULL_REPLACE
withByArgumentsFilter { it.isNotEmpty() && it.first().type(context.symbolProvider)?.isArrayType() == false },
Method("java.lang.String.valueOf")
convertTo Method("kotlin.String")
withReplaceType ReplaceType.FULL_REPLACE
withByArgumentsFilter { it.isNotEmpty() && it.first().type(context.symbolProvider)?.isArrayType() == true },
Method("java.lang.String.copyValueOf")
convertTo Method("kotlin.String")
withReplaceType ReplaceType.FULL_REPLACE
withByArgumentsFilter { it.isNotEmpty() && it.first().type(context.symbolProvider)?.isArrayType() == true },
Method("java.lang.String.replaceAll")
convertTo Method("kotlin.text.replace")
withArgumentsProvider { arguments ->
val detachedArguments = arguments::arguments.detached()
val first =
detachedArguments.first()::value.detached().callOn(
context.symbolProvider.provideByFqName(
"kotlin.text.toRegex",
multiResolve = true
)
)
JKArgumentListImpl(listOf(JKArgumentImpl(first)) + detachedArguments.drop(1))
},
Method("java.lang.String.replaceFirst")
convertTo Method("kotlin.text.replaceFirst")
withArgumentsProvider { arguments ->
val detachedArguments = arguments::arguments.detached()
val first =
detachedArguments.first()::value.detached().callOn(
context.symbolProvider.provideByFqName(
"kotlin.text.toRegex",
multiResolve = true
)
)
JKArgumentListImpl(listOf(JKArgumentImpl(first)) + detachedArguments.drop(1))
},
Method("java.lang.String.equalsIgnoreCase")
convertTo Method("kotlin.text.equals")
withArgumentsProvider { arguments ->
JKArgumentListImpl(
arguments::arguments.detached() + JKNamedArgumentImpl(
JKBooleanLiteral(true),
JKNameIdentifierImpl("ignoreCase")
)
)
},
Method("java.lang.String.compareToIgnoreCase")
convertTo Method("kotlin.text.compareTo")
withArgumentsProvider { arguments ->
JKArgumentListImpl(
arguments::arguments.detached() + JKNamedArgumentImpl(
JKBooleanLiteral(true),
JKNameIdentifierImpl("ignoreCase")
)
)
},
Method("java.lang.String.split")
convertTo Method("kotlin.text.split")
withByArgumentsFilter { it.size == 2 }
andAfter { expression ->
val arguments =
expression.cast<JKQualifiedExpression>()
.selector.cast<JKMethodCallExpression>()
.arguments
val limitArgument = arguments.arguments[1].value
val limit = limitArgument.asLiteralTextWithPrefix()?.toIntOrNull()
when {
limit != null -> {
if (limit > 0) expression
else expression
.also {
arguments.arguments = arguments.arguments.dropLast(1)
}.let {
it.callOn(
context.symbolProvider.provideByFqName("kotlin.collections.dropLastWhile", multiResolve = true),
JKLambdaExpressionImpl(
JKFieldAccessExpressionImpl(
JKUnresolvedField(//TODO replace with `it` parameter
"it",
context.symbolProvider
)
).callOn(
context.symbolProvider.provideByFqName(
"kotlin.text.isEmpty",
multiResolve = true
)
).asStatement(),
emptyList()
)
)
}
}
else -> expression.also {
val lastArgument = arguments.arguments.last().value.copyTreeAndDetach()
.callOn(
context.symbolProvider.provideByFqName("kotlin.ranges.coerceAtLeast"),
JKKtLiteralExpressionImpl("0", JKLiteralExpression.LiteralType.INT)
)
arguments.arguments = arguments.arguments.dropLast(1) + JKArgumentImpl(lastArgument)
}
}.castToTypedArray()
},
Method("java.lang.String.split")
convertTo Method("kotlin.text.split")
andAfter { it.castToTypedArray() },
Method("java.lang.String.trim")
convertTo Method("kotlin.text.trim")
withArgumentsProvider {
JKArgumentListImpl(
JKLambdaExpressionImpl(
JKExpressionStatementImpl(
kotlinBinaryExpression(
JKFieldAccessExpressionImpl(
JKUnresolvedField(//TODO replace with `it` parameter
"it",
context.symbolProvider
)
),
JKKtLiteralExpressionImpl("' '", JKLiteralExpression.LiteralType.CHAR),
KtTokens.LTEQ,
context.symbolProvider
)
),
emptyList()
)
)
},
Method("java.util.Collections.singletonList") convertTo Method("kotlin.collections.listOf")
withReplaceType ReplaceType.FULL_REPLACE,
@@ -195,4 +359,26 @@ class BuiltinMembersConversion(private val context: ConversionContext) : Recursi
Method("java.util.Collections.emptyMap")
convertTo Method("kotlin.collections.emptyMap") withReplaceType ReplaceType.FULL_REPLACE
).groupBy { it.from.fqName }
private fun JKExpression.callOn(symbol: JKMethodSymbol, arguments: List<JKArgument> = emptyList()) =
JKQualifiedExpressionImpl(
this,
JKKtQualifierImpl.DOT,
JKKtCallExpressionImpl(
symbol,
JKArgumentListImpl(arguments),
JKTypeArgumentListImpl()
)
)
private fun JKExpression.callOn(symbol: JKMethodSymbol, vararg arguments: JKArgument) =
callOn(symbol, arguments.toList())
private fun JKExpression.callOn(symbol: JKMethodSymbol, vararg arguments: JKExpression) =
callOn(symbol, arguments.map { JKArgumentImpl(it) })
private fun JKExpression.castToTypedArray() =
callOn(context.symbolProvider.provideByFqName("kotlin.collections.toTypedArray", multiResolve = true))
}
@@ -46,8 +46,7 @@ class ImplicitCastsConversion(private val context: ConversionContext) : Recursiv
} else this
}
fun JKBinaryExpression.convertComparationExpression(): JKBinaryExpression {
if (!operator.isComparationOperator()) return this
fun JKBinaryExpression.convertBinaryOperationWithChar(): JKBinaryExpression {
val leftType = left.type(context.symbolProvider)?.asPrimitiveType() ?: return this
val rightType = right.type(context.symbolProvider)?.asPrimitiveType() ?: return this
@@ -67,7 +66,6 @@ class ImplicitCastsConversion(private val context: ConversionContext) : Recursiv
)
}
println()
return when {
leftType.jvmPrimitiveType == rightType.jvmPrimitiveType -> this
@@ -80,7 +78,7 @@ class ImplicitCastsConversion(private val context: ConversionContext) : Recursiv
}
}
return binaryExpression.convertComparationExpression().addBangBang()
return binaryExpression.convertBinaryOperationWithChar().addBangBang()
}
private fun convertVariable(variable: JKVariable) {
@@ -136,6 +134,22 @@ class ImplicitCastsConversion(private val context: ConversionContext) : Recursiv
return null
}
private fun JKExpression.castStringToRegex(toType: JKType): JKExpression? {
if (toType.safeAs<JKClassType>()?.classReference?.fqName != "java.util.regex.Pattern") return null
val expressionType = type(context.symbolProvider) ?: return null
if (!expressionType.isStringType()) return null
return JKQualifiedExpressionImpl(
copyTreeAndDetach().parenthesizeIfBinaryExpression(),
JKKtQualifierImpl.DOT,
JKKtCallExpressionImpl(
context.symbolProvider.provideByFqName("kotlin.text.toRegex", multiResolve = true),
JKArgumentListImpl(),
JKTypeArgumentListImpl()
)
)
}
private fun JKExpression.castToAsPrimitiveTypes(toType: JKType, strict: Boolean): JKExpression? {
if (this is JKPrefixExpression
&& (operator.token.text == "+" || operator.token.text == "-")
@@ -181,6 +195,7 @@ class ImplicitCastsConversion(private val context: ConversionContext) : Recursiv
val expressionType = type(context.symbolProvider)
if (expressionType == toType) return null
castToAsPrimitiveTypes(toType, strict)?.also { return it }
castStringToRegex(toType)?.also { return it }
return addBangBang(toType)
}
}