diff --git a/analysis/decompiled/light-classes-for-decompiled/build.gradle.kts b/analysis/decompiled/light-classes-for-decompiled/build.gradle.kts index 6a376e39fc1..6be903c4f49 100644 --- a/analysis/decompiled/light-classes-for-decompiled/build.gradle.kts +++ b/analysis/decompiled/light-classes-for-decompiled/build.gradle.kts @@ -5,6 +5,9 @@ plugins { dependencies { api(project(":compiler:psi")) + api(project(":analysis:analysis-api")) + api(project(":analysis:analysis-api-providers")) + api(project(":analysis:project-structure")) api(project(":analysis:decompiled:decompiler-to-psi")) api(project(":compiler:light-classes")) implementation(intellijCore()) diff --git a/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/DecompiledPsiDeclarationProvider.kt b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/DecompiledPsiDeclarationProvider.kt new file mode 100644 index 00000000000..315c8534513 --- /dev/null +++ b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/DecompiledPsiDeclarationProvider.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2010-2022 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.analysis.providers + +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.analysis.api.symbols.* + +public object DecompiledPsiDeclarationProvider { + public fun findPsi(ktSymbol: KtSymbol, project: Project): PsiElement? { + return when (ktSymbol) { + is KtConstructorSymbol -> providePsiForConstructor(ktSymbol, project) + is KtFunctionLikeSymbol -> providePsiForFunction(ktSymbol, project) + is KtEnumEntrySymbol -> providePsiForEnumEntry(ktSymbol, project) + is KtVariableLikeSymbol -> providePsiForProperty(ktSymbol, project) + is KtClassLikeSymbol -> providePsiForClass(ktSymbol, project) + else -> null + } + } + + private fun providePsiForConstructor( + constructorSymbol: KtConstructorSymbol, + project: Project + ): PsiElement? { + val classId = constructorSymbol.containingClassIdIfNonLocal ?: return null + val psiClass = project.createPsiDeclarationProvider(constructorSymbol.scope(project)) + ?.getClassesByClassId(classId) + ?.firstOrNull() ?: return null + return psiClass.constructors.firstOrNull() + } + + private fun providePsiForFunction( + functionLikeSymbol: KtFunctionLikeSymbol, + project: Project + ): PsiElement? { + return functionLikeSymbol.callableIdIfNonLocal?.let { + project.createPsiDeclarationProvider(functionLikeSymbol.scope(project)) + ?.getFunctions(it) + ?.firstOrNull() + } + } + + private fun providePsiForProperty( + variableLikeSymbol: KtVariableLikeSymbol, + project: Project + ): PsiElement? { + return variableLikeSymbol.callableIdIfNonLocal?.let { + project.createPsiDeclarationProvider(variableLikeSymbol.scope(project)) + ?.getProperties(it) + // TODO: needs to pick field/getter/setter accordingly? + ?.firstOrNull() + } + } + + private fun providePsiForClass( + classLikeSymbol: KtClassLikeSymbol, + project: Project + ): PsiElement? { + return classLikeSymbol.classIdIfNonLocal?.let { + project.createPsiDeclarationProvider(classLikeSymbol.scope(project)) + ?.getClassesByClassId(it) + ?.firstOrNull() + } + } + + private fun providePsiForEnumEntry( + enumEntrySymbol: KtEnumEntrySymbol, + project: Project + ): PsiElement? { + val classId = enumEntrySymbol.containingEnumClassIdIfNonLocal ?: return null + val psiClass = project.createPsiDeclarationProvider(enumEntrySymbol.scope(project)) + ?.getClassesByClassId(classId) + ?.firstOrNull() ?: return null + return psiClass.fields.find { + it.name == enumEntrySymbol.name.asString() + } + } + + private fun KtSymbol.scope(project: Project): GlobalSearchScope { + // TODO: finding containing module and use a narrower scope? + return GlobalSearchScope.allScope(project) + } +} \ No newline at end of file diff --git a/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/KotlinPsiDeclarationProvider.kt b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/KotlinPsiDeclarationProvider.kt new file mode 100644 index 00000000000..75966c8322f --- /dev/null +++ b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/KotlinPsiDeclarationProvider.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2010-2022 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.analysis.providers + +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiMember +import com.intellij.psi.PsiMethod +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.ClassId + +/** + * A [PsiMember] declaration provider for a given scope. Can be created via [KotlinPsiDeclarationProviderFactory]. + */ +public abstract class KotlinPsiDeclarationProvider { + /** + * Gets a collection of [PsiClass] by [ClassId] + * + * In standalone mode, this is simply [PsiClassStub]-based [PsiClass] + */ + public abstract fun getClassesByClassId(classId: ClassId): Collection + + public abstract fun getProperties(callableId: CallableId): Collection + public abstract fun getFunctions(callableId: CallableId): Collection +} + +public abstract class KotlinPsiDeclarationProviderFactory { + public abstract fun createPsiDeclarationProvider(searchScope: GlobalSearchScope): KotlinPsiDeclarationProvider +} + +public fun Project.createPsiDeclarationProvider(searchScope: GlobalSearchScope): KotlinPsiDeclarationProvider? = + // TODO: avoid using fail-safe service loading once the factory has an easy-to-register ctor. + getServiceIfCreated(KotlinPsiDeclarationProviderFactory::class.java) + ?.createPsiDeclarationProvider(searchScope) diff --git a/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinStaticPsiDeclarationFromBinaryModuleProvider.kt b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinStaticPsiDeclarationFromBinaryModuleProvider.kt new file mode 100644 index 00000000000..39a8a91056c --- /dev/null +++ b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/providers/impl/KotlinStaticPsiDeclarationFromBinaryModuleProvider.kt @@ -0,0 +1,107 @@ +/* + * Copyright 2010-2022 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.analysis.providers.impl + +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.impl.jar.CoreJarFileSystem +import com.intellij.psi.* +import com.intellij.psi.impl.compiled.ClsClassImpl +import com.intellij.psi.impl.compiled.ClsFileImpl +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.analysis.decompiled.light.classes.ClsJavaStubByVirtualFileCache +import org.jetbrains.kotlin.analysis.project.structure.KtBinaryModule +import org.jetbrains.kotlin.analysis.providers.KotlinPsiDeclarationProvider +import org.jetbrains.kotlin.analysis.providers.KotlinPsiDeclarationProviderFactory +import org.jetbrains.kotlin.asJava.builder.ClsWrapperStubPsiFactory +import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.util.capitalizeDecapitalize.decapitalizeSmart + +private class KotlinStaticPsiDeclarationFromBinaryModuleProvider( + private val project: Project, + override val scope: GlobalSearchScope, + private val binaryModules: Collection, + override val jarFileSystem: CoreJarFileSystem, +) : KotlinPsiDeclarationProvider(), AbstractDeclarationFromBinaryModuleProvider { + private val psiManager by lazy { PsiManager.getInstance(project) } + + private fun clsClassImplsByFqName( + fqName: FqName, + isPackageName: Boolean = true, + ): Collection { + return binaryModules + .flatMap { + virtualFilesFromModule(it, fqName, isPackageName) + } + .mapNotNull { + createClsJavaClassFromVirtualFile(it) + } + } + + private fun createClsJavaClassFromVirtualFile( + classFile: VirtualFile, + ): ClsClassImpl? { + val javaFileStub = ClsJavaStubByVirtualFileCache.getInstance(project).get(classFile) ?: return null + javaFileStub.psiFactory = ClsWrapperStubPsiFactory.INSTANCE + val fakeFile = object : ClsFileImpl(ClassFileViewProvider(psiManager, classFile)) { + override fun getStub() = javaFileStub + + override fun isPhysical() = false + } + javaFileStub.psi = fakeFile + return fakeFile.classes.single() as ClsClassImpl + } + + override fun getClassesByClassId(classId: ClassId): Collection { + return clsClassImplsByFqName(classId.asSingleFqName(), isPackageName = false) + } + + override fun getProperties(callableId: CallableId): Collection { + val classes = callableId.classId?.let { classId -> + getClassesByClassId(classId) + } ?: clsClassImplsByFqName(callableId.packageName) + return classes.flatMap { psiClass -> + psiClass.children + .filterIsInstance() + .filter { psiMember -> + if (psiMember !is PsiMethod && psiMember !is PsiField) return@filter false + val name = psiMember.name ?: return@filter false + // PsiField a.k.a. backing field + name == callableId.callableName.identifier || + // PsiMethod, i.e., accessors + (name.startsWith("get") || name.startsWith("set")) && + // E.g., getFooBar -> FooBar -> fooBar + (name.substring(3).decapitalizeSmart().endsWith(callableId.callableName.identifier)) + + } + }.toList() + } + + override fun getFunctions(callableId: CallableId): Collection { + val classes = callableId.classId?.let { classId -> + getClassesByClassId(classId) + } ?: clsClassImplsByFqName(callableId.packageName) + return classes.flatMap { psiClass -> + psiClass.methods.filter { psiMethod -> + psiMethod.name == callableId.callableName.identifier + } + }.toList() + } +} + +// TODO: we can't register this in IDE yet due to non-trivial parameters: lib modules and jar file system. +// We need a session or facade that maintains such information +public class KotlinStaticPsiDeclarationProviderFactory( + private val project: Project, + private val binaryModules: Collection, + private val jarFileSystem: CoreJarFileSystem, +) : KotlinPsiDeclarationProviderFactory() { + override fun createPsiDeclarationProvider(searchScope: GlobalSearchScope): KotlinPsiDeclarationProvider { + return KotlinStaticPsiDeclarationFromBinaryModuleProvider(project, searchScope, binaryModules, jarFileSystem) + } +}