diff --git a/idea/resources/inspectionDescriptions/HasPlatformType.html b/idea/resources/inspectionDescriptions/HasPlatformType.html new file mode 100644 index 00000000000..5fdc2b7ccd5 --- /dev/null +++ b/idea/resources/inspectionDescriptions/HasPlatformType.html @@ -0,0 +1,5 @@ + + +This inspection reports functions and properties that have platform type. In order to prevent unexpected errors, the type should be declared explicitly. + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index eba7789b6d1..edde68e5111 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1569,6 +1569,14 @@ language="kotlin" /> + + if (declaration.isLocal || declaration.hasDeclaredReturnType()) return else "Function" + is KtProperty -> if (declaration.isLocal || declaration.typeReference != null) return else "Property" + else -> return + } + + if (declaration.containingClassOrObject?.isLocal() ?: false) return + + val context = declaration.analyze(BodyResolveMode.PARTIAL) + val returnType = declaration.getReturnType(context) ?: return + if (!returnType.isFlexibleRecursive()) return + + val fixes = mutableListOf(IntentionWrapper(SpecifyTypeExplicitlyIntention(), declaration.containingFile)) + + if (returnType.isNullabilityFlexible()) { + val expression = declaration.node.findChildByType(KtTokens.EQ)?.psi?.getNextSiblingIgnoringWhitespaceAndComments() + if (expression != null) { + fixes += IntentionWrapper(AddExclExclCallFix(expression), declaration.containingFile) + } + } + + val nameElement = declaration.nameIdentifier ?: return + val problemDescriptor = holder.manager.createProblemDescriptor( + nameElement, + nameElement, + "$declType has platform type. Make the type explicit to prevent subtle bugs.", + ProblemHighlightType.WEAK_WARNING, + isOnTheFly, + *fixes.toTypedArray() + ) + holder.registerProblem(problemDescriptor) + } + + private fun KtDeclaration.getReturnType(bindingContext: BindingContext): KotlinType? { + val callable = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, this] as? CallableDescriptor ?: return null + return callable.returnType + } + + private fun KotlinType.isFlexibleRecursive(): Boolean { + if (isFlexible()) return true + return arguments.any { it.type.isFlexibleRecursive() } + } + } + } + + +} \ No newline at end of file diff --git a/idea/testData/inspections/hasPlatformType/function.kt b/idea/testData/inspections/hasPlatformType/function.kt new file mode 100644 index 00000000000..c41deedf2c1 --- /dev/null +++ b/idea/testData/inspections/hasPlatformType/function.kt @@ -0,0 +1,3 @@ +// WITH_RUNTIME + +fun foo() = java.lang.String.valueOf(1) \ No newline at end of file diff --git a/idea/testData/inspections/hasPlatformType/inspectionData/expected.xml b/idea/testData/inspections/hasPlatformType/inspectionData/expected.xml new file mode 100644 index 00000000000..f4f660b9d2e --- /dev/null +++ b/idea/testData/inspections/hasPlatformType/inspectionData/expected.xml @@ -0,0 +1,26 @@ + + + function.kt + 3 + light_idea_test_case + + Function, property or variable has platform type. + Function has platform type. Make the type explicit to prevent subtle bugs. + + + property1.kt + 3 + light_idea_test_case + + Function, property or variable has platform type. + Property has platform type. Make the type explicit to prevent subtle bugs. + + + property2.kt + 3 + light_idea_test_case + + Function, property or variable has platform type. + Property has platform type. Make the type explicit to prevent subtle bugs. + + \ No newline at end of file diff --git a/idea/testData/inspections/hasPlatformType/inspectionData/inspections.test b/idea/testData/inspections/hasPlatformType/inspectionData/inspections.test new file mode 100644 index 00000000000..a40fc8b49dc --- /dev/null +++ b/idea/testData/inspections/hasPlatformType/inspectionData/inspections.test @@ -0,0 +1 @@ +// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.HasPlatformTypeInspection \ No newline at end of file diff --git a/idea/testData/inspections/hasPlatformType/local.kt b/idea/testData/inspections/hasPlatformType/local.kt new file mode 100644 index 00000000000..d2cc1014e43 --- /dev/null +++ b/idea/testData/inspections/hasPlatformType/local.kt @@ -0,0 +1,13 @@ +// WITH_RUNTIME + +fun foo() { + val local = java.lang.String.valueOf(1) + + fun bar() = java.lang.String.valueOf(2) + + class Local { + val local = java.lang.String.valueOf(3) + + fun bar() = java.lang.String.valueOf(4) + } +} \ No newline at end of file diff --git a/idea/testData/inspections/hasPlatformType/property1.kt b/idea/testData/inspections/hasPlatformType/property1.kt new file mode 100644 index 00000000000..f54eb28efe4 --- /dev/null +++ b/idea/testData/inspections/hasPlatformType/property1.kt @@ -0,0 +1,3 @@ +// WITH_RUNTIME + +val bar = java.lang.String.valueOf(1) \ No newline at end of file diff --git a/idea/testData/inspections/hasPlatformType/property2.kt b/idea/testData/inspections/hasPlatformType/property2.kt new file mode 100644 index 00000000000..72602113a6f --- /dev/null +++ b/idea/testData/inspections/hasPlatformType/property2.kt @@ -0,0 +1,3 @@ +// WITH_RUNTIME + +val list = arrayOf(java.lang.String.valueOf(1)) \ No newline at end of file diff --git a/idea/testData/inspections/hasPlatformType/star.kt b/idea/testData/inspections/hasPlatformType/star.kt new file mode 100644 index 00000000000..c6e71914b5a --- /dev/null +++ b/idea/testData/inspections/hasPlatformType/star.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME + +class Wrapper(val value: T) + +fun star(): Wrapper> = Wrapper(Wrapper(Any())) + +fun star2(): List<*> = listOf(Any()) \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java index 4c137231dd8..ae1625946b3 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java @@ -154,6 +154,12 @@ public class InspectionTestGenerated extends AbstractInspectionTest { doTest(fileName); } + @TestMetadata("hasPlatformType/inspectionData/inspections.test") + public void testHasPlatformType_inspectionData_Inspections_test() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/hasPlatformType/inspectionData/inspections.test"); + doTest(fileName); + } + @TestMetadata("leakingThis/inspectionData/inspections.test") public void testLeakingThis_inspectionData_Inspections_test() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/leakingThis/inspectionData/inspections.test");