From 7de8b4de4e2dd48f279d4c146aa2559ff6c520ec Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Tue, 20 Nov 2018 12:24:26 +0300 Subject: [PATCH] Support declarations returning object literals in ultra-light classes --- .../codegen/state/KotlinTypeMapper.java | 46 ++++++-- .../kotlin/asJava/classes/ultraLightUtils.kt | 40 ++++++- .../inferringAnonymousObjectTypes.java | 101 ++++++++++++++++++ .../inferringAnonymousObjectTypes.kt | 39 +++++-- .../load/kotlin/typeSignatureMapping.kt | 7 ++ .../resolve/IDELightClassGenerationSupport.kt | 19 ---- .../kotlin/idea/perf/UltraLightChecker.kt | 2 +- .../AbstractUltraLightClassLoadingTest.kt | 42 ++++++-- 8 files changed, 250 insertions(+), 46 deletions(-) create mode 100644 compiler/testData/asJava/ultraLightClasses/inferringAnonymousObjectTypes.java diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java index 222dcc06ff8..bc817d02801 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java @@ -10,6 +10,7 @@ import com.intellij.psi.PsiElement; import kotlin.Pair; import kotlin.Unit; import kotlin.collections.CollectionsKt; +import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.builtins.BuiltInsPackageFragment; @@ -89,6 +90,8 @@ public class KotlinTypeMapper { private final LanguageVersionSettings languageVersionSettings; private final boolean isReleaseCoroutines; private final boolean isIrBackend; + @Nullable + private final Function1 typePreprocessor; private final TypeMappingConfiguration typeMappingConfiguration = new TypeMappingConfiguration() { @NotNull @@ -121,6 +124,13 @@ public class KotlinTypeMapper { public boolean releaseCoroutines() { return isReleaseCoroutines; } + + @Nullable + @Override + public KotlinType preprocessType(@NotNull KotlinType kotlinType) { + if (typePreprocessor == null) return null; + return typePreprocessor.invoke(kotlinType); + } }; private static final TypeMappingConfiguration staticTypeMappingConfiguration = new TypeMappingConfiguration() { @@ -151,8 +161,35 @@ public class KotlinTypeMapper { public boolean releaseCoroutines() { return false; } + + @Nullable + @Override + public KotlinType preprocessType(@NotNull KotlinType kotlinType) { + return null; + } }; + public KotlinTypeMapper( + @NotNull BindingContext bindingContext, + @NotNull ClassBuilderMode classBuilderMode, + @NotNull IncompatibleClassTracker incompatibleClassTracker, + @NotNull String moduleName, + @NotNull JvmTarget jvmTarget, + @NotNull LanguageVersionSettings languageVersionSettings, + boolean isIrBackend, + @Nullable Function1 typePreprocessor + ) { + this.bindingContext = bindingContext; + this.classBuilderMode = classBuilderMode; + this.incompatibleClassTracker = incompatibleClassTracker; + this.moduleName = moduleName; + this.jvmTarget = jvmTarget; + this.languageVersionSettings = languageVersionSettings; + this.isReleaseCoroutines = languageVersionSettings.supportsFeature(LanguageFeature.ReleaseCoroutines); + this.isIrBackend = isIrBackend; + this.typePreprocessor = typePreprocessor; + } + public KotlinTypeMapper( @NotNull BindingContext bindingContext, @NotNull ClassBuilderMode classBuilderMode, @@ -162,14 +199,7 @@ public class KotlinTypeMapper { @NotNull LanguageVersionSettings languageVersionSettings, boolean isIrBackend ) { - this.bindingContext = bindingContext; - this.classBuilderMode = classBuilderMode; - this.incompatibleClassTracker = incompatibleClassTracker; - this.moduleName = moduleName; - this.jvmTarget = jvmTarget; - this.languageVersionSettings = languageVersionSettings; - this.isReleaseCoroutines = languageVersionSettings.supportsFeature(LanguageFeature.ReleaseCoroutines); - this.isIrBackend = isIrBackend; + this(bindingContext, classBuilderMode, incompatibleClassTracker, moduleName, jvmTarget, languageVersionSettings, isIrBackend, null); } /** diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/ultraLightUtils.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/ultraLightUtils.kt index a5a2563ed9d..787be706572 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/ultraLightUtils.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/ultraLightUtils.kt @@ -21,13 +21,18 @@ import org.jetbrains.kotlin.codegen.state.IncompatibleClassTracker import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.config.JvmTarget import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.descriptors.ValueDescriptor import org.jetbrains.kotlin.load.kotlin.TypeMappingMode import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtTypeParameterListOwner import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeProjectionImpl +import org.jetbrains.kotlin.types.replace +import org.jetbrains.kotlin.types.typeUtil.supertypes import java.text.StringCharacterIterator internal fun buildTypeParameterList( @@ -105,5 +110,38 @@ internal fun typeMapper(support: UltraLightSupport): KotlinTypeMapper = KotlinTy IncompatibleClassTracker.DoNothing, support.moduleName, JvmTarget.JVM_1_8, KotlinTypeMapper.LANGUAGE_VERSION_SETTINGS_DEFAULT, // TODO use proper LanguageVersionSettings - false + false, + KotlinType::cleanFromAnonymousTypes ) + +// Returns null when type is unchanged +fun KotlinType.cleanFromAnonymousTypes(): KotlinType? { + val returnTypeClass = constructor.declarationDescriptor as? ClassDescriptor ?: return null + if (DescriptorUtils.isAnonymousObject(returnTypeClass)) { + // We choose just the first supertype because: + // - In public declarations, object literals should always have a single supertype (otherwise it's an error) + // - For private declarations, they might have more than one supertype + // but it looks like it's not important how we choose a representative for them + val representative = returnTypeClass.defaultType.supertypes().firstOrNull() ?: return null + return representative.cleanFromAnonymousTypes() ?: representative + } + + if (arguments.isEmpty()) return null + + var wasUpdates = false + + val newArguments = arguments.map { typeProjection -> + val updatedType = + typeProjection.takeUnless { it.isStarProjection } + ?.type?.cleanFromAnonymousTypes() + ?: return@map typeProjection + + wasUpdates = true + + TypeProjectionImpl(typeProjection.projectionKind, updatedType) + } + + if (!wasUpdates) return null + + return replace(newArguments = newArguments) +} diff --git a/compiler/testData/asJava/ultraLightClasses/inferringAnonymousObjectTypes.java b/compiler/testData/asJava/ultraLightClasses/inferringAnonymousObjectTypes.java new file mode 100644 index 00000000000..fb451707d13 --- /dev/null +++ b/compiler/testData/asJava/ultraLightClasses/inferringAnonymousObjectTypes.java @@ -0,0 +1,101 @@ +public final class Prop /* Prop*/ { + @org.jetbrains.annotations.NotNull() + private final java.lang.Object someProp; + + @null() + public Prop(); + +} + +public final class Fun /* Fun*/ { + @null() + public Fun(); + + @org.jetbrains.annotations.NotNull() + private final java.lang.Object someFun(); + +} + +public final class ArrayOfAnonymous /* ArrayOfAnonymous*/ { + @org.jetbrains.annotations.NotNull() + private final java.lang.Object[] a1; + + @null() + public ArrayOfAnonymous(); + + @org.jetbrains.annotations.NotNull() + public final java.lang.Object[] getA1(); + +} + +final class C /* C*/ { + @null() + private final int y; + + @org.jetbrains.annotations.NotNull() + private final kotlin.jvm.functions.Function0 initChild; + + @null() + public C(@null() int); + + @null() + public final int getY(); + + @org.jetbrains.annotations.NotNull() + public final kotlin.jvm.functions.Function0 getInitChild(); + +} + +public abstract class Super /* Super*/ { + @null() + public Super(); + + @org.jetbrains.annotations.Nullable() + public abstract java.lang.Object getA(); + +} + +public final class Sub /* Sub*/ extends Super { + @org.jetbrains.annotations.NotNull() + private final java.lang.Object[] a; + + @null() + public Sub(); + + @org.jetbrains.annotations.NotNull() + public java.lang.Object[] getA(); + +} + +public final class ValidPublicSupertype /* ValidPublicSupertype*/ { + @org.jetbrains.annotations.NotNull() + private final java.lang.Runnable x; + + @null() + public ValidPublicSupertype(); + + @org.jetbrains.annotations.NotNull() + public final java.lang.Runnable bar(); + + @org.jetbrains.annotations.NotNull() + public final java.lang.Runnable getX(); + +} + +public abstract interface I /* I*/ { +} + +public final class InvalidPublicSupertype /* InvalidPublicSupertype*/ { + @org.jetbrains.annotations.NotNull() + private final java.lang.Runnable x; + + @null() + public InvalidPublicSupertype(); + + @org.jetbrains.annotations.NotNull() + public final java.lang.Runnable bar(); + + @org.jetbrains.annotations.NotNull() + public final java.lang.Runnable getX(); + +} diff --git a/compiler/testData/asJava/ultraLightClasses/inferringAnonymousObjectTypes.kt b/compiler/testData/asJava/ultraLightClasses/inferringAnonymousObjectTypes.kt index e7f75f2caa5..10b5d902c1f 100644 --- a/compiler/testData/asJava/ultraLightClasses/inferringAnonymousObjectTypes.kt +++ b/compiler/testData/asJava/ultraLightClasses/inferringAnonymousObjectTypes.kt @@ -1,22 +1,21 @@ -/** should load cls */ class Prop { private val someProp = object { } } -/** should load cls */ + class Fun { private fun someFun() = object { } } -/** should load cls */ -class Array { + +class ArrayOfAnonymous { val a1 = arrayOf( object { val fy = "text"} ) } -/** should load cls */ + private class C(val y: Int) { val initChild = { -> object { @@ -28,14 +27,34 @@ private class C(val y: Int) { } -class Super { - val a: Any? +abstract class Super { + abstract val a: Any? } -/** should load cls */ -class Sub { + +class Sub : Super() { override val a = arrayOf( object { val fy = "text"} ) -} \ No newline at end of file +} +class ValidPublicSupertype { + val x = object : Runnable { + override fun run() {} + } + + fun bar() = object : Runnable { + override fun run() {} + } +} + +interface I +class InvalidPublicSupertype { + val x = object : Runnable, I { + override fun run() {} + } + + fun bar() = object : Runnable, I { + override fun run() {} + } +} diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/typeSignatureMapping.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/typeSignatureMapping.kt index cc45938b803..5672c22e015 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/typeSignatureMapping.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/typeSignatureMapping.kt @@ -40,6 +40,9 @@ interface TypeMappingConfiguration { fun getPredefinedTypeForClass(classDescriptor: ClassDescriptor): T? fun getPredefinedInternalNameForClass(classDescriptor: ClassDescriptor): String? fun processErrorType(kotlinType: KotlinType, descriptor: ClassDescriptor) + // returns null when type doesn't need to be preprocessed + fun preprocessType(kotlinType: KotlinType): KotlinType? = null + fun releaseCoroutines(): Boolean } @@ -54,6 +57,10 @@ fun mapType( writeGenericType: (KotlinType, T, TypeMappingMode) -> Unit = DO_NOTHING_3, isIrBackend: Boolean ): T { + typeMappingConfiguration.preprocessType(kotlinType)?.let { newType -> + return mapType(newType, factory, mode, typeMappingConfiguration, descriptorTypeWriter, writeGenericType, isIrBackend) + } + if (kotlinType.isSuspendFunctionType) { return mapType( transformSuspendFunctionToRuntimeFunctionType(kotlinType, typeMappingConfiguration.releaseCoroutines()), diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDELightClassGenerationSupport.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDELightClassGenerationSupport.kt index 258d689ba9e..f4bad05bd8d 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDELightClassGenerationSupport.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDELightClassGenerationSupport.kt @@ -25,7 +25,6 @@ import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValuesManager import com.intellij.psi.util.PsiModificationTracker -import com.intellij.psi.util.PsiTreeUtil import com.intellij.util.containers.ConcurrentFactoryMap import org.jetbrains.kotlin.asJava.LightClassBuilder import org.jetbrains.kotlin.asJava.LightClassGenerationSupport @@ -53,7 +52,6 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier -import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.annotations.JVM_STATIC_ANNOTATION_FQ_NAME import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe @@ -157,9 +155,6 @@ class IDELightClassGenerationSupport(private val project: Project) : LightClassG } if (declaration is KtCallableDeclaration) { declaration.valueParameters.mapNotNull { findTooComplexDeclaration(it) }.firstOrNull()?.let { return it } - if (declaration.typeReference == null && (declaration as? KtFunction)?.hasBlockBody() != true) { - findPotentiallyReturnedAnonymousObject(declaration)?.let { return it } - } if (declaration.typeReference?.hasModifier(KtTokens.SUSPEND_KEYWORD) == true) { return declaration.typeReference } @@ -172,19 +167,6 @@ class IDELightClassGenerationSupport(private val project: Project) : LightClassG } - private fun findPotentiallyReturnedAnonymousObject(declaration: KtDeclaration): KtObjectDeclaration? { - val spine = declaration.containingKtFile.stubbedSpine - for (i in 0 until spine.stubCount) { - if (spine.getStubType(i) == KtStubElementTypes.OBJECT_DECLARATION) { - val obj = spine.getStubPsi(i) as KtObjectDeclaration - if (obj.isObjectLiteral() && PsiTreeUtil.isContextAncestor(declaration, obj, true)) { - return obj - } - } - } - return null - } - private fun implementsKotlinCollection(classOrObject: KtClassOrObject): Boolean { if (classOrObject.superTypeListEntries.isEmpty()) return false @@ -223,7 +205,6 @@ class IDELightClassGenerationSupport(private val project: Project) : LightClassG } } - override fun createDataHolderForFacade(files: Collection, builder: LightClassBuilder): LightClassDataHolder.ForFacade { assert(!files.isEmpty()) { "No files in facade" } diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/perf/UltraLightChecker.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/perf/UltraLightChecker.kt index 8212bf4c26b..7da9cf42b91 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/perf/UltraLightChecker.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/perf/UltraLightChecker.kt @@ -110,7 +110,7 @@ object UltraLightChecker { } + ";" - private fun PsiClass.renderClass(): String { + fun PsiClass.renderClass(): String { val classWord = when { isAnnotationType -> "@interface" isInterface -> "interface" diff --git a/idea/tests/org/jetbrains/kotlin/asJava/classes/AbstractUltraLightClassLoadingTest.kt b/idea/tests/org/jetbrains/kotlin/asJava/classes/AbstractUltraLightClassLoadingTest.kt index 87b29c37f80..9f93679e79a 100644 --- a/idea/tests/org/jetbrains/kotlin/asJava/classes/AbstractUltraLightClassLoadingTest.kt +++ b/idea/tests/org/jetbrains/kotlin/asJava/classes/AbstractUltraLightClassLoadingTest.kt @@ -6,10 +6,13 @@ package org.jetbrains.kotlin.asJava.classes import com.intellij.testFramework.LightProjectDescriptor +import org.jetbrains.kotlin.asJava.LightClassGenerationSupport import org.jetbrains.kotlin.idea.perf.UltraLightChecker import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor +import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.test.KotlinTestUtils import java.io.File abstract class AbstractUltraLightClassLoadingTest : KotlinLightCodeInsightFixtureTestCase() { @@ -17,17 +20,42 @@ abstract class AbstractUltraLightClassLoadingTest : KotlinLightCodeInsightFixtur fun doTest(testDataPath: String) { val file = myFixture.addFileToProject(testDataPath, File(testDataPath).readText()) as KtFile + + val expectedTextFile = File(testDataPath.replaceFirst("\\.kt\$".toRegex(), ".java")) + if (expectedTextFile.exists()) { + val renderedResult = + UltraLightChecker.allClasses(file).mapNotNull { ktClass -> + LightClassGenerationSupport.getInstance(ktClass.project).createUltraLightClass(ktClass)?.let { it to ktClass } + }.joinToString("\n\n") { (ultraLightClass, ktClass) -> + with(UltraLightChecker) { + ultraLightClass.renderClass().also { + checkClassLoadingExpectations(ktClass, ultraLightClass) + } + } + } + + KotlinTestUtils.assertEqualsToFile(expectedTextFile, renderedResult) + return + } + for (ktClass in UltraLightChecker.allClasses(file)) { - val clsLoadingExpected = ktClass.docComment?.text?.contains("should load cls") == true val ultraLightClass = UltraLightChecker.checkClassEquivalence(ktClass) if (ultraLightClass != null) { - assertEquals( - "Cls-loaded status differs from expected for ${ultraLightClass.qualifiedName}", - clsLoadingExpected, - ultraLightClass.isClsDelegateLoaded - ) + checkClassLoadingExpectations(ktClass, ultraLightClass) } } } -} \ No newline at end of file + + private fun checkClassLoadingExpectations( + ktClass: KtClassOrObject, + ultraLightClass: KtUltraLightClass + ) { + val clsLoadingExpected = ktClass.docComment?.text?.contains("should load cls") == true + assertEquals( + "Cls-loaded status differs from expected for ${ultraLightClass.qualifiedName}", + clsLoadingExpected, + ultraLightClass.isClsDelegateLoaded + ) + } +}