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.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<KotlinType, KotlinType> typePreprocessor;
private final TypeMappingConfiguration<Type> typeMappingConfiguration = new TypeMappingConfiguration<Type>() {
@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<Type> staticTypeMappingConfiguration = new TypeMappingConfiguration<Type>() {
@@ -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<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(
@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);
}
/**
@@ -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)
}
@@ -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 {
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"}
)
}
}
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 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 <T : Any> 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()),
@@ -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<KtFile>, builder: LightClassBuilder): LightClassDataHolder.ForFacade {
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 {
isAnnotationType -> "@interface"
isInterface -> "interface"
@@ -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)
}
}
}
}
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
)
}
}