From accf80a624716dfdc331a34b5515e0efe2b20334 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Wed, 9 Mar 2016 20:48:00 +0300 Subject: [PATCH] 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 --- .../library1/a1.kt | 9 ++ .../library1/b1.kt | 9 ++ .../library2/a2.kt | 9 ++ .../library2/b2.kt | 9 ++ .../output.txt | 7 ++ .../source.kt | 12 ++ .../missingDependencySimple/library/a.kt | 3 + .../missingDependencySimple/library/b.kt | 7 ++ .../missingDependencySimple/output.txt | 4 + .../missingDependencySimple/source.kt | 11 ++ ...singEnumReferencedInAnnotationArgument.txt | 4 +- ...ompileKotlinAgainstCustomBinariesTest.java | 13 ++ .../deserialization/DeserializedType.kt | 2 - .../deserialization/NotFoundClasses.kt | 117 ++++++++++++++++++ .../deserialization/TypeDeserializer.kt | 21 +--- .../serialization/deserialization/context.kt | 1 + .../DeserializedClassDescriptor.kt | 24 ++-- 17 files changed, 228 insertions(+), 34 deletions(-) create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/library1/a1.kt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/library1/b1.kt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/library2/a2.kt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/library2/b2.kt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/output.txt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/source.kt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/library/a.kt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/library/b.kt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/output.txt create mode 100644 compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/source.kt create mode 100644 core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/NotFoundClasses.kt diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/library1/a1.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/library1/a1.kt new file mode 100644 index 00000000000..9bf2d35bded --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/library1/a1.kt @@ -0,0 +1,9 @@ +package a + +class A { + inner class Inner +} + +class AA { + inner class Inner +} diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/library1/b1.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/library1/b1.kt new file mode 100644 index 00000000000..284e4c1af29 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/library1/b1.kt @@ -0,0 +1,9 @@ +package b + +import a.A +import a.AA + +interface B1 { + fun produceA(): A.Inner + fun produceAA(): AA.Inner +} diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/library2/a2.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/library2/a2.kt new file mode 100644 index 00000000000..0f8359b44ef --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/library2/a2.kt @@ -0,0 +1,9 @@ +package a + +class A { + inner class Inner +} + +class AA { + inner class Inner +} diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/library2/b2.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/library2/b2.kt new file mode 100644 index 00000000000..bbbf0213b89 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/library2/b2.kt @@ -0,0 +1,9 @@ +package b + +import a.A +import a.AA + +interface B2 { + fun consumeA(a: A.Inner) + fun consumeAA(a: AA.Inner) +} diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/output.txt b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/output.txt new file mode 100644 index 00000000000..8d0140057b2 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/output.txt @@ -0,0 +1,7 @@ +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:11:18: error: type mismatch: inferred type is AA.Inner but AA.Inner was expected + b2.consumeAA(b1.produceAA()) + ^ +COMPILATION_ERROR \ No newline at end of file diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/source.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/source.kt new file mode 100644 index 00000000000..e2679bfb912 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencyConflictingLibraries/source.kt @@ -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()) +} diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/library/a.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/library/a.kt new file mode 100644 index 00000000000..0ba6a65f47f --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/library/a.kt @@ -0,0 +1,3 @@ +package a + +interface A diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/library/b.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/library/b.kt new file mode 100644 index 00000000000..253a275fa1f --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/library/b.kt @@ -0,0 +1,7 @@ +package b + +import a.A + +interface B { + fun foo(): A +} diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/output.txt b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/output.txt new file mode 100644 index 00000000000..1e6e2903286 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/output.txt @@ -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 \ No newline at end of file diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/source.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/source.kt new file mode 100644 index 00000000000..5cf11b3c769 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/missingDependencySimple/source.kt @@ -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() +} diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/missingEnumReferencedInAnnotationArgument/missingEnumReferencedInAnnotationArgument.txt b/compiler/testData/compileKotlinAgainstCustomBinaries/missingEnumReferencedInAnnotationArgument/missingEnumReferencedInAnnotationArgument.txt index fe09641b246..8f45601d63d 100644 --- a/compiler/testData/compileKotlinAgainstCustomBinaries/missingEnumReferencedInAnnotationArgument/missingEnumReferencedInAnnotationArgument.txt +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/missingEnumReferencedInAnnotationArgument/missingEnumReferencedInAnnotationArgument.txt @@ -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 { diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java index d9dc8abb27b..2ae9e7c3d45 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.java @@ -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 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); diff --git a/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/DeserializedType.kt b/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/DeserializedType.kt index 4b69ceede5a..9f7676b7cd9 100644 --- a/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/DeserializedType.kt +++ b/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/DeserializedType.kt @@ -88,6 +88,4 @@ class DeserializedType( } private class PossiblyInnerTypeCapabilityImpl(override val possiblyInnerType: PossiblyInnerType?) : PossiblyInnerTypeCapability - - fun getPresentableText(): String = typeDeserializer.presentableTextForErrorType(typeProto) } diff --git a/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/NotFoundClasses.kt b/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/NotFoundClasses.kt new file mode 100644 index 00000000000..8bbe046603c --- /dev/null +++ b/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/NotFoundClasses.kt @@ -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) + + private val packageFragments = storageManager.createMemoizedFunction { fqName -> + EmptyPackageFragmentDescriptor(module, fqName) + } + + private val classes = storageManager.createMemoizedFunction { 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 = 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): 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 + } +} diff --git a/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/TypeDeserializer.kt b/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/TypeDeserializer.kt index c2b54cae945..8020edc7be1 100644 --- a/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/TypeDeserializer.kt +++ b/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/TypeDeserializer.kt @@ -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 ?: diff --git a/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/context.kt b/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/context.kt index 76cec4ba2af..26af1a93ffe 100644 --- a/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/context.kt +++ b/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/context.kt @@ -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) diff --git a/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/descriptors/DeserializedClassDescriptor.kt b/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/descriptors/DeserializedClassDescriptor.kt index 3ebfbd23b62..e8aee48bb8c 100644 --- a/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/descriptors/DeserializedClassDescriptor.kt +++ b/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/descriptors/DeserializedClassDescriptor.kt @@ -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 { - val result = ArrayList(classProto.supertypeCount) - val unresolved = ArrayList(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()