From 8caf45820cffe35ad9d278b3088bd7cd6c3ba9dd Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Wed, 13 Feb 2019 12:44:25 +0300 Subject: [PATCH] New J2K: Add support of more operations on string conversion --- .../conversions/BuiltinMembersConversion.kt | 206 +++++++++++++++++- .../conversions/ImplicitCastsConversion.kt | 23 +- 2 files changed, 215 insertions(+), 14 deletions(-) diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/BuiltinMembersConversion.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/BuiltinMembersConversion.kt index f33855e2f7a..6371a707438 100644 --- a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/BuiltinMembersConversion.kt +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/BuiltinMembersConversion.kt @@ -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) -> 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) -> 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> = 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()?.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() + .selector.cast() + .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 = 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)) } diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/ImplicitCastsConversion.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/ImplicitCastsConversion.kt index 8969541a856..5ce924b7d7e 100644 --- a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/ImplicitCastsConversion.kt +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/ImplicitCastsConversion.kt @@ -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()?.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) } } \ No newline at end of file