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");