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:
+62
@@ -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()
|
||||
}
|
||||
}
|
||||
+3
@@ -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);
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+3
-8
@@ -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() }
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -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
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
package a
|
||||
|
||||
interface A
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
package b
|
||||
|
||||
import a.A
|
||||
|
||||
interface B {
|
||||
fun returnType(): A
|
||||
fun parameter(param: A?)
|
||||
}
|
||||
|
||||
fun A?.extensionReceiver() {}
|
||||
Vendored
+10
@@ -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
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
package c
|
||||
|
||||
import b.*
|
||||
|
||||
fun bar(b: B) {
|
||||
b.returnType()
|
||||
b.parameter(null)
|
||||
null.extensionReceiver()
|
||||
}
|
||||
+6
@@ -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
|
||||
+5
-1
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user