diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/reflect/ReflectionTypes.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/reflect/ReflectionTypes.kt index 763f0538ac4..5d6b023f71d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/reflect/ReflectionTypes.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/reflect/ReflectionTypes.kt @@ -22,6 +22,8 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.isSubpackageOf +import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.scopes.JetScope import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.TypeUtils.makeStarProjection @@ -125,4 +127,12 @@ public class ReflectionTypes(private val module: ModuleDescriptor) { arguments.add(TypeProjectionImpl(returnType)) return JetTypeImpl(annotations, classDescriptor.getTypeConstructor(), false, arguments, classDescriptor.getMemberScope(arguments)) } + + companion object { + public fun isReflectionType(type: JetType): Boolean { + val descriptor = type.getConstructor().getDeclarationDescriptor() ?: return false + val fqName = DescriptorUtils.getFqName(descriptor) + return fqName.isSafe() && fqName.toSafe().isSubpackageOf(KOTLIN_REFLECT_FQ_NAME) + } + } } diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/ReflectionNotFoundInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/ReflectionNotFoundInspection.kt index 31a6afece84..f473f4df3c3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/ReflectionNotFoundInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/ReflectionNotFoundInspection.kt @@ -27,6 +27,7 @@ import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.psi.PsiElementVisitor import org.jetbrains.kotlin.idea.JetBundle +import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor import org.jetbrains.kotlin.idea.configuration.ConfigureKotlinInProjectUtils import org.jetbrains.kotlin.idea.configuration.KotlinJavaModuleConfigurator @@ -38,7 +39,9 @@ import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.psi.JetDoubleColonExpression import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.JetVisitorVoid +import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies +import org.jetbrains.kotlin.types.reflect.ReflectionTypes import org.jetbrains.kotlin.utils.PathUtil import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.singletonOrEmptyList @@ -49,7 +52,7 @@ public class ReflectionNotFoundInspection : AbstractKotlinInspection() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { val file = holder.getFile() - val reportProblem = + val noReflectionInClassPath = file is JetFile && ProjectRootsUtil.isInProjectSource(file) && file.findModuleDescriptor().findClassAcrossModuleDependencies(JvmAbi.REFLECTION_FACTORY_IMPL) == null @@ -70,14 +73,20 @@ public class ReflectionNotFoundInspection : AbstractKotlinInspection() { } override fun visitDoubleColonExpression(expression: JetDoubleColonExpression) { - if (reportProblem) { - holder.registerProblem( - expression.getDoubleColonTokenReference(), - JetBundle.message("reflection.not.found"), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - *(createQuickFix().singletonOrEmptyList().copyToArray()) - ) - } + if (!noReflectionInClassPath) return + + val expectedType = expression.analyze().get(BindingContext.EXPECTED_EXPRESSION_TYPE, expression) + if (expectedType != null && !ReflectionTypes.isReflectionType(expectedType)) return + + // If a callable reference is used where a KFunction/KProperty/... expected, we should report that usage as dangerous + // because reflection features will fail without kotlin-reflect.jar in the classpath. + // If it's only used as a Function however (for example, "list.map(::function)"), we should not report anything + holder.registerProblem( + expression.getDoubleColonTokenReference(), + JetBundle.message("reflection.not.found"), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + *(createQuickFix().singletonOrEmptyList().copyToArray()) + ) } } } diff --git a/idea/testData/inspections/reflectionNotFound/functionReference.kt b/idea/testData/inspections/reflectionNotFound/functionReference.kt new file mode 100644 index 00000000000..289cd4ecda9 --- /dev/null +++ b/idea/testData/inspections/reflectionNotFound/functionReference.kt @@ -0,0 +1,18 @@ +package test + +// WITH_RUNTIME + +import kotlin.reflect.KFunction0 + +fun foo() {} + +fun bar(f: () -> Unit) = f() + +// Inspection should be reported here because '::foo' may be used as a reflection object +val p1 = ::foo // the type is KFunction0 by default +val p2: KFunction0 = ::foo // the expected type is KFunction0 + +// But shouldn't be reported here +val p3 = bar(::foo) // the expected type is Function0, '::foo' is used as an ordinary function +val p4: Any = ::foo // the expected type is Any +val p5: UnresolvedClass = ::foo // an error, another warning would be useless diff --git a/idea/testData/inspections/reflectionNotFound/inspectionData/expected.xml b/idea/testData/inspections/reflectionNotFound/inspectionData/expected.xml new file mode 100644 index 00000000000..51426e80ffa --- /dev/null +++ b/idea/testData/inspections/reflectionNotFound/inspectionData/expected.xml @@ -0,0 +1,18 @@ + + + functionReference.kt + 12 + light_idea_test_case + + Reflection not found + Reflection not found in the classpath + + + functionReference.kt + 13 + light_idea_test_case + + Reflection not found + Reflection not found in the classpath + + diff --git a/idea/testData/inspections/reflectionNotFound/inspectionData/inspections.test b/idea/testData/inspections/reflectionNotFound/inspectionData/inspections.test new file mode 100644 index 00000000000..e5639d6dfe9 --- /dev/null +++ b/idea/testData/inspections/reflectionNotFound/inspectionData/inspections.test @@ -0,0 +1 @@ +// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.ReflectionNotFoundInspection diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java index c001d36975c..583964ec1c7 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java @@ -81,6 +81,12 @@ public class JetInspectionTestGenerated extends AbstractJetInspectionTest { JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/inspections"), Pattern.compile("^(inspections\\.test)$")); } + @TestMetadata("reflectionNotFound/inspectionData/inspections.test") + public void testReflectionNotFound_inspectionData_Inspections_test() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/inspections/reflectionNotFound/inspectionData/inspections.test"); + doTest(fileName); + } + @TestMetadata("spelling/inspectionData/inspections.test") public void testSpelling_inspectionData_Inspections_test() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/inspections/spelling/inspectionData/inspections.test");