Support declarations returning object literals in ultra-light classes

This commit is contained in:
Denis Zharkov
2018-11-20 12:24:26 +03:00
parent 28f20a97f8
commit 7de8b4de4e
8 changed files with 250 additions and 46 deletions
@@ -10,6 +10,7 @@ import com.intellij.psi.PsiElement;
import kotlin.Pair; import kotlin.Pair;
import kotlin.Unit; import kotlin.Unit;
import kotlin.collections.CollectionsKt; import kotlin.collections.CollectionsKt;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.BuiltInsPackageFragment; import org.jetbrains.kotlin.builtins.BuiltInsPackageFragment;
@@ -89,6 +90,8 @@ public class KotlinTypeMapper {
private final LanguageVersionSettings languageVersionSettings; private final LanguageVersionSettings languageVersionSettings;
private final boolean isReleaseCoroutines; private final boolean isReleaseCoroutines;
private final boolean isIrBackend; private final boolean isIrBackend;
@Nullable
private final Function1<KotlinType, KotlinType> typePreprocessor;
private final TypeMappingConfiguration<Type> typeMappingConfiguration = new TypeMappingConfiguration<Type>() { private final TypeMappingConfiguration<Type> typeMappingConfiguration = new TypeMappingConfiguration<Type>() {
@NotNull @NotNull
@@ -121,6 +124,13 @@ public class KotlinTypeMapper {
public boolean releaseCoroutines() { public boolean releaseCoroutines() {
return isReleaseCoroutines; return isReleaseCoroutines;
} }
@Nullable
@Override
public KotlinType preprocessType(@NotNull KotlinType kotlinType) {
if (typePreprocessor == null) return null;
return typePreprocessor.invoke(kotlinType);
}
}; };
private static final TypeMappingConfiguration<Type> staticTypeMappingConfiguration = new TypeMappingConfiguration<Type>() { private static final TypeMappingConfiguration<Type> staticTypeMappingConfiguration = new TypeMappingConfiguration<Type>() {
@@ -151,8 +161,35 @@ public class KotlinTypeMapper {
public boolean releaseCoroutines() { public boolean releaseCoroutines() {
return false; 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<KotlinType, KotlinType> 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( public KotlinTypeMapper(
@NotNull BindingContext bindingContext, @NotNull BindingContext bindingContext,
@NotNull ClassBuilderMode classBuilderMode, @NotNull ClassBuilderMode classBuilderMode,
@@ -162,14 +199,7 @@ public class KotlinTypeMapper {
@NotNull LanguageVersionSettings languageVersionSettings, @NotNull LanguageVersionSettings languageVersionSettings,
boolean isIrBackend boolean isIrBackend
) { ) {
this.bindingContext = bindingContext; this(bindingContext, classBuilderMode, incompatibleClassTracker, moduleName, jvmTarget, languageVersionSettings, isIrBackend, null);
this.classBuilderMode = classBuilderMode;
this.incompatibleClassTracker = incompatibleClassTracker;
this.moduleName = moduleName;
this.jvmTarget = jvmTarget;
this.languageVersionSettings = languageVersionSettings;
this.isReleaseCoroutines = languageVersionSettings.supportsFeature(LanguageFeature.ReleaseCoroutines);
this.isIrBackend = isIrBackend;
} }
/** /**
@@ -21,13 +21,18 @@ import org.jetbrains.kotlin.codegen.state.IncompatibleClassTracker
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.config.JvmTarget import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.descriptors.ValueDescriptor import org.jetbrains.kotlin.descriptors.ValueDescriptor
import org.jetbrains.kotlin.load.kotlin.TypeMappingMode import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtTypeParameterListOwner import org.jetbrains.kotlin.psi.KtTypeParameterListOwner
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.types.KotlinType 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 import java.text.StringCharacterIterator
internal fun buildTypeParameterList( internal fun buildTypeParameterList(
@@ -105,5 +110,38 @@ internal fun typeMapper(support: UltraLightSupport): KotlinTypeMapper = KotlinTy
IncompatibleClassTracker.DoNothing, support.moduleName, IncompatibleClassTracker.DoNothing, support.moduleName,
JvmTarget.JVM_1_8, JvmTarget.JVM_1_8,
KotlinTypeMapper.LANGUAGE_VERSION_SETTINGS_DEFAULT, // TODO use proper LanguageVersionSettings 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)
}
@@ -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<java.lang.Object> initChild;
@null()
public C(@null() int);
@null()
public final int getY();
@org.jetbrains.annotations.NotNull()
public final kotlin.jvm.functions.Function0<java.lang.Object> 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();
}
@@ -1,22 +1,21 @@
/** should load cls */
class Prop { class Prop {
private val someProp = object { } private val someProp = object { }
} }
/** should load cls */
class Fun { class Fun {
private fun someFun() = object { } private fun someFun() = object { }
} }
/** should load cls */
class Array { class ArrayOfAnonymous {
val a1 = arrayOf( val a1 = arrayOf(
object { val fy = "text"} object { val fy = "text"}
) )
} }
/** should load cls */
private class C(val y: Int) { private class C(val y: Int) {
val initChild = { -> val initChild = { ->
object { object {
@@ -28,14 +27,34 @@ private class C(val y: Int) {
} }
class Super { abstract class Super {
val a: Any? abstract val a: Any?
} }
/** should load cls */
class Sub { class Sub : Super() {
override val a = arrayOf( override val a = arrayOf(
object { val fy = "text"} object { val fy = "text"}
) )
} }
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() {}
}
}
@@ -40,6 +40,9 @@ interface TypeMappingConfiguration<out T : Any> {
fun getPredefinedTypeForClass(classDescriptor: ClassDescriptor): T? fun getPredefinedTypeForClass(classDescriptor: ClassDescriptor): T?
fun getPredefinedInternalNameForClass(classDescriptor: ClassDescriptor): String? fun getPredefinedInternalNameForClass(classDescriptor: ClassDescriptor): String?
fun processErrorType(kotlinType: KotlinType, descriptor: ClassDescriptor) 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 fun releaseCoroutines(): Boolean
} }
@@ -54,6 +57,10 @@ fun <T : Any> mapType(
writeGenericType: (KotlinType, T, TypeMappingMode) -> Unit = DO_NOTHING_3, writeGenericType: (KotlinType, T, TypeMappingMode) -> Unit = DO_NOTHING_3,
isIrBackend: Boolean isIrBackend: Boolean
): T { ): T {
typeMappingConfiguration.preprocessType(kotlinType)?.let { newType ->
return mapType(newType, factory, mode, typeMappingConfiguration, descriptorTypeWriter, writeGenericType, isIrBackend)
}
if (kotlinType.isSuspendFunctionType) { if (kotlinType.isSuspendFunctionType) {
return mapType( return mapType(
transformSuspendFunctionToRuntimeFunctionType(kotlinType, typeMappingConfiguration.releaseCoroutines()), transformSuspendFunctionToRuntimeFunctionType(kotlinType, typeMappingConfiguration.releaseCoroutines()),
@@ -25,7 +25,6 @@ import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiModificationTracker import com.intellij.psi.util.PsiModificationTracker
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.containers.ConcurrentFactoryMap import com.intellij.util.containers.ConcurrentFactoryMap
import org.jetbrains.kotlin.asJava.LightClassBuilder import org.jetbrains.kotlin.asJava.LightClassBuilder
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport 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.name.Name
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier 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.BindingContext
import org.jetbrains.kotlin.resolve.annotations.JVM_STATIC_ANNOTATION_FQ_NAME import org.jetbrains.kotlin.resolve.annotations.JVM_STATIC_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
@@ -157,9 +155,6 @@ class IDELightClassGenerationSupport(private val project: Project) : LightClassG
} }
if (declaration is KtCallableDeclaration) { if (declaration is KtCallableDeclaration) {
declaration.valueParameters.mapNotNull { findTooComplexDeclaration(it) }.firstOrNull()?.let { return it } 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) { if (declaration.typeReference?.hasModifier(KtTokens.SUSPEND_KEYWORD) == true) {
return declaration.typeReference 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 { private fun implementsKotlinCollection(classOrObject: KtClassOrObject): Boolean {
if (classOrObject.superTypeListEntries.isEmpty()) return false if (classOrObject.superTypeListEntries.isEmpty()) return false
@@ -223,7 +205,6 @@ class IDELightClassGenerationSupport(private val project: Project) : LightClassG
} }
} }
override fun createDataHolderForFacade(files: Collection<KtFile>, builder: LightClassBuilder): LightClassDataHolder.ForFacade { override fun createDataHolderForFacade(files: Collection<KtFile>, builder: LightClassBuilder): LightClassDataHolder.ForFacade {
assert(!files.isEmpty()) { "No files in facade" } assert(!files.isEmpty()) { "No files in facade" }
@@ -110,7 +110,7 @@ object UltraLightChecker {
} + } +
";" ";"
private fun PsiClass.renderClass(): String { fun PsiClass.renderClass(): String {
val classWord = when { val classWord = when {
isAnnotationType -> "@interface" isAnnotationType -> "@interface"
isInterface -> "interface" isInterface -> "interface"
@@ -6,10 +6,13 @@
package org.jetbrains.kotlin.asJava.classes package org.jetbrains.kotlin.asJava.classes
import com.intellij.testFramework.LightProjectDescriptor import com.intellij.testFramework.LightProjectDescriptor
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
import org.jetbrains.kotlin.idea.perf.UltraLightChecker import org.jetbrains.kotlin.idea.perf.UltraLightChecker
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File import java.io.File
abstract class AbstractUltraLightClassLoadingTest : KotlinLightCodeInsightFixtureTestCase() { abstract class AbstractUltraLightClassLoadingTest : KotlinLightCodeInsightFixtureTestCase() {
@@ -17,17 +20,42 @@ abstract class AbstractUltraLightClassLoadingTest : KotlinLightCodeInsightFixtur
fun doTest(testDataPath: String) { fun doTest(testDataPath: String) {
val file = myFixture.addFileToProject(testDataPath, File(testDataPath).readText()) as KtFile 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)) { for (ktClass in UltraLightChecker.allClasses(file)) {
val clsLoadingExpected = ktClass.docComment?.text?.contains("should load cls") == true
val ultraLightClass = UltraLightChecker.checkClassEquivalence(ktClass) val ultraLightClass = UltraLightChecker.checkClassEquivalence(ktClass)
if (ultraLightClass != null) { if (ultraLightClass != null) {
assertEquals( checkClassLoadingExpectations(ktClass, ultraLightClass)
"Cls-loaded status differs from expected for ${ultraLightClass.qualifiedName}",
clsLoadingExpected,
ultraLightClass.isClsDelegateLoaded
)
} }
} }
} }
}
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
)
}
}