From db5e00bcc060a2bba6630de2814b683c61b837b6 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Thu, 10 Mar 2016 19:07:40 +0300 Subject: [PATCH] Report error if some deserialized classes are missing in dependencies Technically we often can compile code which uses missing classes (as long as nothing is called on them) but it seems better to let the user know something's wrong in their setup before the error manifests itself at runtime. Also the Java compiler does the same #KT-4328 Fixed --- .../checkers/MissingDependencyClassChecker.kt | 62 +++++++++++++++++++ .../diagnostics/DefaultErrorMessagesJvm.java | 3 + .../resolve/jvm/diagnostics/ErrorsJvm.java | 2 + .../jvm/platform/JvmPlatformConfigurator.kt | 4 +- .../resolve/calls/checkers/CallChecker.kt | 9 +++ .../CallReturnsArrayOfNothingChecker.kt | 11 +--- .../output.txt | 12 ++++ .../library/a.kt | 3 + .../library/b.kt | 10 +++ .../output.txt | 10 +++ .../missingDependencyDifferentCases/source.kt | 9 +++ .../missingDependencySimple/output.txt | 6 ++ ...ompileKotlinAgainstCustomBinariesTest.java | 6 +- .../org/jetbrains/kotlin/utils/collections.kt | 7 ++- 14 files changed, 141 insertions(+), 13 deletions(-) create mode 100644 compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/MissingDependencyClassChecker.kt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/library/a.kt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/library/b.kt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/output.txt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/source.kt diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/MissingDependencyClassChecker.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/MissingDependencyClassChecker.kt new file mode 100644 index 00000000000..1880e5f1b13 --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/MissingDependencyClassChecker.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2010-2016 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.checkers + +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker +import org.jetbrains.kotlin.resolve.calls.checkers.isComputingDeferredType +import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.descriptorUtil.classId +import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm +import org.jetbrains.kotlin.serialization.deserialization.NotFoundClasses +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.utils.newLinkedHashSetWithExpectedSize + +class MissingDependencyClassChecker : CallChecker { + override fun check(resolvedCall: ResolvedCall<*>, context: BasicCallResolutionContext) { + for (classId in collectNotFoundClasses(resolvedCall.resultingDescriptor)) { + context.trace.report(ErrorsJvm.MISSING_DEPENDENCY_CLASS.on(resolvedCall.call.callElement, classId.asSingleFqName())) + } + } + + private fun collectNotFoundClasses(descriptor: CallableDescriptor): Set { + val result: MutableSet = newLinkedHashSetWithExpectedSize(1) + + fun consider(classDescriptor: ClassDescriptor) { + if (classDescriptor is NotFoundClasses.MockClassDescriptor) { + result.add(classDescriptor.classId) + return + } + (classDescriptor.containingDeclaration as? ClassDescriptor)?.let(::consider) + } + + fun consider(type: KotlinType) { + if (!isComputingDeferredType(type)) { + (type.constructor.declarationDescriptor as? ClassDescriptor)?.let(::consider) + } + } + + descriptor.returnType?.let(::consider) + descriptor.extensionReceiverParameter?.value?.type?.let(::consider) + descriptor.valueParameters.forEach { consider(it.type) } + + return result.orEmpty() + } +} 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 90912d7dea9..03729a88944 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 @@ -54,6 +54,9 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension { MAP.put(ErrorsJvm.CONFLICTING_JVM_DECLARATIONS, "Platform declaration clash: {0}", CONFLICTING_JVM_DECLARATIONS_DATA); MAP.put(ErrorsJvm.ACCIDENTAL_OVERRIDE, "Accidental override: {0}", CONFLICTING_JVM_DECLARATIONS_DATA); MAP.put(ErrorsJvm.CONFLICTING_INHERITED_JVM_DECLARATIONS, "Inherited platform declarations clash: {0}", CONFLICTING_JVM_DECLARATIONS_DATA); + + MAP.put(ErrorsJvm.MISSING_DEPENDENCY_CLASS, "Cannot access class ''{0}''. Check your module classpath for missing or conflicting dependencies", Renderers.TO_STRING); + MAP.put(ErrorsJvm.JVM_STATIC_NOT_IN_OBJECT, "Only functions in named objects and companion objects of classes can be annotated with ''@JvmStatic''"); MAP.put(ErrorsJvm.JVM_STATIC_ON_CONST_OR_JVM_FIELD, "''@JvmStatic'' annotation is useless for const or ''@JvmField'' properties"); MAP.put(ErrorsJvm.OVERRIDE_CANNOT_BE_STATIC, "Override member cannot be ''@JvmStatic'' in object"); 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 d61e9bd45a5..f325333ab09 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 @@ -41,6 +41,8 @@ public interface ErrorsJvm { DiagnosticFactory1 CONFLICTING_INHERITED_JVM_DECLARATIONS = DiagnosticFactory1.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); + DiagnosticFactory1 MISSING_DEPENDENCY_CLASS = DiagnosticFactory1.create(ERROR); + DiagnosticFactory0 OVERRIDE_CANNOT_BE_STATIC = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE); DiagnosticFactory0 JVM_STATIC_NOT_IN_OBJECT = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE); DiagnosticFactory0 JVM_STATIC_ON_CONST_OR_JVM_FIELD = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE); diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt index 6b78131bb89..3c7c34874f9 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt @@ -27,7 +27,6 @@ import org.jetbrains.kotlin.resolve.jvm.checkers.* import org.jetbrains.kotlin.synthetic.JavaSyntheticScopes import org.jetbrains.kotlin.types.DynamicTypesSettings - object JvmPlatformConfigurator : PlatformConfigurator( DynamicTypesSettings(), additionalDeclarationCheckers = listOf( @@ -51,7 +50,8 @@ object JvmPlatformConfigurator : PlatformConfigurator( JavaClassOnCompanionChecker(), ProtectedInSuperClassCompanionCallChecker(), UnsupportedSyntheticCallableReferenceChecker(), - SuperCallWithDefaultArgumentsChecker() + SuperCallWithDefaultArgumentsChecker(), + MissingDependencyClassChecker() ), additionalTypeCheckers = listOf( diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/CallChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/CallChecker.kt index 1e6803765b9..8405427b716 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/CallChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/CallChecker.kt @@ -18,7 +18,16 @@ package org.jetbrains.kotlin.resolve.calls.checkers import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.types.DeferredType +import org.jetbrains.kotlin.types.KotlinType interface CallChecker { fun check(resolvedCall: ResolvedCall<*>, context: BasicCallResolutionContext) } + +// Use this utility to avoid premature computation of deferred return type of a resolved callable descriptor. +// Computing it in CallChecker#check is not feasible since it would trigger "type checking has run into a recursive problem" errors. +// Receiver parameter is present to emphasize that this function should ideally be only used from call checkers. +@Suppress("unused") +fun CallChecker.isComputingDeferredType(type: KotlinType) = + type is DeferredType && type.isComputing diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/CallReturnsArrayOfNothingChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/CallReturnsArrayOfNothingChecker.kt index e6d7fe934ab..aa436238e79 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/CallReturnsArrayOfNothingChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/CallReturnsArrayOfNothingChecker.kt @@ -19,7 +19,6 @@ package org.jetbrains.kotlin.resolve.calls.checkers import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall -import org.jetbrains.kotlin.types.DeferredType import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.typeUtil.isArrayOfNothing @@ -35,13 +34,9 @@ class CallReturnsArrayOfNothingChecker : CallChecker { } private fun KotlinType?.containsArrayOfNothing(): Boolean { - // if this.isComputing is true, it means that resolve - // has run into recursion, so checking for Array is meaningless anyway, - // and error about recursion will be reported later - if (this == null || this is DeferredType && this.isComputing) return false + if (this == null || isComputingDeferredType(this)) return false - if (isArrayOfNothing()) return true - - return arguments.any { !it.isStarProjection && it.type.containsArrayOfNothing() } + return isArrayOfNothing() || + arguments.any { !it.isStarProjection && it.type.containsArrayOfNothing() } } } diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/output.txt b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/output.txt index 8d0140057b2..9f04735f032 100644 --- a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/output.txt +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/output.txt @@ -1,7 +1,19 @@ +compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/source.kt:7:8: error: cannot access class 'a.A.Inner'. Check your module classpath for missing or conflicting dependencies + b2.consumeA(b1.produceA()) + ^ compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/source.kt:7:17: error: type mismatch: inferred type is A.Inner but A.Inner was expected b2.consumeA(b1.produceA()) ^ +compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/source.kt:7:20: error: cannot access class 'a.A.Inner'. Check your module classpath for missing or conflicting dependencies + b2.consumeA(b1.produceA()) + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/source.kt:11:8: error: cannot access class 'a.AA.Inner'. Check your module classpath for missing or conflicting dependencies + b2.consumeAA(b1.produceAA()) + ^ compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/source.kt:11:18: error: type mismatch: inferred type is AA.Inner but AA.Inner was expected b2.consumeAA(b1.produceAA()) ^ +compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/source.kt:11:21: error: cannot access class 'a.AA.Inner'. Check your module classpath for missing or conflicting dependencies + b2.consumeAA(b1.produceAA()) + ^ COMPILATION_ERROR \ No newline at end of file diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/library/a.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/library/a.kt new file mode 100644 index 00000000000..0ba6a65f47f --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/library/a.kt @@ -0,0 +1,3 @@ +package a + +interface A diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/library/b.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/library/b.kt new file mode 100644 index 00000000000..172152d6eb8 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/library/b.kt @@ -0,0 +1,10 @@ +package b + +import a.A + +interface B { + fun returnType(): A + fun parameter(param: A?) +} + +fun A?.extensionReceiver() {} diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/output.txt b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/output.txt new file mode 100644 index 00000000000..954d65f09e6 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/output.txt @@ -0,0 +1,10 @@ +compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/source.kt:6:7: error: cannot access class 'a.A'. Check your module classpath for missing or conflicting dependencies + b.returnType() + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/source.kt:7:7: error: cannot access class 'a.A'. Check your module classpath for missing or conflicting dependencies + b.parameter(null) + ^ +compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/source.kt:8:10: error: cannot access class 'a.A'. Check your module classpath for missing or conflicting dependencies + null.extensionReceiver() + ^ +COMPILATION_ERROR \ No newline at end of file diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/source.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/source.kt new file mode 100644 index 00000000000..e75779943e3 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyDifferentCases/source.kt @@ -0,0 +1,9 @@ +package c + +import b.* + +fun bar(b: B) { + b.returnType() + b.parameter(null) + null.extensionReceiver() +} diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/output.txt b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/output.txt index 1e6e2903286..88ad824dedd 100644 --- a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/output.txt +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/output.txt @@ -1,4 +1,10 @@ +compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/source.kt:7:7: error: cannot access class 'a.A'. Check your module classpath for missing or conflicting dependencies + b.foo() + ^ compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/source.kt:10:21: error: type mismatch: inferred type is A but String was expected val x: String = b.foo() ^ +compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/source.kt:10:23: error: cannot access class 'a.A'. Check your module classpath for missing or conflicting dependencies + val x: String = b.foo() + ^ COMPILATION_ERROR \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java index 2ae9e7c3d45..1163c99abf6 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java @@ -291,7 +291,11 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir { } public void testMissingDependencySimple() throws Exception { - doTestBrokenKotlinLibrary("library", "a/A.class", "a/A$Inner.class"); + doTestBrokenKotlinLibrary("library", "a/A.class"); + } + + public void testMissingDependencyDifferentCases() throws Exception { + doTestBrokenKotlinLibrary("library", "a/A.class"); } public void testMissingDependencyConflictingLibraries() throws Exception { diff --git a/core/util.runtime/src/org/jetbrains/kotlin/utils/collections.kt b/core/util.runtime/src/org/jetbrains/kotlin/utils/collections.kt index c5ed3067b6c..8677202b0e7 100644 --- a/core/util.runtime/src/org/jetbrains/kotlin/utils/collections.kt +++ b/core/util.runtime/src/org/jetbrains/kotlin/utils/collections.kt @@ -41,8 +41,7 @@ fun Iterable.mapToIndex(): Map { return map } - -public inline fun MutableMap.getOrPutNullable(key: K, defaultValue: () -> V): V { +inline fun MutableMap.getOrPutNullable(key: K, defaultValue: () -> V): V { return if (!containsKey(key)) { val answer = defaultValue() put(key, answer) @@ -71,6 +70,10 @@ fun newHashSetWithExpectedSize(expectedSize: Int): HashSet { return HashSet(if (expectedSize < 3) 3 else expectedSize + expectedSize / 3 + 1) } +fun newLinkedHashSetWithExpectedSize(expectedSize: Int): LinkedHashSet { + return LinkedHashSet(if (expectedSize < 3) 3 else expectedSize + expectedSize / 3 + 1) +} + fun Collection.toReadOnlyList(): List = when (size) { 0 -> emptyList()