From 636b63a8c59ef42890b1247def58b4021b8e3237 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Thu, 2 Jul 2015 17:39:16 +0300 Subject: [PATCH] Make "reflection not found" a warning, provide a quick fix Reporting the warning on each "::", as ReflectionNotFoundInspection did, is not correct anymore, because for example name/get/set on properties works perfectly without kotlin-reflect.jar in the classpath. So instead we report the warning on calls to functions from reflection interfaces. This is not perfect either because it's wrong in projects with custom implementations of reflection interfaces, but this case is so rare that the users can suppress the warning there anyway #KT-7176 Fixed --- .../kotlin/codegen/ExpressionCodegen.java | 13 -- .../load/kotlin/KotlinJvmCheckerProvider.kt | 4 +- .../checkers/ReflectionAPICallChecker.kt | 70 +++++++++ .../diagnostics/DefaultErrorMessagesJvm.java | 4 +- .../resolve/jvm/diagnostics/ErrorsJvm.java | 3 +- .../cli/jvm/noReflectionInClasspath.args | 3 - .../cli/jvm/noReflectionInClasspath.kt | 4 - .../cli/jvm/noReflectionInClasspath.out | 7 - .../reflection/noReflectionInClassPath.kt | 21 +++ .../reflection/noReflectionInClassPath.txt | 25 ++++ .../checkers/JetDiagnosticsTestGenerated.java | 15 ++ .../cli/KotlincExecutableTestGenerated.java | 6 - .../resolve/JetExpectedResolveDataUtil.java | 5 +- .../kotlin/types/JetTypeCheckerTest.java | 7 +- .../kotlin/builtins/ReflectionTypes.kt | 13 +- idea/src/META-INF/plugin.xml | 7 - .../idea/inspections/AddReflectionQuickFix.kt | 79 ++++++++++ .../ReflectionNotFoundInspection.kt | 137 ------------------ .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 12 +- .../reflectionNotFound/functionReference.kt | 18 --- .../inspectionData/expected.xml | 26 ---- .../inspectionData/inspections.test | 1 - .../withinAnnotationEntry.kt | 15 -- .../JetInspectionTestGenerated.java | 6 - 24 files changed, 240 insertions(+), 261 deletions(-) create mode 100644 compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/calls/checkers/ReflectionAPICallChecker.kt delete mode 100644 compiler/testData/cli/jvm/noReflectionInClasspath.args delete mode 100644 compiler/testData/cli/jvm/noReflectionInClasspath.kt delete mode 100644 compiler/testData/cli/jvm/noReflectionInClasspath.out create mode 100644 compiler/testData/diagnostics/tests/reflection/noReflectionInClassPath.kt create mode 100644 compiler/testData/diagnostics/tests/reflection/noReflectionInClassPath.txt create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/AddReflectionQuickFix.kt delete mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/ReflectionNotFoundInspection.kt delete mode 100644 idea/testData/inspections/reflectionNotFound/functionReference.kt delete mode 100644 idea/testData/inspections/reflectionNotFound/inspectionData/expected.xml delete mode 100644 idea/testData/inspections/reflectionNotFound/inspectionData/inspections.test delete mode 100644 idea/testData/inspections/reflectionNotFound/withinAnnotationEntry.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index 4efc5b90c7e..ad8442d37fb 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -70,7 +70,6 @@ import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluat import org.jetbrains.kotlin.resolve.constants.evaluate.EvaluatePackage; import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilPackage; import org.jetbrains.kotlin.resolve.inline.InlineUtil; -import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm; import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind; import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterSignature; import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature; @@ -103,7 +102,6 @@ import static org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilPackage. import static org.jetbrains.kotlin.resolve.jvm.AsmTypes.*; import static org.jetbrains.kotlin.resolve.jvm.diagnostics.DiagnosticsPackage.OtherOrigin; import static org.jetbrains.kotlin.resolve.jvm.diagnostics.DiagnosticsPackage.TraitImpl; -import static org.jetbrains.kotlin.serialization.deserialization.DeserializationPackage.findClassAcrossModuleDependencies; import static org.jetbrains.org.objectweb.asm.Opcodes.*; public class ExpressionCodegen extends JetVisitor implements LocalLookup { @@ -2733,8 +2731,6 @@ public class ExpressionCodegen extends JetVisitor implem @Override public StackValue visitClassLiteralExpression(@NotNull JetClassLiteralExpression expression, StackValue data) { - checkReflectionIsAvailable(expression); - JetType type = bindingContext.getType(expression); assert type != null; @@ -2754,9 +2750,6 @@ public class ExpressionCodegen extends JetVisitor implem (FunctionDescriptor) resolvedCall.getResultingDescriptor()); } - // TODO: this diagnostic should also be reported on function references once they obtain reflection - checkReflectionIsAvailable(expression); - VariableDescriptor variableDescriptor = bindingContext.get(VARIABLE, expression); if (variableDescriptor != null) { return generatePropertyReference(expression, variableDescriptor, resolvedCall); @@ -2789,12 +2782,6 @@ public class ExpressionCodegen extends JetVisitor implem return codegen.putInstanceOnStack(); } - private void checkReflectionIsAvailable(@NotNull JetExpression expression) { - if (findClassAcrossModuleDependencies(state.getModule(), JvmAbi.REFLECTION_FACTORY_IMPL) == null) { - state.getDiagnostics().report(ErrorsJvm.NO_REFLECTION_IN_CLASS_PATH.on(expression, expression)); - } - } - @NotNull public static StackValue generateClassLiteralReference(@NotNull final JetTypeMapper typeMapper, @NotNull final JetType type) { return StackValue.operation(K_CLASS_TYPE, new Function1() { diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmCheckerProvider.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmCheckerProvider.kt index 9f0c1a1838b..a8b0f8f4625 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmCheckerProvider.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmCheckerProvider.kt @@ -39,6 +39,7 @@ import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory import org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability import org.jetbrains.kotlin.resolve.jvm.calls.checkers.NeedSyntheticChecker +import org.jetbrains.kotlin.resolve.jvm.calls.checkers.ReflectionAPICallChecker import org.jetbrains.kotlin.resolve.jvm.calls.checkers.TraitDefaultMethodCallChecker import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm.NullabilityInformationSource @@ -59,7 +60,8 @@ public class KotlinJvmCheckerProvider(private val module: ModuleDescriptor) : Ad PublicFieldAnnotationChecker()), additionalCallCheckers = listOf(NeedSyntheticChecker(), JavaAnnotationCallChecker(), - JavaAnnotationMethodCallChecker(), TraitDefaultMethodCallChecker()), + JavaAnnotationMethodCallChecker(), TraitDefaultMethodCallChecker(), + ReflectionAPICallChecker(module)), additionalTypeCheckers = listOf(JavaNullabilityWarningsChecker()), additionalSymbolUsageValidators = listOf() diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/calls/checkers/ReflectionAPICallChecker.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/calls/checkers/ReflectionAPICallChecker.kt new file mode 100644 index 00000000000..ad12ca997b4 --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/calls/checkers/ReflectionAPICallChecker.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.resolve.jvm.calls.checkers + +import org.jetbrains.kotlin.builtins.KOTLIN_REFLECT_FQ_NAME +import org.jetbrains.kotlin.builtins.ReflectionTypes +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor +import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker +import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm.NO_REFLECTION_IN_CLASS_PATH +import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies +import org.jetbrains.kotlin.types.expressions.OperatorConventions +import kotlin.reflect.jvm.java + +/** + * If there's no Kotlin reflection implementation found in the classpath, checks that there are no usages + * of reflection API which will fail at runtime. + */ +class ReflectionAPICallChecker(private val module: ModuleDescriptor) : CallChecker { + private val isReflectionAvailable by lazy { + module.findClassAcrossModuleDependencies(JvmAbi.REFLECTION_FACTORY_IMPL) != null + } + + private val kPropertyClasses by lazy { + val reflectionTypes = ReflectionTypes(module) + setOf(reflectionTypes.kProperty0, reflectionTypes.kProperty1, reflectionTypes.kProperty2) + } + + override fun check(resolvedCall: ResolvedCall, context: BasicCallResolutionContext) { + if (isReflectionAvailable) return + + val descriptor = resolvedCall.getResultingDescriptor() + val containingClass = descriptor.getContainingDeclaration() as? ClassDescriptor ?: return + if (!ReflectionTypes.isReflectionClass(containingClass)) return + + // Skip some symbols which are supposed to work fine without kotlin-reflect.jar: + // - 'name' on anything + // - 'invoke' on functions (or on anything else for that matter) + // - 'get'/'set' on properties + val name = descriptor.getName() + when { + name == OperatorConventions.INVOKE -> return + name.asString() == "name" -> return + (name.asString() == "get" || name.asString() == "set") && + kPropertyClasses.any { kProperty -> DescriptorUtils.isSubclass(containingClass, kProperty) } -> return + } + + context.trace.report(NO_REFLECTION_IN_CLASS_PATH.on(resolvedCall.getCall().getCallElement())) + } +} diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java index 3f3c39dd126..c5c2d84881e 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java @@ -59,8 +59,8 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension { MAP.put(ErrorsJvm.POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION, "Only named arguments are available for Java annotations"); MAP.put(ErrorsJvm.DEPRECATED_ANNOTATION_METHOD_CALL, "Annotation methods are deprecated. Use property instead"); - MAP.put(ErrorsJvm.NO_REFLECTION_IN_CLASS_PATH, "Expression ''{0}'' uses reflection which is not found in compilation classpath. " + - "Make sure you have kotlin-reflect.jar in the classpath", Renderers.ELEMENT_TEXT); + MAP.put(ErrorsJvm.NO_REFLECTION_IN_CLASS_PATH, "Call uses reflection API which is not found in compilation classpath. " + + "Make sure you have kotlin-reflect.jar in the classpath"); MAP.put(ErrorsJvm.NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS, "Expected type does not accept nulls in {0}, but the value may be null in {1}", Renderers.TO_STRING, Renderers.TO_STRING); diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java index 9fddc2d9a71..1c59989e593 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java @@ -58,8 +58,7 @@ public interface ErrorsJvm { DiagnosticFactory0 INAPPLICABLE_PUBLIC_FIELD = DiagnosticFactory0.create(ERROR); - // TODO: make this a warning - DiagnosticFactory1 NO_REFLECTION_IN_CLASS_PATH = DiagnosticFactory1.create(ERROR); + DiagnosticFactory0 NO_REFLECTION_IN_CLASS_PATH = DiagnosticFactory0.create(WARNING); enum NullabilityInformationSource { KOTLIN { diff --git a/compiler/testData/cli/jvm/noReflectionInClasspath.args b/compiler/testData/cli/jvm/noReflectionInClasspath.args deleted file mode 100644 index 6c45450a152..00000000000 --- a/compiler/testData/cli/jvm/noReflectionInClasspath.args +++ /dev/null @@ -1,3 +0,0 @@ -$TESTDATA_DIR$/noReflectionInClasspath.kt --d -$TEMP_DIR$ diff --git a/compiler/testData/cli/jvm/noReflectionInClasspath.kt b/compiler/testData/cli/jvm/noReflectionInClasspath.kt deleted file mode 100644 index aea00a248ca..00000000000 --- a/compiler/testData/cli/jvm/noReflectionInClasspath.kt +++ /dev/null @@ -1,4 +0,0 @@ -class Foo(val prop: Any) - -fun t1() = Foo::prop -fun t2() = Foo::class diff --git a/compiler/testData/cli/jvm/noReflectionInClasspath.out b/compiler/testData/cli/jvm/noReflectionInClasspath.out deleted file mode 100644 index 77b22ce279d..00000000000 --- a/compiler/testData/cli/jvm/noReflectionInClasspath.out +++ /dev/null @@ -1,7 +0,0 @@ -compiler/testData/cli/jvm/noReflectionInClasspath.kt:3:12: error: expression 'Foo::prop' uses reflection which is not found in compilation classpath. Make sure you have kotlin-reflect.jar in the classpath -fun t1() = Foo::prop - ^ -compiler/testData/cli/jvm/noReflectionInClasspath.kt:4:12: error: expression 'Foo::class' uses reflection which is not found in compilation classpath. Make sure you have kotlin-reflect.jar in the classpath -fun t2() = Foo::class - ^ -COMPILATION_ERROR diff --git a/compiler/testData/diagnostics/tests/reflection/noReflectionInClassPath.kt b/compiler/testData/diagnostics/tests/reflection/noReflectionInClassPath.kt new file mode 100644 index 00000000000..2e37aa21352 --- /dev/null +++ b/compiler/testData/diagnostics/tests/reflection/noReflectionInClassPath.kt @@ -0,0 +1,21 @@ +import kotlin.reflect.* + +class Foo(val prop: Any) { + fun func() {} +} + +fun n01() = Foo::prop +fun n02() = Foo::func +fun n03() = Foo::class +fun n04(p: KProperty0) = p.get() +fun n05(p: KMutableProperty0) = p.set("") +fun n06(p: KProperty0) = p.get() +fun n07(p: KFunction) = p.name +fun n08(p: KProperty1) = p[""] +fun n09(p: KProperty2) = p["", ""] +fun n10() = (Foo::func).invoke(Foo("")) +fun n11() = (Foo::func)(Foo("")) + +fun y01() = Foo::prop.getter +fun y02() = Foo::class.properties +fun y03() = Foo::class.simpleName diff --git a/compiler/testData/diagnostics/tests/reflection/noReflectionInClassPath.txt b/compiler/testData/diagnostics/tests/reflection/noReflectionInClassPath.txt new file mode 100644 index 00000000000..6b6d957333b --- /dev/null +++ b/compiler/testData/diagnostics/tests/reflection/noReflectionInClassPath.txt @@ -0,0 +1,25 @@ +package + +internal fun n01(): kotlin.reflect.KProperty1 +internal fun n02(): kotlin.reflect.KFunction1 +internal fun n03(): kotlin.reflect.KClass +internal fun n04(/*0*/ p: kotlin.reflect.KProperty0): kotlin.Int +internal fun n05(/*0*/ p: kotlin.reflect.KMutableProperty0): kotlin.Unit +internal fun n06(/*0*/ p: kotlin.reflect.KProperty0): kotlin.Int +internal fun n07(/*0*/ p: kotlin.reflect.KFunction): kotlin.String +internal fun n08(/*0*/ p: kotlin.reflect.KProperty1): kotlin.Int +internal fun n09(/*0*/ p: kotlin.reflect.KProperty2): kotlin.Int +internal fun n10(): kotlin.Unit +internal fun n11(): kotlin.Unit +internal fun y01(): kotlin.reflect.KProperty1.Getter +internal fun y02(): kotlin.Collection> +internal fun y03(): kotlin.String? + +internal final class Foo { + public constructor Foo(/*0*/ prop: kotlin.Any) + internal final val prop: kotlin.Any + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + internal final fun func(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index f636caee210..075a993b04f 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -10435,6 +10435,21 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { } } + @TestMetadata("compiler/testData/diagnostics/tests/reflection") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Reflection extends AbstractJetDiagnosticsTest { + public void testAllFilesPresentInReflection() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/reflection"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("noReflectionInClassPath.kt") + public void testNoReflectionInClassPath() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/reflection/noReflectionInClassPath.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/diagnostics/tests/regressions") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/cli/KotlincExecutableTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/cli/KotlincExecutableTestGenerated.java index 23ad7e546e4..0495cffe9fa 100644 --- a/compiler/tests/org/jetbrains/kotlin/cli/KotlincExecutableTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/cli/KotlincExecutableTestGenerated.java @@ -97,12 +97,6 @@ public class KotlincExecutableTestGenerated extends AbstractKotlincExecutableTes doJvmTest(fileName); } - @TestMetadata("noReflectionInClasspath.args") - public void testNoReflectionInClasspath() throws Exception { - String fileName = JetTestUtils.navigationMetadata("compiler/testData/cli/jvm/noReflectionInClasspath.args"); - doJvmTest(fileName); - } - @TestMetadata("nonExistingClassPathAndAnnotationsPath.args") public void testNonExistingClassPathAndAnnotationsPath() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/cli/jvm/nonExistingClassPathAndAnnotationsPath.args"); diff --git a/compiler/tests/org/jetbrains/kotlin/resolve/JetExpectedResolveDataUtil.java b/compiler/tests/org/jetbrains/kotlin/resolve/JetExpectedResolveDataUtil.java index 9d5af24634e..179bcf2d4d1 100644 --- a/compiler/tests/org/jetbrains/kotlin/resolve/JetExpectedResolveDataUtil.java +++ b/compiler/tests/org/jetbrains/kotlin/resolve/JetExpectedResolveDataUtil.java @@ -21,6 +21,7 @@ import com.intellij.psi.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.descriptors.*; +import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl; import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; @@ -126,8 +127,10 @@ public class JetExpectedResolveDataUtil { Project project, JetType... parameterTypes ) { - ModuleDescriptor emptyModule = JetTestUtils.createEmptyModule(); + ModuleDescriptorImpl emptyModule = JetTestUtils.createEmptyModule(); ContainerForTests container = DiPackage.createContainerForTests(project, emptyModule); + emptyModule.setDependencies(emptyModule); + emptyModule.initialize(PackageFragmentProvider.Empty.INSTANCE$); ExpressionTypingContext context = ExpressionTypingContext.newContext( container.getAdditionalCheckerProvider(), new BindingTraceContext(), classDescriptor.getDefaultType().getMemberScope(), diff --git a/compiler/tests/org/jetbrains/kotlin/types/JetTypeCheckerTest.java b/compiler/tests/org/jetbrains/kotlin/types/JetTypeCheckerTest.java index 0beede9467d..608c06c003b 100644 --- a/compiler/tests/org/jetbrains/kotlin/types/JetTypeCheckerTest.java +++ b/compiler/tests/org/jetbrains/kotlin/types/JetTypeCheckerTest.java @@ -23,8 +23,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; import org.jetbrains.kotlin.descriptors.ModuleDescriptor; +import org.jetbrains.kotlin.descriptors.PackageFragmentProvider; import org.jetbrains.kotlin.descriptors.PackageViewDescriptor; import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor; +import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl; import org.jetbrains.kotlin.descriptors.impl.ReceiverParameterDescriptorImpl; import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.name.Name; @@ -73,7 +75,10 @@ public class JetTypeCheckerTest extends JetLiteFixture { builtIns = KotlinBuiltIns.getInstance(); - ContainerForTests container = DiPackage.createContainerForTests(getProject(), JetTestUtils.createEmptyModule()); + ModuleDescriptorImpl module = JetTestUtils.createEmptyModule(); + ContainerForTests container = DiPackage.createContainerForTests(getProject(), module); + module.setDependencies(Collections.singletonList(module)); + module.initialize(PackageFragmentProvider.Empty.INSTANCE$); typeResolver = container.getTypeResolver(); expressionTypingServices = container.getExpressionTypingServices(); diff --git a/core/descriptors/src/org/jetbrains/kotlin/builtins/ReflectionTypes.kt b/core/descriptors/src/org/jetbrains/kotlin/builtins/ReflectionTypes.kt index 5b02b53cd56..787d7a775da 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/builtins/ReflectionTypes.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/builtins/ReflectionTypes.kt @@ -18,20 +18,19 @@ package org.jetbrains.kotlin.builtins import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor 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 java.util.ArrayList -import kotlin.properties.Delegates val KOTLIN_REFLECT_FQ_NAME = FqName("kotlin.reflect") public class ReflectionTypes(private val module: ModuleDescriptor) { - private val kotlinReflectScope: JetScope by Delegates.lazy { + private val kotlinReflectScope: JetScope by lazy { module.getPackage(KOTLIN_REFLECT_FQ_NAME).memberScope } @@ -52,6 +51,7 @@ public class ReflectionTypes(private val module: ModuleDescriptor) { public val kClass: ClassDescriptor by ClassLookup public val kProperty0: ClassDescriptor by ClassLookup public val kProperty1: ClassDescriptor by ClassLookup + public val kProperty2: ClassDescriptor by ClassLookup public val kMutableProperty0: ClassDescriptor by ClassLookup public val kMutableProperty1: ClassDescriptor by ClassLookup @@ -108,10 +108,9 @@ public class ReflectionTypes(private val module: ModuleDescriptor) { } 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) + public fun isReflectionClass(descriptor: ClassDescriptor): Boolean { + val containingPackage = DescriptorUtils.getParentOfType(descriptor, javaClass()) + return containingPackage != null && containingPackage.fqName == KOTLIN_REFLECT_FQ_NAME } } } diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 513efab7734..cc30188b4b7 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1047,13 +1047,6 @@ level="WARNING" /> - - (element) { + override fun getText() = JetBundle.message("add.reflection.to.classpath") + override fun getFamilyName() = getText() + + override fun invoke(project: Project, editor: Editor?, file: JetFile) { + val pluginReflectJar = PathUtil.getKotlinPathsForIdeaPlugin().getReflectPath() + if (!pluginReflectJar.exists()) return + + val configurator = Extensions.getExtensions(KotlinProjectConfigurator.EP_NAME) + .firstIsInstanceOrNull() ?: return + + for (library in KotlinRuntimeLibraryUtil.findKotlinLibraries(project)) { + val runtimeJar = JavaRuntimePresentationProvider.getRuntimeJar(library) ?: continue + if (JavaRuntimePresentationProvider.getReflectJar(library) != null) continue + + val model = library.getModifiableModel() + + val libFilesDir = VfsUtilCore.virtualToIoFile(runtimeJar).getParent() + val reflectIoFile = File(libFilesDir, PathUtil.KOTLIN_JAVA_REFLECT_JAR) + if (reflectIoFile.exists()) { + model.addRoot(VfsUtil.getUrlForLibraryRoot(reflectIoFile), OrderRootType.CLASSES) + } + else { + val copied = configurator.copyFileToDir(pluginReflectJar, libFilesDir)!! + model.addRoot(VfsUtil.getUrlForLibraryRoot(copied), OrderRootType.CLASSES) + } + + model.commit() + + ConfigureKotlinInProjectUtils.showInfoNotification( + "${PathUtil.KOTLIN_JAVA_REFLECT_JAR} was added to the library ${library.getName()}" + ) + } + } + + companion object : JetSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic) = diagnostic.createIntentionForFirstParentOfType(::AddReflectionQuickFix) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/ReflectionNotFoundInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/ReflectionNotFoundInspection.kt deleted file mode 100644 index 53e9ec236b7..00000000000 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/ReflectionNotFoundInspection.kt +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2010-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.idea.inspections - -import com.intellij.codeInspection.LocalQuickFix -import com.intellij.codeInspection.ProblemDescriptor -import com.intellij.codeInspection.ProblemHighlightType -import com.intellij.codeInspection.ProblemsHolder -import com.intellij.openapi.extensions.Extensions -import com.intellij.openapi.module.ModuleUtilCore -import com.intellij.openapi.project.Project -import com.intellij.openapi.roots.OrderRootType -import com.intellij.openapi.vfs.VfsUtil -import com.intellij.openapi.vfs.VfsUtilCore -import com.intellij.psi.PsiElementVisitor -import com.intellij.psi.PsiFile -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 -import org.jetbrains.kotlin.idea.configuration.KotlinProjectConfigurator -import org.jetbrains.kotlin.idea.framework.JavaRuntimePresentationProvider -import org.jetbrains.kotlin.idea.project.ProjectStructureUtil -import org.jetbrains.kotlin.idea.util.ProjectRootsUtil -import org.jetbrains.kotlin.idea.versions.KotlinRuntimeLibraryUtil -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.builtins.ReflectionTypes -import org.jetbrains.kotlin.psi.JetAnnotationEntry -import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType -import org.jetbrains.kotlin.utils.PathUtil -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull -import org.jetbrains.kotlin.utils.singletonOrEmptyList -import java.io.File - -public class ReflectionNotFoundInspection : AbstractKotlinInspection() { - override fun runForWholeFile() = true - - override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { - if (!shouldReportInFile(holder.getFile())) return PsiElementVisitor.EMPTY_VISITOR - - return object : JetVisitorVoid() { - private fun createQuickFix(): LocalQuickFix? { - val pluginReflectJar = PathUtil.getKotlinPathsForIdeaPlugin().getReflectPath() - if (pluginReflectJar.exists()) { - val configurator = - Extensions.getExtensions(KotlinProjectConfigurator.EP_NAME).firstIsInstanceOrNull() - - if (configurator != null) { - return AddReflectJarQuickFix(configurator, pluginReflectJar) - } - } - - return null - } - - override fun visitDoubleColonExpression(expression: JetDoubleColonExpression) { - val expectedType = expression.analyze().get(BindingContext.EXPECTED_EXPRESSION_TYPE, expression) - if (expectedType != null && !ReflectionTypes.isReflectionType(expectedType)) return - - if (expression.getStrictParentOfType() != null) 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().toTypedArray()) - ) - } - } - } - - private fun shouldReportInFile(file: PsiFile): Boolean { - if (file !is JetFile || !ProjectRootsUtil.isInProjectSource(file)) return false - - val module = ModuleUtilCore.findModuleForPsiElement(file) - if (module == null || !ProjectStructureUtil.isJavaKotlinModule(module)) return false - - return file.findModuleDescriptor().findClassAcrossModuleDependencies(JvmAbi.REFLECTION_FACTORY_IMPL) == null - } - - class AddReflectJarQuickFix( - val configurator: KotlinJavaModuleConfigurator, - val pluginReflectJar: File - ) : LocalQuickFix { - override fun getName() = JetBundle.message("add.reflection.to.classpath") - - override fun getFamilyName() = getName() - - override fun applyFix(project: Project, descriptor: ProblemDescriptor?) { - for (library in KotlinRuntimeLibraryUtil.findKotlinLibraries(project)) { - val runtimeJar = JavaRuntimePresentationProvider.getRuntimeJar(library) ?: continue - if (JavaRuntimePresentationProvider.getReflectJar(library) != null) continue - - val model = library.getModifiableModel() - - val libFilesDir = VfsUtilCore.virtualToIoFile(runtimeJar).getParent() - val reflectIoFile = File(libFilesDir, PathUtil.KOTLIN_JAVA_REFLECT_JAR) - if (reflectIoFile.exists()) { - model.addRoot(VfsUtil.getUrlForLibraryRoot(reflectIoFile), OrderRootType.CLASSES) - } - else { - val copied = configurator.copyFileToDir(pluginReflectJar, libFilesDir)!! - model.addRoot(VfsUtil.getUrlForLibraryRoot(copied), OrderRootType.CLASSES) - } - - model.commit() - - ConfigureKotlinInProjectUtils.showInfoNotification( - "${PathUtil.KOTLIN_JAVA_REFLECT_JAR} was added to the library ${library.getName()}" - ) - } - } - } -} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 256dbe1aaae..c23ea011a44 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -20,6 +20,7 @@ import com.intellij.codeInsight.intention.IntentionAction import org.jetbrains.kotlin.diagnostics.DiagnosticFactory import org.jetbrains.kotlin.diagnostics.Errors.* import org.jetbrains.kotlin.idea.core.codeInsight.ImplementMethodsHandler +import org.jetbrains.kotlin.idea.inspections.AddReflectionQuickFix import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createCallable.* import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.CreateClassFromCallWithConstructorCalleeActionFactory import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createClass.CreateClassFromConstructorCallActionFactory @@ -30,7 +31,9 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateP import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateParameterByNamedArgumentActionFactory import org.jetbrains.kotlin.lexer.JetTokens.* import org.jetbrains.kotlin.psi.JetClass -import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm +import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm.DEPRECATED_ANNOTATION_METHOD_CALL +import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm.NO_REFLECTION_IN_CLASS_PATH +import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm.POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION public class QuickFixRegistrar : QuickFixContributor { public override fun registerQuickFixes(quickFixes: QuickFixes) { @@ -306,8 +309,7 @@ public class QuickFixRegistrar : QuickFixContributor { EXPLICIT_DELEGATION_CALL_REQUIRED.registerFactory(InsertDelegationCallQuickfix.InsertThisDelegationCallFactory) EXPLICIT_DELEGATION_CALL_REQUIRED.registerFactory(InsertDelegationCallQuickfix.InsertSuperDelegationCallFactory) - ErrorsJvm.DEPRECATED_ANNOTATION_METHOD_CALL.registerFactory(MigrateAnnotationMethodCallFix, - MigrateAnnotationMethodCallInWholeFile) + DEPRECATED_ANNOTATION_METHOD_CALL.registerFactory(MigrateAnnotationMethodCallFix, MigrateAnnotationMethodCallInWholeFile) ENUM_ENTRY_USES_DEPRECATED_SUPER_CONSTRUCTOR.registerFactory(DeprecatedEnumEntrySuperConstructorSyntaxFix, DeprecatedEnumEntrySuperConstructorSyntaxFix.createWholeProjectFixFactory()) @@ -327,6 +329,8 @@ public class QuickFixRegistrar : QuickFixContributor { DEPRECATED_SYMBOL_WITH_MESSAGE.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix) - ErrorsJvm.POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION.registerFactory(ReplaceJavaAnnotationPositionedArgumentsFix) + POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION.registerFactory(ReplaceJavaAnnotationPositionedArgumentsFix) + + NO_REFLECTION_IN_CLASS_PATH.registerFactory(AddReflectionQuickFix) } } diff --git a/idea/testData/inspections/reflectionNotFound/functionReference.kt b/idea/testData/inspections/reflectionNotFound/functionReference.kt deleted file mode 100644 index 289cd4ecda9..00000000000 --- a/idea/testData/inspections/reflectionNotFound/functionReference.kt +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index fed4c656629..00000000000 --- a/idea/testData/inspections/reflectionNotFound/inspectionData/expected.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - 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 - - - withinAnnotationEntry.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 deleted file mode 100644 index e5639d6dfe9..00000000000 --- a/idea/testData/inspections/reflectionNotFound/inspectionData/inspections.test +++ /dev/null @@ -1 +0,0 @@ -// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.ReflectionNotFoundInspection diff --git a/idea/testData/inspections/reflectionNotFound/withinAnnotationEntry.kt b/idea/testData/inspections/reflectionNotFound/withinAnnotationEntry.kt deleted file mode 100644 index 3e75d6bc3e8..00000000000 --- a/idea/testData/inspections/reflectionNotFound/withinAnnotationEntry.kt +++ /dev/null @@ -1,15 +0,0 @@ -package test - -// WITH_RUNTIME - -import kotlin.reflect.KClass - -annotation class A(val arg: KClass<*>, val args: Array>, vararg val other: KClass<*>) - -A(Int::class, array(String::class), Double::class, Char::class) -class MyClass { - throws(Exception::class) - fun foo() { - Exception::class - } -} diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java index 2ebab0d6aca..70dafa1ab80 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java @@ -88,12 +88,6 @@ 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");