diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/jdk2k/ReplaceJavaStaticMethodWithKotlinAnalogInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/jdk2k/ReplaceJavaStaticMethodWithKotlinAnalogInspection.kt index 7e36cb36f72..8a0fb939c91 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/jdk2k/ReplaceJavaStaticMethodWithKotlinAnalogInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/jdk2k/ReplaceJavaStaticMethodWithKotlinAnalogInspection.kt @@ -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"), diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/jdk2k/Transformation.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/jdk2k/Transformation.kt index 7d17c63d66c..da7894b1a0f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/jdk2k/Transformation.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/jdk2k/Transformation.kt @@ -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 + ) } \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/collections/toStringWithNullableReceiver.kt b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/collections/toStringWithNullableReceiver.kt new file mode 100644 index 00000000000..026ebf78c16 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/collections/toStringWithNullableReceiver.kt @@ -0,0 +1,8 @@ +// WITH_RUNTIME +// PROBLEM: none +import java.util.Arrays + +fun test() { + val a: Array<*>? = arrayOf(1) + val str = Arrays.toString(a) +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/toString/nullableIntToString.kt b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/toString/nullableIntToString.kt new file mode 100644 index 00000000000..71025295273 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/toString/nullableIntToString.kt @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + val a: Int? = 5 + val t: String = Integer.toString(a) +} diff --git a/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/toString/nullableIntToString.kt.after b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/toString/nullableIntToString.kt.after new file mode 100644 index 00000000000..71a031c5706 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/toString/nullableIntToString.kt.after @@ -0,0 +1,6 @@ +// WITH_RUNTIME + +fun foo() { + val a: Int? = 5 + val t: String = a.toString() +} diff --git a/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/toString/nullableIntToString2.kt b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/toString/nullableIntToString2.kt new file mode 100644 index 00000000000..11f55e78436 --- /dev/null +++ b/idea/testData/inspectionsLocal/replaceJavaStaticMethodWithKotlinAnalog/toString/nullableIntToString2.kt @@ -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.toString(a, 5) +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 51192d667b9..eef4415569b 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -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");