From 7dcda00e1d0f9abacadf630afdabffe55b15a1d4 Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Tue, 20 Oct 2020 17:04:04 +0300 Subject: [PATCH] FIR IDE: do not fail on invalid code --- .../api/element/builder/FirElementBuilder.kt | 1 + .../resolve/FirLazyDeclarationResolver.kt | 29 +++++-------- .../low/level/api/util/FirElementFinder.kt | 21 +++++++--- .../low/level/api/util/declarationUtils.kt | 42 +++++++++++++++---- .../idea/fir/low/level/api/util/utils.kt | 11 +++++ 5 files changed, 72 insertions(+), 32 deletions(-) diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/element/builder/FirElementBuilder.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/element/builder/FirElementBuilder.kt index f20d11fb637..e61bc0475ab 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/element/builder/FirElementBuilder.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/element/builder/FirElementBuilder.kt @@ -73,6 +73,7 @@ fun KtElement.getNonLocalContainingOrThisDeclaration(): KtNamedDeclaration? { while (container != null && container !is KtFile) { if (container is KtNamedDeclaration && (container is KtClassOrObject || container is KtDeclarationWithBody || container is KtProperty || container is KtTypeAlias) + && container !is KtPrimaryConstructor && !KtPsiUtil.isLocal(container) && container !is KtEnumEntry && container.containingClassOrObject !is KtEnumEntry diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/lazy/resolve/FirLazyDeclarationResolver.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/lazy/resolve/FirLazyDeclarationResolver.kt index d9d436f897a..92cc85ad746 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/lazy/resolve/FirLazyDeclarationResolver.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/lazy/resolve/FirLazyDeclarationResolver.kt @@ -5,26 +5,25 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve -import org.jetbrains.kotlin.fir.containingClass +import com.intellij.psi.util.parentsOfType import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.psi import org.jetbrains.kotlin.fir.render import org.jetbrains.kotlin.fir.resolve.ResolutionMode import org.jetbrains.kotlin.fir.resolve.providers.FirProvider import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.FirTowerDataContextCollector +import org.jetbrains.kotlin.idea.fir.low.level.api.trasformers.FirDesignatedBodyResolveTransformerForIDE import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.getNonLocalContainingOrThisDeclaration import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.FirFileBuilder import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache import org.jetbrains.kotlin.idea.fir.low.level.api.providers.firIdeProvider -import org.jetbrains.kotlin.idea.fir.low.level.api.trasformers.FirDesignatedBodyResolveTransformerForIDE import org.jetbrains.kotlin.idea.fir.low.level.api.trasformers.FirDesignatedContractsResolveTransformerForIDE import org.jetbrains.kotlin.idea.fir.low.level.api.trasformers.FirDesignatedImplicitTypesTransformerForIDE +import org.jetbrains.kotlin.idea.fir.low.level.api.util.* import org.jetbrains.kotlin.idea.fir.low.level.api.util.checkCanceled import org.jetbrains.kotlin.idea.fir.low.level.api.util.executeWithoutPCE import org.jetbrains.kotlin.idea.fir.low.level.api.util.findSourceNonLocalFirDeclaration -import org.jetbrains.kotlin.idea.fir.low.level.api.util.getContainingFile -import org.jetbrains.kotlin.psi.KtDeclaration -import org.jetbrains.kotlin.psi.KtPsiUtil +import org.jetbrains.kotlin.psi.* internal class FirLazyDeclarationResolver( private val firFileBuilder: FirFileBuilder @@ -131,20 +130,14 @@ internal class FirLazyDeclarationResolver( val nonLocalDeclarationToResolve = firDeclarationToResolve.getNonLocalDeclarationToResolve(provider, moduleFileCache) val designation = mutableListOf(containerFirFile) + if (nonLocalDeclarationToResolve !is FirFile) { - val id = when (nonLocalDeclarationToResolve) { - is FirCallableDeclaration<*> -> { - nonLocalDeclarationToResolve.containingClass()?.classId - } - is FirClassLikeDeclaration<*> -> { - nonLocalDeclarationToResolve.symbol.classId - } - else -> error("Unsupported: ${nonLocalDeclarationToResolve.render()}") - } - val outerClasses = generateSequence(id) { classId -> - classId.outerClassId - }.mapTo(mutableListOf()) { provider.getFirClassifierByFqName(it)!! } - designation += outerClasses.asReversed() + val ktDeclaration = firDeclarationToResolve.ktDeclaration + designation += ktDeclaration.parentsOfType() + .filter { it !is KtEnumEntry } + .map { it.findSourceNonLocalFirDeclaration(firFileBuilder, provider.symbolProvider, moduleFileCache) } + .toList() + .asReversed() if (nonLocalDeclarationToResolve is FirCallableDeclaration<*>) { designation += nonLocalDeclarationToResolve } diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/util/FirElementFinder.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/util/FirElementFinder.kt index 637414d7da4..a6f9191b187 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/util/FirElementFinder.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/util/FirElementFinder.kt @@ -6,20 +6,31 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.util import org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.declarations.FirFile import org.jetbrains.kotlin.fir.psi import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid import org.jetbrains.kotlin.psi.KtElement object FirElementFinder { - inline fun findElementIn(container: FirElement, crossinline predicate: (E) -> Boolean): E? { + inline fun findElementIn( + container: FirElement, + crossinline goInside: (E) -> Boolean = { true }, + crossinline predicate: (E) -> Boolean, + ): E? { var result: E? = null container.accept(object : FirVisitorVoid() { override fun visitElement(element: FirElement) { if (result != null) return - if (element is E && predicate(element)) { - result = element - } else { - element.acceptChildren(this) + when { + element !is E || element is FirFile -> { + element.acceptChildren(this) + } + predicate(element) -> { + result = element + } + goInside(element) -> { + element.acceptChildren(this) + } } } }) diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/util/declarationUtils.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/util/declarationUtils.kt index f6303cd96b9..a5be4b7cf91 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/util/declarationUtils.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/util/declarationUtils.kt @@ -9,10 +9,12 @@ import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.declarations.FirRegularClass import org.jetbrains.kotlin.fir.declarations.FirTypeAlias import org.jetbrains.kotlin.fir.psi +import org.jetbrains.kotlin.fir.realPsi import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProvider import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.FirFileBuilder import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache import org.jetbrains.kotlin.idea.util.classIdIfNonLocal +import org.jetbrains.kotlin.idea.util.getElementTextInContext import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject @@ -22,30 +24,53 @@ internal fun KtDeclaration.findSourceNonLocalFirDeclaration( firSymbolProvider: FirSymbolProvider, moduleFileCache: ModuleFileCache ): FirDeclaration { - require(!KtPsiUtil.isLocal(this)) - return when { + //TODO test what way faster + findSourceNonLocalFirDeclarationByProvider(firFileBuilder, firSymbolProvider, moduleFileCache)?.let { return it } + findSourceOfNonLocalFirDeclarationByTraversingWholeTree(firFileBuilder, moduleFileCache)?.let { return it } + error("No fir element was found for\n${getElementTextInContext()}") +} + +private fun KtDeclaration.findSourceOfNonLocalFirDeclarationByTraversingWholeTree( + firFileBuilder: FirFileBuilder, + moduleFileCache: ModuleFileCache, +): FirDeclaration? { + val firFile = firFileBuilder.buildRawFirFileWithCaching(containingKtFile, moduleFileCache, lazyBodiesMode = true) + val originalDeclaration = originalDeclaration + return FirElementFinder.findElementIn(firFile, goInside = { it is FirRegularClass }) { firDeclaration -> + firDeclaration.psi == this || firDeclaration.psi == originalDeclaration + } +} + +private fun KtDeclaration.findSourceNonLocalFirDeclarationByProvider( + firFileBuilder: FirFileBuilder, + firSymbolProvider: FirSymbolProvider, + moduleFileCache: ModuleFileCache +): FirDeclaration? { + val candidate = when { this is KtClassOrObject -> findFir(firSymbolProvider) this is KtNamedDeclaration && (this is KtProperty || this is KtNamedFunction) -> { val containerClass = containingClassOrObject val declarations = if (containerClass != null) { val containerClassFir = containerClass.findFir(firSymbolProvider) - containerClassFir.declarations + containerClassFir?.declarations } else { val ktFile = containingKtFile val firFile = firFileBuilder.buildRawFirFileWithCaching(ktFile, moduleFileCache, lazyBodiesMode = true) firFile.declarations } val original = originalDeclaration - declarations.first { it.psi === this || it.psi == original } + declarations?.first { it.psi === this || it.psi == original } } this is KtConstructor<*> -> { - val containerClassFir = containingClassOrObject?.findFir(firSymbolProvider) + val containingClass = containingClassOrObject ?: error("Container class should be not null for KtConstructor") + val containerClassFir = containingClass.findFir(firSymbolProvider) ?: return null containerClassFir.declarations.first { it.psi === this } } this is KtTypeAlias -> findFir(firSymbolProvider) else -> error("Invalid container $this::class") } + return candidate?.takeIf { it.realPsi == this } } val ORIGINAL_DECLARATION_KEY = com.intellij.openapi.util.Key("ORIGINAL_DECLARATION_KEY") @@ -53,9 +78,8 @@ val ORIGINAL_DECLARATION_KEY = com.intellij.openapi.util.Key("ORI var KtDeclaration.originalDeclaration by UserDataProperty(ORIGINAL_DECLARATION_KEY) -private fun KtClassOrObject.findFir(firSymbolProvider: FirSymbolProvider): FirRegularClass { - val classId = classIdIfNonLocal() - ?: error("Container classId should not be null for non-local declaration") +private fun KtClassOrObject.findFir(firSymbolProvider: FirSymbolProvider): FirRegularClass? { + val classId = classIdIfNonLocal() ?: return null return executeWithoutPCE { firSymbolProvider.getClassLikeSymbolByFqName(classId)?.fir as? FirRegularClass ?: error("Could not find class $classId") @@ -68,4 +92,4 @@ private fun KtTypeAlias.findFir(firSymbolProvider: FirSymbolProvider): FirTypeAl firSymbolProvider.getClassLikeSymbolByFqName(typeAlias)?.fir as? FirTypeAlias ?: error("Could not find type alias $typeAlias") } -} \ No newline at end of file +} diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/util/utils.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/util/utils.kt index 9c3e12d5109..835b763794b 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/util/utils.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/util/utils.kt @@ -8,8 +8,12 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.util import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProgressManager import org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.diagnostics.FirDiagnosticHolder +import org.jetbrains.kotlin.fir.psi +import org.jetbrains.kotlin.fir.render import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo +import org.jetbrains.kotlin.psi.KtDeclaration import java.util.concurrent.TimeUnit import java.util.concurrent.locks.ReentrantLock @@ -52,6 +56,13 @@ internal inline fun checkCanceled() { internal val FirElement.isErrorElement get() = this is FirDiagnosticHolder +internal val FirDeclaration.ktDeclaration: KtDeclaration + get() { + val psi = psi + ?: error("PSI element was not found for${render()}") + return psi as KtDeclaration + } + internal fun IdeaModuleInfo.collectTransitiveDependenciesWithSelf(): List { val result = mutableSetOf()