Do not create error types in deserialization on not found classes
For a class which cannot be resolved in the current deserialization session, create a special ClassDescriptor instance with an empty scope and put in the correct package under the current module. Codegen will perfectly map such class to its JVM signature (because only the precise FQ name is needed, which is available). For more details on this approach, see the issue description. #KT-4328 Fixed #KT-11497 Fixed
This commit is contained in:
+9
@@ -0,0 +1,9 @@
|
||||
package a
|
||||
|
||||
class A<T> {
|
||||
inner class Inner<X : Number, Y>
|
||||
}
|
||||
|
||||
class AA<T> {
|
||||
inner class Inner<U, V>
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package b
|
||||
|
||||
import a.A
|
||||
import a.AA
|
||||
|
||||
interface B1 {
|
||||
fun produceA(): A<String>.Inner<Int, Unit>
|
||||
fun produceAA(): AA<Int>.Inner<Unit, String>
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package a
|
||||
|
||||
class A<T, U: CharSequence, V> {
|
||||
inner class Inner<Z>
|
||||
}
|
||||
|
||||
class AA<T, U> {
|
||||
inner class Inner<V>
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package b
|
||||
|
||||
import a.A
|
||||
import a.AA
|
||||
|
||||
interface B2 {
|
||||
fun consumeA(a: A<Int, String, Double>.Inner<B2>)
|
||||
fun consumeAA(a: AA<Int, Unit>.Inner<String>)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
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: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())
|
||||
^
|
||||
COMPILATION_ERROR
|
||||
compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/source.kt
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
package c
|
||||
|
||||
import b.B1
|
||||
import b.B2
|
||||
|
||||
fun testA(b1: B1, b2: B2) {
|
||||
b2.consumeA(b1.produceA())
|
||||
}
|
||||
|
||||
fun testAA(b1: B1, b2: B2) {
|
||||
b2.consumeAA(b1.produceAA())
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package a
|
||||
|
||||
interface A
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package b
|
||||
|
||||
import a.A
|
||||
|
||||
interface B {
|
||||
fun foo(): A
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/source.kt:10:21: error: type mismatch: inferred type is A but String was expected
|
||||
val x: String = b.foo()
|
||||
^
|
||||
COMPILATION_ERROR
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package c
|
||||
|
||||
import b.B
|
||||
|
||||
fun bar(b: B) {
|
||||
// Implicit usage of (unavailable) a.A, return value is not used. It should still be an error as in Java
|
||||
b.foo()
|
||||
|
||||
// Return value is used but the type is incorrect, also an error
|
||||
val x: String = b.foo()
|
||||
}
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package test
|
||||
|
||||
public final annotation class Anno : kotlin.Annotation {
|
||||
public constructor Anno(/*0*/ e: [ERROR : test.E])
|
||||
public final val e: [ERROR : test.E]
|
||||
public constructor Anno(/*0*/ e: test.E)
|
||||
public final val e: test.E
|
||||
}
|
||||
|
||||
@test.Anno(e = Unresolved enum entry: test/E.ENTRY) public open class Class {
|
||||
|
||||
+13
@@ -290,6 +290,19 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir {
|
||||
doTestBrokenKotlinLibrary("library", "test/Super.class");
|
||||
}
|
||||
|
||||
public void testMissingDependencySimple() throws Exception {
|
||||
doTestBrokenKotlinLibrary("library", "a/A.class", "a/A$Inner.class");
|
||||
}
|
||||
|
||||
public void testMissingDependencyConflictingLibraries() throws Exception {
|
||||
File library1 = copyJarFileWithoutEntry(compileLibrary("library1"),
|
||||
"a/A.class", "a/A$Inner.class", "a/AA.class", "a/AA$Inner.class");
|
||||
File library2 = copyJarFileWithoutEntry(compileLibrary("library2"),
|
||||
"a/A.class", "a/A$Inner.class", "a/AA.class", "a/AA$Inner.class");
|
||||
Pair<String, ExitCode> output = compileKotlin("source.kt", tmpdir, library1, library2);
|
||||
KotlinTestUtils.assertEqualsToFile(new File(getTestDataDirectory(), "output.txt"), normalizeOutput(output));
|
||||
}
|
||||
|
||||
/*test source mapping generation when source info is absent*/
|
||||
public void testInlineFunWithoutDebugInfo() throws Exception {
|
||||
compileKotlin("sourceInline.kt", tmpdir);
|
||||
|
||||
-2
@@ -88,6 +88,4 @@ class DeserializedType(
|
||||
}
|
||||
|
||||
private class PossiblyInnerTypeCapabilityImpl(override val possiblyInnerType: PossiblyInnerType?) : PossiblyInnerTypeCapability
|
||||
|
||||
fun getPresentableText(): String = typeDeserializer.presentableTextForErrorType(typeProto)
|
||||
}
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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.serialization.deserialization
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorBase
|
||||
import org.jetbrains.kotlin.descriptors.impl.EmptyPackageFragmentDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.impl.TypeParameterDescriptorImpl
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
import org.jetbrains.kotlin.serialization.ProtoBuf
|
||||
import org.jetbrains.kotlin.storage.StorageManager
|
||||
import org.jetbrains.kotlin.types.ClassTypeConstructorImpl
|
||||
import org.jetbrains.kotlin.types.TypeConstructor
|
||||
import org.jetbrains.kotlin.types.Variance
|
||||
import org.jetbrains.kotlin.utils.toReadOnlyList
|
||||
|
||||
class NotFoundClasses(private val storageManager: StorageManager, private val module: ModuleDescriptor) {
|
||||
/**
|
||||
* @param typeParametersCount list of numbers of type parameters in this class and all its outer classes, starting from this class
|
||||
*/
|
||||
private data class ClassRequest(val classId: ClassId, val typeParametersCount: List<Int>)
|
||||
|
||||
private val packageFragments = storageManager.createMemoizedFunction<FqName, PackageFragmentDescriptor> { fqName ->
|
||||
EmptyPackageFragmentDescriptor(module, fqName)
|
||||
}
|
||||
|
||||
private val classes = storageManager.createMemoizedFunction<ClassRequest, ClassDescriptor> { request ->
|
||||
val (classId, typeParametersCount) = request
|
||||
|
||||
if (classId.isLocal) {
|
||||
throw UnsupportedOperationException("Unresolved local class: $classId")
|
||||
}
|
||||
|
||||
val container =
|
||||
if (classId.isNestedClass) get(classId.outerClassId, typeParametersCount.drop(1))
|
||||
else packageFragments(classId.packageFqName)
|
||||
|
||||
// Treat a class with a nested ClassId as inner for simplicity, otherwise the outer type cannot have generic arguments
|
||||
val isInner = classId.isNestedClass
|
||||
|
||||
MockClassDescriptor(storageManager, container, classId.shortClassName, isInner, typeParametersCount.firstOrNull() ?: 0)
|
||||
}
|
||||
|
||||
class MockClassDescriptor(
|
||||
storageManager: StorageManager,
|
||||
container: DeclarationDescriptor,
|
||||
name: Name,
|
||||
private val isInner: Boolean,
|
||||
numberOfDeclaredTypeParameters: Int
|
||||
) : ClassDescriptorBase(storageManager, container, name, SourceElement.NO_SOURCE) {
|
||||
private val typeParameters = (1..numberOfDeclaredTypeParameters).map { index ->
|
||||
TypeParameterDescriptorImpl.createWithDefaultBound(
|
||||
this, Annotations.EMPTY, false, Variance.INVARIANT, Name.identifier("T$index"), index
|
||||
)
|
||||
}
|
||||
|
||||
private val typeConstructor = ClassTypeConstructorImpl(
|
||||
this, Annotations.EMPTY, /* isFinal = */ true, typeParameters, setOf(module.builtIns.anyType)
|
||||
)
|
||||
|
||||
override fun getKind() = ClassKind.CLASS
|
||||
override fun getModality() = Modality.FINAL
|
||||
override fun getVisibility() = Visibilities.PUBLIC
|
||||
override fun getTypeConstructor() = typeConstructor
|
||||
override fun getDeclaredTypeParameters() = typeParameters
|
||||
override fun isInner() = isInner
|
||||
|
||||
override fun isCompanionObject() = false
|
||||
override fun isData() = false
|
||||
override fun getAnnotations() = Annotations.EMPTY
|
||||
|
||||
override fun getUnsubstitutedMemberScope() = MemberScope.Empty
|
||||
override fun getStaticScope() = MemberScope.Empty
|
||||
override fun getConstructors(): Collection<ConstructorDescriptor> = emptySet()
|
||||
override fun getUnsubstitutedPrimaryConstructor(): ConstructorDescriptor? = null
|
||||
override fun getCompanionObjectDescriptor(): ClassDescriptor? = null
|
||||
|
||||
override fun toString() = "class $name (not found)"
|
||||
}
|
||||
|
||||
// We create different ClassDescriptor instances for types with the same ClassId but different number of type arguments.
|
||||
// (This may happen when a class with the same FQ name is instantiated with different type arguments in different modules.)
|
||||
// It's better than creating just one descriptor because otherwise would fail in multiple places where it's asserted that
|
||||
// the number of type arguments in a type must be equal to the number of the type parameters of the class
|
||||
private fun get(classId: ClassId, typeParametersCount: List<Int>): ClassDescriptor {
|
||||
return classes(ClassRequest(classId, typeParametersCount))
|
||||
}
|
||||
|
||||
fun get(proto: ProtoBuf.Type, nameResolver: NameResolver, typeTable: TypeTable): TypeConstructor {
|
||||
val classId = nameResolver.getClassId(proto.className)
|
||||
val typeParametersCount = generateSequence(proto) { it.outerType(typeTable) }.map { it.argumentCount }.toMutableList()
|
||||
val classNestingLevel = generateSequence(classId) { if (it.isNestedClass) it.outerClassId else null }.count()
|
||||
while (typeParametersCount.size < classNestingLevel) {
|
||||
typeParametersCount.add(0)
|
||||
}
|
||||
return get(classId, typeParametersCount.toReadOnlyList()).typeConstructor
|
||||
}
|
||||
}
|
||||
+6
-15
@@ -71,10 +71,13 @@ class TypeDeserializer(
|
||||
|
||||
fun typeConstructor(proto: ProtoBuf.Type): TypeConstructor =
|
||||
when {
|
||||
proto.hasClassName() ->
|
||||
proto.hasClassName() -> {
|
||||
classDescriptors(proto.className)?.typeConstructor
|
||||
?: c.components.notFoundClasses.get(proto, c.nameResolver, c.typeTable)
|
||||
}
|
||||
proto.hasTypeParameter() ->
|
||||
typeParameterTypeConstructor(proto.typeParameter)
|
||||
?: ErrorUtils.createErrorType("Unknown type parameter ${proto.typeParameter}").constructor
|
||||
proto.hasTypeParameterName() -> {
|
||||
val container = c.containingDeclaration
|
||||
val typeParameters = when (container) {
|
||||
@@ -86,20 +89,8 @@ class TypeDeserializer(
|
||||
val parameter = typeParameters.find { it.name.asString() == name }
|
||||
parameter?.typeConstructor ?: ErrorUtils.createErrorType("Deserialized type parameter $name in $container").constructor
|
||||
}
|
||||
else ->
|
||||
null
|
||||
} ?: ErrorUtils.createErrorType(presentableTextForErrorType(proto)).constructor
|
||||
|
||||
internal fun presentableTextForErrorType(proto: ProtoBuf.Type): String = when {
|
||||
proto.hasClassName() ->
|
||||
c.nameResolver.getClassId(proto.className).asSingleFqName().asString()
|
||||
proto.hasTypeParameter() ->
|
||||
"Unknown type parameter ${proto.typeParameter}"
|
||||
proto.hasTypeParameterName() ->
|
||||
"Unknown type parameter ${c.nameResolver.getString(proto.typeParameterName)}"
|
||||
else ->
|
||||
"Unknown type"
|
||||
}
|
||||
else -> ErrorUtils.createErrorType("Unknown type").constructor
|
||||
}
|
||||
|
||||
private fun typeParameterTypeConstructor(typeParameterId: Int): TypeConstructor? =
|
||||
typeParameterDescriptors().get(typeParameterId)?.typeConstructor ?:
|
||||
|
||||
@@ -41,6 +41,7 @@ class DeserializationComponents(
|
||||
val additionalSupertypes: AdditionalSupertypes = AdditionalSupertypes.None
|
||||
) {
|
||||
val classDeserializer: ClassDeserializer = ClassDeserializer(this)
|
||||
val notFoundClasses: NotFoundClasses = NotFoundClasses(storageManager, moduleDescriptor)
|
||||
|
||||
fun deserializeClass(classId: ClassId): ClassDescriptor? = classDeserializer.deserializeClass(classId)
|
||||
|
||||
|
||||
+9
-15
@@ -25,8 +25,8 @@ import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.DescriptorFactory
|
||||
import org.jetbrains.kotlin.resolve.NonReportingOverrideStrategy
|
||||
import org.jetbrains.kotlin.resolve.OverridingStrategy
|
||||
import org.jetbrains.kotlin.resolve.OverridingUtil
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
|
||||
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.StaticScopeForKotlinEnum
|
||||
@@ -36,7 +36,6 @@ import org.jetbrains.kotlin.serialization.deserialization.*
|
||||
import org.jetbrains.kotlin.types.AbstractClassTypeConstructor
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeConstructor
|
||||
import org.jetbrains.kotlin.types.upperIfFlexible
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import org.jetbrains.kotlin.utils.singletonOrEmptyList
|
||||
import org.jetbrains.kotlin.utils.toReadOnlyList
|
||||
@@ -154,24 +153,19 @@ class DeserializedClassDescriptor(
|
||||
}
|
||||
|
||||
override fun computeSupertypes(): Collection<KotlinType> {
|
||||
val result = ArrayList<KotlinType>(classProto.supertypeCount)
|
||||
val unresolved = ArrayList<DeserializedType>(0)
|
||||
val result = classProto.supertypes(c.typeTable).map { supertypeProto ->
|
||||
c.typeDeserializer.type(supertypeProto)
|
||||
} + c.components.additionalSupertypes.forClass(this@DeserializedClassDescriptor)
|
||||
|
||||
for (supertypeProto in classProto.supertypes(c.typeTable)) {
|
||||
val supertype = c.typeDeserializer.type(supertypeProto)
|
||||
if (supertype.isError) {
|
||||
unresolved.add(supertype.upperIfFlexible() as? DeserializedType ?: error("Not a deserialized type: $supertype"))
|
||||
}
|
||||
else {
|
||||
result.add(supertype)
|
||||
}
|
||||
val unresolved = result.mapNotNull { supertype ->
|
||||
supertype.constructor.declarationDescriptor as? NotFoundClasses.MockClassDescriptor
|
||||
}
|
||||
|
||||
result.addAll(c.components.additionalSupertypes.forClass(this@DeserializedClassDescriptor))
|
||||
|
||||
if (unresolved.isNotEmpty()) {
|
||||
c.components.errorReporter.reportIncompleteHierarchy(
|
||||
this@DeserializedClassDescriptor, unresolved.map(DeserializedType::getPresentableText))
|
||||
this@DeserializedClassDescriptor,
|
||||
unresolved.map { it.classId.asSingleFqName().asString() }
|
||||
)
|
||||
}
|
||||
|
||||
return result.toReadOnlyList()
|
||||
|
||||
Reference in New Issue
Block a user