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()