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
This commit is contained in:
Alexander Udalov
2016-03-10 19:07:40 +03:00
parent accf80a624
commit db5e00bcc0
14 changed files with 141 additions and 13 deletions
@@ -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<ClassId> {
val result: MutableSet<ClassId> = 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()
}
}
@@ -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");
@@ -41,6 +41,8 @@ public interface ErrorsJvm {
DiagnosticFactory1<PsiElement, ConflictingJvmDeclarationsData> CONFLICTING_INHERITED_JVM_DECLARATIONS =
DiagnosticFactory1.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory1<PsiElement, FqName> MISSING_DEPENDENCY_CLASS = DiagnosticFactory1.create(ERROR);
DiagnosticFactory0<KtDeclaration> OVERRIDE_CANNOT_BE_STATIC = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE);
DiagnosticFactory0<KtDeclaration> JVM_STATIC_NOT_IN_OBJECT = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE);
DiagnosticFactory0<KtDeclaration> JVM_STATIC_ON_CONST_OR_JVM_FIELD = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE);
@@ -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(
@@ -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
@@ -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<Nothing> 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() }
}
}
@@ -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<String>.Inner<Int, Unit> but A<Int, String, Double>.Inner<B2> 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<Int>.Inner<Unit, String> but AA<Int, Unit>.Inner<String> 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
@@ -0,0 +1,3 @@
package a
interface A
@@ -0,0 +1,10 @@
package b
import a.A
interface B {
fun returnType(): A
fun parameter(param: A?)
}
fun A?.extensionReceiver() {}
@@ -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
@@ -0,0 +1,9 @@
package c
import b.*
fun bar(b: B) {
b.returnType()
b.parameter(null)
null.extensionReceiver()
}
@@ -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
@@ -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 {
@@ -41,8 +41,7 @@ fun <K> Iterable<K>.mapToIndex(): Map<K, Int> {
return map
}
public inline fun <K, V> MutableMap<K, V>.getOrPutNullable(key: K, defaultValue: () -> V): V {
inline fun <K, V> MutableMap<K, V>.getOrPutNullable(key: K, defaultValue: () -> V): V {
return if (!containsKey(key)) {
val answer = defaultValue()
put(key, answer)
@@ -71,6 +70,10 @@ fun <E> newHashSetWithExpectedSize(expectedSize: Int): HashSet<E> {
return HashSet(if (expectedSize < 3) 3 else expectedSize + expectedSize / 3 + 1)
}
fun <E> newLinkedHashSetWithExpectedSize(expectedSize: Int): LinkedHashSet<E> {
return LinkedHashSet(if (expectedSize < 3) 3 else expectedSize + expectedSize / 3 + 1)
}
fun <T> Collection<T>.toReadOnlyList(): List<T> =
when (size) {
0 -> emptyList()