diff --git a/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/HasPlatformTypeInspection.kt b/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/HasPlatformTypeInspection.kt index df529250fe8a..0dae23a5e3f1 100644 --- a/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/HasPlatformTypeInspection.kt +++ b/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/HasPlatformTypeInspection.kt @@ -1,6 +1,7 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.kotlin.idea.k2.codeinsight.inspections +import com.intellij.codeInsight.intention.IntentionAction import com.intellij.codeInspection.IntentionWrapper import com.intellij.codeInspection.ProblemsHolder import com.intellij.codeInspection.options.OptPane.checkbox @@ -39,11 +40,10 @@ class HasPlatformTypeInspection( } } - context(KaSession) - private fun KaType.isFlexibleRecursive(): Boolean { - if (hasFlexibleNullability) return true - val classType = this as? KaClassType ?: return false - return classType.typeArguments.any { it !is KaStarTypeProjection && it.type?.isFlexibleRecursive() == true } + private fun KaSession.isFlexibleRecursive(type: KaType): Boolean { + if (type.hasFlexibleNullability) return true + val classType = type as? KaClassType ?: return false + return classType.typeArguments.any { arg -> arg !is KaStarTypeProjection && arg.type?.let { isFlexibleRecursive(it) } == true } } private val publicApiVisibilities = setOf( @@ -51,9 +51,10 @@ class HasPlatformTypeInspection( KaSymbolVisibility.PROTECTED, ) - context(KaSession) - private fun dangerousFlexibleTypeOrNull( - declaration: KtCallableDeclaration, publicAPIOnly: Boolean, reportPlatformArguments: Boolean + private fun KaSession.dangerousFlexibleTypeOrNull( + declaration: KtCallableDeclaration, + publicAPIOnly: Boolean, + reportPlatformArguments: Boolean ): KaType? { when (declaration) { is KtFunction -> if (declaration.isLocal || declaration.hasDeclaredReturnType()) return null @@ -65,7 +66,7 @@ class HasPlatformTypeInspection( val type = declaration.returnType if (type is KaDynamicType) return null if (reportPlatformArguments) { - if (!type.isFlexibleRecursive()) return null + if (!isFlexibleRecursive(type)) return null } else { if (!type.hasFlexibleNullability) return null } @@ -73,31 +74,27 @@ class HasPlatformTypeInspection( return type } - context(KaSession) - fun checkForPlatformType(element: KtCallableDeclaration, nameIdentifier: PsiElement, holder: ProblemsHolder) { + private fun KaSession.checkForPlatformType(element: KtCallableDeclaration, nameIdentifier: PsiElement, holder: ProblemsHolder) { val dangerousFlexibleType = dangerousFlexibleTypeOrNull(element, publicAPIOnly, reportPlatformArguments) ?: return + val fixes = mutableListOf( + SpecifyExplicitTypeQuickFix(element, CallableReturnTypeUpdaterUtils.getTypeInfo(element)) + ) if (dangerousFlexibleType.canBeNull) { val nonNullableType = dangerousFlexibleType.withNullability(KaTypeNullability.NON_NULLABLE) val expression = element.node.findChildByType(KtTokens.EQ)?.psi?.getNextSiblingIgnoringWhitespaceAndComments() - if (expression != null && - (!reportPlatformArguments || !nonNullableType.isFlexibleRecursive()) - ) { - holder.registerProblem( - nameIdentifier, - KotlinBundle.message( - "declaration.has.type.inferred.from.a.platform.call.which.can.lead.to.unchecked.nullability.issues" - ), - IntentionWrapper(AddExclExclCallFix(expression)), - IntentionWrapper( - SpecifyExplicitTypeQuickFix( - element, - CallableReturnTypeUpdaterUtils.getTypeInfo(element) - ) - ) - ) + + // Only add this fix if it can actually fully resolve the problem + if (expression != null && (!reportPlatformArguments || !isFlexibleRecursive(nonNullableType))) { + fixes.add(AddExclExclCallFix(expression)) } } + + holder.registerProblem( + nameIdentifier, + KotlinBundle.message("declaration.has.type.inferred.from.a.platform.call.which.can.lead.to.unchecked.nullability.issues"), + *fixes.map { action -> IntentionWrapper(action) }.toTypedArray() + ) } override fun getOptionsPane() = pane( diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java index 84231bbf726a..fc0bd71b0e86 100644 --- a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java +++ b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java @@ -7641,6 +7641,11 @@ public abstract class K2LocalInspectionTestGenerated extends AbstractK2LocalInsp KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); } + @TestMetadata("array.kt") + public void testArray() throws Exception { + runTest("testData/inspectionsLocal/hasPlatformType/array.kt"); + } + @TestMetadata("extensionOnDynamic.kt") public void testExtensionOnDynamic() throws Exception { runTest("testData/inspectionsLocal/hasPlatformType/extensionOnDynamic.kt"); @@ -7656,6 +7661,11 @@ public abstract class K2LocalInspectionTestGenerated extends AbstractK2LocalInsp runTest("testData/inspectionsLocal/hasPlatformType/functionExclExcl.kt"); } + @TestMetadata("internalFunction.kt") + public void testInternalFunction() throws Exception { + runTest("testData/inspectionsLocal/hasPlatformType/internalFunction.kt"); + } + @TestMetadata("localClassFunction.kt") public void testLocalClassFunction() throws Exception { runTest("testData/inspectionsLocal/hasPlatformType/localClassFunction.kt"); @@ -7712,6 +7722,65 @@ public abstract class K2LocalInspectionTestGenerated extends AbstractK2LocalInsp } } + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("testData/inspectionsLocal/hasPlatformTypeWithSettings") + public static class HasPlatformTypeWithSettings extends AbstractK2LocalInspectionTest { + @java.lang.Override + @org.jetbrains.annotations.NotNull + public final KotlinPluginMode getPluginMode() { + return KotlinPluginMode.K2; + } + + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + @TestMetadata("array.kt") + public void testArray() throws Exception { + runTest("testData/inspectionsLocal/hasPlatformTypeWithSettings/array.kt"); + } + + @TestMetadata("internalFunction.kt") + public void testInternalFunction() throws Exception { + runTest("testData/inspectionsLocal/hasPlatformTypeWithSettings/internalFunction.kt"); + } + + @TestMetadata("localFunction.kt") + public void testLocalFunction() throws Exception { + runTest("testData/inspectionsLocal/hasPlatformTypeWithSettings/localFunction.kt"); + } + + @TestMetadata("nullableArrayNoExclExcl.kt") + public void testNullableArrayNoExclExcl() throws Exception { + runTest("testData/inspectionsLocal/hasPlatformTypeWithSettings/nullableArrayNoExclExcl.kt"); + } + + @TestMetadata("privateFunction.kt") + public void testPrivateFunction() throws Exception { + runTest("testData/inspectionsLocal/hasPlatformTypeWithSettings/privateFunction.kt"); + } + + @TestMetadata("property.kt") + public void testProperty() throws Exception { + runTest("testData/inspectionsLocal/hasPlatformTypeWithSettings/property.kt"); + } + + @TestMetadata("propertyExclExcl.kt") + public void testPropertyExclExcl() throws Exception { + runTest("testData/inspectionsLocal/hasPlatformTypeWithSettings/propertyExclExcl.kt"); + } + + @TestMetadata("protectedFunction.kt") + public void testProtectedFunction() throws Exception { + runTest("testData/inspectionsLocal/hasPlatformTypeWithSettings/protectedFunction.kt"); + } + + @TestMetadata("recursiveGeneric.kt") + public void testRecursiveGeneric() throws Exception { + runTest("testData/inspectionsLocal/hasPlatformTypeWithSettings/recursiveGeneric.kt"); + } + } + @RunWith(JUnit3RunnerWithInners.class) @TestMetadata("testData/inspectionsLocal/kotlinOptionsToCompilerOptions") public static class KotlinOptionsToCompilerOptions extends AbstractK2LocalInspectionTest { diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformType/array.kt b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformType/array.kt new file mode 100644 index 000000000000..47b16577010c --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformType/array.kt @@ -0,0 +1,4 @@ +// WITH_STDLIB +// PROBLEM: none + +val test = arrayOf \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformType/internalFunction.kt b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformType/internalFunction.kt new file mode 100644 index 000000000000..81a1166e6f35 --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformType/internalFunction.kt @@ -0,0 +1,4 @@ +// WITH_STDLIB +// PROBLEM: none + +internal fun foo() = java.lang.String.valueOf(1) \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/.k2Inspection b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/.k2Inspection new file mode 100644 index 000000000000..90d808f84f05 --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/.k2Inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.k2.codeinsight.inspections.HasPlatformTypeInspection diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/array.kt b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/array.kt new file mode 100644 index 000000000000..1537ef9efbed --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/array.kt @@ -0,0 +1,3 @@ +// WITH_STDLIB + +val test = arrayOf(java.lang.String.valueOf(1)) \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/array.kt.after b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/array.kt.after new file mode 100644 index 000000000000..2d9c4f0b4085 --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/array.kt.after @@ -0,0 +1,3 @@ +// WITH_STDLIB + +val test: Array = arrayOf(java.lang.String.valueOf(1)) \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/internalFunction.kt b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/internalFunction.kt new file mode 100644 index 000000000000..51bfea63810c --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/internalFunction.kt @@ -0,0 +1,4 @@ +// WITH_STDLIB +// FIX: Specify return type explicitly + +internal fun foo() = java.lang.String.valueOf(1) \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/internalFunction.kt.after b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/internalFunction.kt.after new file mode 100644 index 000000000000..3b4d89f1b7f8 --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/internalFunction.kt.after @@ -0,0 +1,4 @@ +// WITH_STDLIB +// FIX: Specify return type explicitly + +internal fun foo(): String = java.lang.String.valueOf(1) \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/localFunction.kt b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/localFunction.kt new file mode 100644 index 000000000000..6d7b3c548506 --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/localFunction.kt @@ -0,0 +1,6 @@ +// WITH_STDLIB +// PROBLEM: none + +fun foo() { + fun bar() = java.lang.String.valueOf(2) +} \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/nullableArrayNoExclExcl.kt b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/nullableArrayNoExclExcl.kt new file mode 100644 index 000000000000..9b533466dce4 --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/nullableArrayNoExclExcl.kt @@ -0,0 +1,3 @@ +// WITH_STDLIB + +val test = arrayOf(java.lang.String.valueOf(1)).takeIf { it.isEmpty() } \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/nullableArrayNoExclExcl.kt.after b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/nullableArrayNoExclExcl.kt.after new file mode 100644 index 000000000000..dfa5cac9690a --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/nullableArrayNoExclExcl.kt.after @@ -0,0 +1,3 @@ +// WITH_STDLIB + +val test: Array? = arrayOf(java.lang.String.valueOf(1)).takeIf { it.isEmpty() } \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/privateFunction.kt b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/privateFunction.kt new file mode 100644 index 000000000000..21c2948c4279 --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/privateFunction.kt @@ -0,0 +1,4 @@ +// WITH_STDLIB +// FIX: Specify return type explicitly + +private fun foo() = java.lang.String.valueOf(1) \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/privateFunction.kt.after b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/privateFunction.kt.after new file mode 100644 index 000000000000..fdf54788bc5c --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/privateFunction.kt.after @@ -0,0 +1,4 @@ +// WITH_STDLIB +// FIX: Specify return type explicitly + +private fun foo(): String = java.lang.String.valueOf(1) \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/property.kt b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/property.kt new file mode 100644 index 000000000000..c7b1a9e74dc4 --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/property.kt @@ -0,0 +1,4 @@ +// WITH_STDLIB +// FIX: Specify type explicitly + +val foo = java.lang.String.valueOf(1) \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/property.kt.after b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/property.kt.after new file mode 100644 index 000000000000..db904fb8eefc --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/property.kt.after @@ -0,0 +1,4 @@ +// WITH_STDLIB +// FIX: Specify type explicitly + +val foo: String = java.lang.String.valueOf(1) \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/propertyExclExcl.kt b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/propertyExclExcl.kt new file mode 100644 index 000000000000..d691ca95ef62 --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/propertyExclExcl.kt @@ -0,0 +1,4 @@ +// WITH_STDLIB +// FIX: Add non-null asserted (java.lang.String.valueOf(1)!!) call + +val foo = java.lang.String.valueOf(1) \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/propertyExclExcl.kt.after b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/propertyExclExcl.kt.after new file mode 100644 index 000000000000..05f82f22eac2 --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/propertyExclExcl.kt.after @@ -0,0 +1,4 @@ +// WITH_STDLIB +// FIX: Add non-null asserted (java.lang.String.valueOf(1)!!) call + +val foo = java.lang.String.valueOf(1)!! \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/protectedFunction.kt b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/protectedFunction.kt new file mode 100644 index 000000000000..c8a8a4ec097f --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/protectedFunction.kt @@ -0,0 +1,6 @@ +// WITH_STDLIB +// FIX: Specify return type explicitly + +open class My { + protected fun foo() = java.lang.String.valueOf(3) +} \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/protectedFunction.kt.after b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/protectedFunction.kt.after new file mode 100644 index 000000000000..37b86905ba7e --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/protectedFunction.kt.after @@ -0,0 +1,6 @@ +// WITH_STDLIB +// FIX: Specify return type explicitly + +open class My { + protected fun foo(): String = java.lang.String.valueOf(3) +} diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/recursiveGeneric.kt b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/recursiveGeneric.kt new file mode 100644 index 000000000000..4ea5a07766a2 --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/recursiveGeneric.kt @@ -0,0 +1,8 @@ +// WITH_STDLIB +// PROBLEM: none +interface Rec> { + fun t(): T +} +interface Super { + fun foo(p: Rec<*, *>) = p.t() +} \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/settings.xml b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/settings.xml new file mode 100644 index 000000000000..fa313ee22bfc --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/tests/testData/inspectionsLocal/hasPlatformTypeWithSettings/settings.xml @@ -0,0 +1,4 @@ + +