diff --git a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/FirJavaElementFinder.kt.as34 b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/FirJavaElementFinder.kt.as34 new file mode 100644 index 00000000000..99a5961c076 --- /dev/null +++ b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/FirJavaElementFinder.kt.as34 @@ -0,0 +1,288 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.java + +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Pair +import com.intellij.psi.* +import com.intellij.psi.impl.cache.ModifierFlags +import com.intellij.psi.impl.compiled.ClsClassImpl +import com.intellij.psi.impl.compiled.ClsFileImpl +import com.intellij.psi.impl.file.PsiPackageImpl +import com.intellij.psi.impl.java.stubs.* +import com.intellij.psi.impl.java.stubs.impl.* +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.stubs.StubElement +import com.intellij.util.ArrayUtil +import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.declarations.FirClassLikeDeclaration +import org.jetbrains.kotlin.fir.declarations.FirRegularClass +import org.jetbrains.kotlin.fir.declarations.classId +import org.jetbrains.kotlin.fir.declarations.superConeTypes +import org.jetbrains.kotlin.fir.resolve.FirProvider +import org.jetbrains.kotlin.fir.resolve.directExpansionType +import org.jetbrains.kotlin.fir.resolve.toSymbol +import org.jetbrains.kotlin.fir.resolve.transformers.FirSupertypeResolverTransformer +import org.jetbrains.kotlin.fir.service +import org.jetbrains.kotlin.fir.symbols.StandardClassIds +import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol +import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType +import org.jetbrains.kotlin.resolve.jvm.KotlinFinderMarker + +class FirJavaElementFinder( + private val session: FirSession, + project: Project +) : PsiElementFinder(), KotlinFinderMarker { + private val psiManager = PsiManager.getInstance(project) + private val firProvider = session.service() + + override fun findPackage(qualifiedName: String): PsiPackage? { + if (firProvider.getClassNamesInPackage(FqName(qualifiedName)).isEmpty()) return null + return PsiPackageImpl(psiManager, qualifiedName) + } + + override fun getClasses(psiPackage: PsiPackage, scope: GlobalSearchScope): Array { + return firProvider + .getClassNamesInPackage(FqName(psiPackage.qualifiedName)) + .mapNotNull { findClass(psiPackage.qualifiedName + "." + it.identifier, scope) } + .toTypedArray() + } + + override fun findClasses(qualifiedName: String, scope: GlobalSearchScope): Array { + return findClass(qualifiedName, scope)?.let(::arrayOf) ?: emptyArray() + } + + override fun findClass(qualifiedName: String, scope: GlobalSearchScope): PsiClass? { + if (qualifiedName.endsWith(".")) return null + val classId = ClassId.topLevel(FqName(qualifiedName)) + + val firClass = + firProvider.getFirClassifierByFqName(classId) as? FirRegularClass + ?: return null + + val ktFile = firClass.psi?.containingFile as KtFile + val fileStub = createJavaFileStub(classId.packageFqName, listOf(ktFile)) + + return buildStub(firClass, fileStub).psi + } + + private fun buildStub(firClass: FirRegularClass, parent: StubElement<*>): PsiClassStub<*> { + val classId = firClass.classId + val stub = PsiClassStubImpl( + JavaStubElementTypes.CLASS, parent, classId.asSingleFqName().asString(), firClass.name.identifier, null, + PsiClassStubImpl.packFlags( + false, + firClass.classKind == ClassKind.INTERFACE, + firClass.classKind == ClassKind.ENUM_CLASS, false, false, + firClass.classKind == ClassKind.ANNOTATION_CLASS, false, false, + false, false + ) + ) + + PsiModifierListStubImpl(stub, firClass.packFlags()) + + newTypeParameterList( + stub, + firClass.typeParameters.map { Pair(it.name.asString(), arrayOf(CommonClassNames.JAVA_LANG_OBJECT)) } + ) + + if (firClass.supertypesComputationStatus != FirClassLikeDeclaration.SupertypesComputationStatus.COMPUTED) { + val firForSuperClassFile = firProvider.getFirClassifierContainerFile(classId) + FirSupertypeResolverTransformer().apply { + initFromFile(firForSuperClassFile) + transformRegularClass(firClass, null) + } + } + + stub.addSupertypesReferencesLists(firClass, session) + + for (nestedClass in firClass.declarations.filterIsInstance()) { + buildStub(nestedClass, stub) + } + + return stub + } + +} + +private fun FirRegularClass.packFlags(): Int { + var flags = when (visibility) { + Visibilities.PRIVATE -> ModifierFlags.PRIVATE_MASK + Visibilities.PROTECTED -> ModifierFlags.PROTECTED_MASK + Visibilities.PUBLIC -> ModifierFlags.PUBLIC_MASK + else -> ModifierFlags.PACKAGE_LOCAL_MASK + } + + flags = flags or when (modality) { + Modality.FINAL -> ModifierFlags.FINAL_MASK + Modality.ABSTRACT -> ModifierFlags.ABSTRACT_MASK + else -> 0 + } + + if (classId.isNestedClass && !isInner) { + flags = flags or ModifierFlags.STATIC_MASK + } + + return flags +} + +private fun PsiClassStubImpl<*>.addSupertypesReferencesLists(firRegularClass: FirRegularClass, session: FirSession) { + if (firRegularClass.supertypesComputationStatus == FirClassLikeDeclaration.SupertypesComputationStatus.COMPUTING) return + require(firRegularClass.superTypeRefs.all { it is FirResolvedTypeRef }) { + "Supertypes for light class $qualifiedName are being added too early" + } + + val isInterface = firRegularClass.classKind == ClassKind.INTERFACE + + val interfaceNames = mutableListOf() + var superName: String? = null + + for (superConeType in firRegularClass.superConeTypes) { + val supertypeFirClass = superConeType.toFirClass(session) ?: continue + + val canonicalString = superConeType.mapToCanonicalString(session) + + if (isInterface || supertypeFirClass.classKind == ClassKind.INTERFACE) { + interfaceNames.add(canonicalString) + } else { + superName = canonicalString + } + } + + if (this.isInterface) { + if (interfaceNames.isNotEmpty() && this.isAnnotationType) { + interfaceNames.remove(CommonClassNames.JAVA_LANG_ANNOTATION_ANNOTATION) + } + newReferenceList(JavaStubElementTypes.EXTENDS_LIST, this, ArrayUtil.toStringArray(interfaceNames)) + newReferenceList(JavaStubElementTypes.IMPLEMENTS_LIST, this, ArrayUtil.EMPTY_STRING_ARRAY) + } else { + if (superName == null || "java/lang/Object" == superName || this.isEnum && "java/lang/Enum" == superName) { + newReferenceList(JavaStubElementTypes.EXTENDS_LIST, this, ArrayUtil.EMPTY_STRING_ARRAY) + } else { + newReferenceList(JavaStubElementTypes.EXTENDS_LIST, this, arrayOf(superName)) + } + newReferenceList(JavaStubElementTypes.IMPLEMENTS_LIST, this, ArrayUtil.toStringArray(interfaceNames)) + } + +} + +private fun newReferenceList(type: JavaClassReferenceListElementType, parent: StubElement<*>, types: Array) { + PsiClassReferenceListStubImpl(type, parent, types) +} + +private fun newTypeParameterList(parent: StubElement<*>, parameters: List>>) { + val listStub = PsiTypeParameterListStubImpl(parent) + for (parameter in parameters) { + val parameterStub = PsiTypeParameterStubImpl(listStub, parameter.first) + newReferenceList(JavaStubElementTypes.EXTENDS_BOUND_LIST, parameterStub, parameter.second) + } +} + +private fun createJavaFileStub(packageFqName: FqName, files: Collection): PsiJavaFileStub { + val javaFileStub = PsiJavaFileStubImpl(packageFqName.asString(), /*compiled = */true) + javaFileStub.psiFactory = ClsStubPsiFactory.INSTANCE + + val fakeFile = object : ClsFileImpl(files.first().viewProvider) { + override fun getStub() = javaFileStub + + override fun getPackageName() = packageFqName.asString() + + override fun isPhysical() = false + + override fun getText(): String { + return files.singleOrNull()?.text ?: super.getText() + } + } + + javaFileStub.psi = fakeFile + return javaFileStub +} + +private fun ConeClassLikeType.toFirClass(session: FirSession): FirRegularClass? { + return when (this) { + is ConeAbbreviatedType -> this.directExpansionType(session)?.toFirClass(session) + else -> (this.lookupTag.toSymbol(session) as? FirClassSymbol)?.fir + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// JVM TYPE MAPPING +// TODO: reuse other type mapping implementations when possible +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// +private const val ERROR_TYPE_STUB = CommonClassNames.JAVA_LANG_OBJECT + +private fun ConeKotlinType.mapToCanonicalString(session: FirSession): String { + return when (this) { + is ConeClassLikeType -> mapToCanonicalString(session) + is ConeTypeParameterType -> lookupTag.name.asString() + is ConeTypeVariableType, is ConeFlexibleType, is ConeCapturedType, is ConeDefinitelyNotNullType, is ConeIntersectionType -> + error("Unexpected type: $this [${this::class}]") + } +} + +private fun ConeClassLikeType.mapToCanonicalString(session: FirSession): String { + return when (this) { + is ConeAbbreviatedType -> this.directExpansionType(session)?.mapToCanonicalString(session) ?: ERROR_TYPE_STUB + is ConeClassType -> mapToCanonicalString(session) + is ConeClassErrorType -> ERROR_TYPE_STUB + } +} + +private fun ConeClassType.mapToCanonicalString(session: FirSession): String { + if (lookupTag.classId == StandardClassIds.Array) { + return when (val typeProjection = typeArguments[0]) { + is ConeStarProjection -> CommonClassNames.JAVA_LANG_OBJECT + is ConeTypedProjection -> { + if (typeProjection.kind == ProjectionKind.IN) + CommonClassNames.JAVA_LANG_VOID + else + (typeProjection.type as ConeClassLikeType).mapToCanonicalString(session) + } + else -> ERROR_TYPE_STUB + } + "[]" + } + + val context = ConeTypeCheckerContext(false, session) + + with(context) { + val typeConstructor = typeConstructor() + typeConstructor.getPrimitiveType()?.let { return JvmPrimitiveType.get(it).wrapperFqName.asString() } + typeConstructor.getPrimitiveArrayType()?.let { return JvmPrimitiveType.get(it).javaKeywordName + "[]" } + val kotlinClassFqName = typeConstructor.getClassFqNameUnsafe() ?: return ERROR_TYPE_STUB + val mapped = JavaToKotlinClassMap.mapKotlinToJava(kotlinClassFqName)?.asSingleFqName() ?: kotlinClassFqName + + return mapped.toString() + + typeArguments.takeIf { it.isNotEmpty() } + ?.joinToString(separator = ", ", prefix = "<", postfix = ">") { it.mapToCanonicalString(session) } + .orEmpty() + } + +} + +private fun ConeKotlinTypeProjection.mapToCanonicalString(session: FirSession): String { + return when (this) { + is ConeStarProjection -> "?" + is ConeTypedProjection -> { + val wildcard = when (kind) { + ProjectionKind.STAR -> error("Should be handled in the case above") + ProjectionKind.IN -> "? super " + ProjectionKind.OUT -> "? extends " + ProjectionKind.INVARIANT -> "" + } + + wildcard + type.mapToCanonicalString(session) + } + else -> ERROR_TYPE_STUB + } +}