Support declarations returning object literals in ultra-light classes
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
+101
@@ -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();
|
||||
|
||||
}
|
||||
+29
-10
@@ -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()),
|
||||
|
||||
-19
@@ -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"
|
||||
|
||||
+35
-7
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user