ReplaceJavaStaticMethodWithKotlinAnalogInspection: introduce Transformation for extension with non-null receiver

This commit is contained in:
Dmitry Gridin
2019-07-09 17:16:11 +03:00
parent d47a7234bb
commit 8b8858b223
7 changed files with 106 additions and 26 deletions
@@ -29,8 +29,9 @@ class ReplaceJavaStaticMethodWithKotlinAnalogInspection : AbstractKotlinInspecti
?.filter { it.filter(call) && it.transformation.isApplicable(call) }
?.takeIf { it.isNotEmpty() }
?.let { list ->
val callDescriptor = call.getResolvedCall(call.analyze(BodyResolveMode.PARTIAL)) ?: return
list.filter { callDescriptor.isCalling(FqName(it.javaMethodFqName)) }
val context = call.analyze(BodyResolveMode.PARTIAL)
val callDescriptor = call.getResolvedCall(context) ?: return
list.filter { callDescriptor.isCalling(FqName(it.javaMethodFqName)) && it.transformation.isApplicable(call, context) }
}
?.takeIf { it.isNotEmpty() }
?.map(::ReplaceWithKotlinAnalogFunction)
@@ -66,13 +67,21 @@ class ReplaceJavaStaticMethodWithKotlinAnalogInspection : AbstractKotlinInspecti
"Float" to "Float"
).flatMap { (javaPrimitive, kotlinPrimitive) ->
listOf(
Replacement("java.lang.$javaPrimitive.toString", "kotlin.text.toString", ToExtensionFunction) {
Replacement("java.lang.$javaPrimitive.toString", "kotlin.text.toString", ToExtensionFunctionWithNonNullableReceiver) {
it.valueArguments.size == 2
},
Replacement("java.lang.$javaPrimitive.toString", "kotlin.primitives.$kotlinPrimitive.toString", ToExtensionFunction) {
Replacement(
"java.lang.$javaPrimitive.toString",
"kotlin.primitives.$kotlinPrimitive.toString",
ToExtensionFunctionWithNullableReceiver
) {
it.valueArguments.size == 1
},
Replacement("java.lang.$javaPrimitive.compare", "kotlin.primitives.$kotlinPrimitive.compareTo", ToExtensionFunction)
Replacement(
"java.lang.$javaPrimitive.compare",
"kotlin.primitives.$kotlinPrimitive.compareTo",
ToExtensionFunctionWithNonNullableReceiver
)
)
}
@@ -100,38 +109,38 @@ class ReplaceJavaStaticMethodWithKotlinAnalogInspection : AbstractKotlinInspecti
Replacement("java.lang.Math.expm1", "kotlin.math.expm1"),
Replacement("java.lang.Math.floor", "kotlin.math.floor"),
Replacement("java.lang.Math.hypot", "kotlin.math.hypot"),
Replacement("java.lang.Math.IEEEremainder", "kotlin.math.IEEErem", ToExtensionFunction),
Replacement("java.lang.Math.IEEEremainder", "kotlin.math.IEEErem", ToExtensionFunctionWithNonNullableReceiver),
Replacement("java.lang.Math.log", "kotlin.math.ln"),
Replacement("java.lang.Math.log1p", "kotlin.math.ln1p"),
Replacement("java.lang.Math.log10", "kotlin.math.log10"),
Replacement("java.lang.Math.max", "kotlin.math.max"),
Replacement("java.lang.Math.max", "kotlin.ranges.coerceAtLeast", ToExtensionFunction),
Replacement("java.lang.Math.max", "kotlin.ranges.coerceAtLeast", ToExtensionFunctionWithNonNullableReceiver),
Replacement("java.lang.Math.min", "kotlin.math.min"),
Replacement("java.lang.Math.min", "kotlin.ranges.coerceAtMost", ToExtensionFunction),
Replacement("java.lang.Math.nextDown", "kotlin.math.nextDown", ToExtensionFunction),
Replacement("java.lang.Math.nextAfter", "kotlin.math.nextTowards", ToExtensionFunction),
Replacement("java.lang.Math.nextUp", "kotlin.math.nextUp", ToExtensionFunction),
Replacement("java.lang.Math.pow", "kotlin.math.pow", ToExtensionFunction),
Replacement("java.lang.Math.min", "kotlin.ranges.coerceAtMost", ToExtensionFunctionWithNonNullableReceiver),
Replacement("java.lang.Math.nextDown", "kotlin.math.nextDown", ToExtensionFunctionWithNonNullableReceiver),
Replacement("java.lang.Math.nextAfter", "kotlin.math.nextTowards", ToExtensionFunctionWithNonNullableReceiver),
Replacement("java.lang.Math.nextUp", "kotlin.math.nextUp", ToExtensionFunctionWithNonNullableReceiver),
Replacement("java.lang.Math.pow", "kotlin.math.pow", ToExtensionFunctionWithNonNullableReceiver),
Replacement("java.lang.Math.rint", "kotlin.math.round"),
Replacement("java.lang.Math.round", "kotlin.math.roundToLong", ToExtensionFunction),
Replacement("java.lang.Math.round", "kotlin.math.roundToInt", ToExtensionFunction),
Replacement("java.lang.Math.round", "kotlin.math.roundToLong", ToExtensionFunctionWithNonNullableReceiver),
Replacement("java.lang.Math.round", "kotlin.math.roundToInt", ToExtensionFunctionWithNonNullableReceiver),
Replacement("java.lang.Math.signum", "kotlin.math.sign"),
Replacement("java.lang.Math.sin", "kotlin.math.sin"),
Replacement("java.lang.Math.sinh", "kotlin.math.sinh"),
Replacement("java.lang.Math.sqrt", "kotlin.math.sqrt"),
Replacement("java.lang.Math.tan", "kotlin.math.tan"),
Replacement("java.lang.Math.tanh", "kotlin.math.tanh"),
Replacement("java.lang.Math.copySign", "kotlin.math.withSign", ToExtensionFunction)
Replacement("java.lang.Math.copySign", "kotlin.math.withSign", ToExtensionFunctionWithNonNullableReceiver)
)
private val JAVA_COLLECTIONS = listOf(
Replacement("java.util.Arrays.copyOf", "kotlin.collections.copyOf", ToExtensionFunction) {
Replacement("java.util.Arrays.copyOf", "kotlin.collections.copyOf", ToExtensionFunctionWithNonNullableReceiver) {
it.valueArguments.size == 2
},
Replacement("java.util.Arrays.copyOfRange", "kotlin.collections.copyOfRange", ToExtensionFunction),
Replacement("java.util.Arrays.copyOfRange", "kotlin.collections.copyOfRange", ToExtensionFunctionWithNonNullableReceiver),
// Replacement("java.util.Arrays.binarySearch", "kotlin.collections.binarySearch", ToExtensionFunction),
Replacement("java.util.Arrays.equals", "kotlin.collections.contentEquals", ToExtensionFunction),
Replacement("java.util.Arrays.deepEquals", "kotlin.collections.contentDeepEquals", ToExtensionFunction),
Replacement("java.util.Arrays.equals", "kotlin.collections.contentEquals", ToExtensionFunctionWithNonNullableReceiver),
Replacement("java.util.Arrays.deepEquals", "kotlin.collections.contentDeepEquals", ToExtensionFunctionWithNonNullableReceiver),
// Replacement("java.util.Arrays.fill", "kotlin.collections.fill", ToExtensionFunction),
// Replacement("java.util.Arrays.sort", "kotlin.collections.sort", ToExtensionFunction) {
// it.valueArguments.size == 3
@@ -139,10 +148,18 @@ class ReplaceJavaStaticMethodWithKotlinAnalogInspection : AbstractKotlinInspecti
// Replacement("java.util.Arrays.sort", "kotlin.collections.sortWith", ToExtensionFunction) {
// it.valueArguments.size != 3
// },
Replacement("java.util.Arrays.deepHashCode", "kotlin.collections.contentDeepHashCode", ToExtensionFunction),
Replacement("java.util.Arrays.hashCode", "kotlin.collections.contentHashCode", ToExtensionFunction),
Replacement("java.util.Arrays.deepToString", "kotlin.collections.contentDeepToString", ToExtensionFunction),
Replacement("java.util.Arrays.toString", "kotlin.collections.contentToString", ToExtensionFunction),
Replacement(
"java.util.Arrays.deepHashCode",
"kotlin.collections.contentDeepHashCode",
ToExtensionFunctionWithNonNullableReceiver
),
Replacement("java.util.Arrays.hashCode", "kotlin.collections.contentHashCode", ToExtensionFunctionWithNonNullableReceiver),
Replacement(
"java.util.Arrays.deepToString",
"kotlin.collections.contentDeepToString",
ToExtensionFunctionWithNonNullableReceiver
),
Replacement("java.util.Arrays.toString", "kotlin.collections.contentToString", ToExtensionFunctionWithNonNullableReceiver),
Replacement("java.util.Arrays.asList", "kotlin.collections.listOf"),
Replacement("java.util.Arrays.asList", "kotlin.collections.mutableListOf"),
Replacement("java.util.Set.of", "kotlin.collections.setOf"),
@@ -14,10 +14,14 @@ import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtOperationExpression
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
import org.jetbrains.kotlin.types.isNullable
interface Transformation {
operator fun invoke(callExpression: KtCallExpression, replacement: Replacement)
fun isApplicable(call: KtCallExpression): Boolean = true
fun isApplicable(callExpression: KtCallExpression): Boolean = true
fun isApplicable(callExpression: KtCallExpression, context: BindingContext): Boolean = true
}
object WithoutAdditionalTransformation : Transformation {
@@ -31,7 +35,7 @@ object WithoutAdditionalTransformation : Transformation {
}
}
object ToExtensionFunction : Transformation {
object ToExtensionFunctionWithNonNullableReceiver : Transformation {
override fun invoke(callExpression: KtCallExpression, replacement: Replacement) {
val file = callExpression.containingKtFile
val psiFactory = KtPsiFactory(callExpression)
@@ -49,5 +53,22 @@ object ToExtensionFunction : Transformation {
}
}
override fun isApplicable(call: KtCallExpression): Boolean = call.valueArguments.isNotEmpty()
override fun isApplicable(callExpression: KtCallExpression): Boolean = callExpression.valueArguments.isNotEmpty()
override fun isApplicable(callExpression: KtCallExpression, context: BindingContext): Boolean = callExpression
.valueArguments.firstOrNull()
?.getArgumentExpression()
?.getType(context)
?.isNullable() == false
}
object ToExtensionFunctionWithNullableReceiver : Transformation {
override fun invoke(callExpression: KtCallExpression, replacement: Replacement) = ToExtensionFunctionWithNonNullableReceiver(
callExpression,
replacement
)
override fun isApplicable(callExpression: KtCallExpression): Boolean = ToExtensionFunctionWithNonNullableReceiver.isApplicable(
callExpression
)
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
// PROBLEM: none
import java.util.Arrays
fun test() {
val a: Array<*>? = arrayOf(1)
val str = Arrays.<caret>toString(a)
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo() {
val a: Int? = 5
val t: String = Integer.<caret>toString(a)
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun foo() {
val a: Int? = 5
val t: String = a.toString()
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
// PROBLEM: none
// ERROR: Type mismatch: inferred type is Int? but Int was expected
fun foo() {
val a: Int? = 5
val t: String = Integer.<caret>toString(a, 5)
}
@@ -9140,6 +9140,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
public void testToString() throws Exception {
runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/collections/toString.kt");
}
@TestMetadata("toStringWithNullableReceiver.kt")
public void testToStringWithNullableReceiver() throws Exception {
runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/collections/toStringWithNullableReceiver.kt");
}
}
@TestMetadata("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/compare")
@@ -9491,6 +9496,16 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/toString/longToString2.kt");
}
@TestMetadata("nullableIntToString.kt")
public void testNullableIntToString() throws Exception {
runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/toString/nullableIntToString.kt");
}
@TestMetadata("nullableIntToString2.kt")
public void testNullableIntToString2() throws Exception {
runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/toString/nullableIntToString2.kt");
}
@TestMetadata("replaceIntToString.kt")
public void testReplaceIntToString() throws Exception {
runTest("idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/toString/replaceIntToString.kt");