From 7c912cd3e462ff1525f15471c8f109d9ebb63305 Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Sun, 1 Nov 2020 12:43:29 +0300 Subject: [PATCH] FIR IDE: introduce FileElementFactory --- ...IdeStructureElementDiagnosticsCollector.kt | 23 ++++ .../api/file/structure/FileElementFactory.kt | 50 +++++++ .../level/api/file/structure/FileStructure.kt | 72 +--------- .../file/structure/FileStructureElement.kt | 124 ++++++++++++------ .../idea/fir/low/level/api/util/psiUtils.kt | 11 -- 5 files changed, 163 insertions(+), 117 deletions(-) create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileElementFactory.kt delete mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/util/psiUtils.kt diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/FirIdeStructureElementDiagnosticsCollector.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/FirIdeStructureElementDiagnosticsCollector.kt index b8b31868e34..57e5c9dd0bb 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/FirIdeStructureElementDiagnosticsCollector.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostics/FirIdeStructureElementDiagnosticsCollector.kt @@ -51,5 +51,28 @@ internal class FirIdeStructureElementDiagnosticsCollector private constructor( collector.collectDiagnostics(firFile) FileStructureElementDiagnostics(collector.result) } + + fun collectForSingleDeclaration(firFile: FirFile, declaration: FirDeclaration): FileStructureElementDiagnostics { + var inCurrentDeclaration = false + + return collectForStructureElement( + firFile, + onDeclarationEnter = { firDeclaration -> + when { + firDeclaration == declaration -> { + inCurrentDeclaration = true + DiagnosticCollectorDeclarationAction.CHECK_CURRENT_DECLARATION_AND_CHECK_NESTED + } + inCurrentDeclaration -> DiagnosticCollectorDeclarationAction.CHECK_CURRENT_DECLARATION_AND_CHECK_NESTED + else -> DiagnosticCollectorDeclarationAction.SKIP_CURRENT_DECLARATION_AND_CHECK_NESTED + } + }, + onDeclarationExit = { firDeclaration -> + if (declaration == firDeclaration) { + inCurrentDeclaration = false + } + } + ) + } } } diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileElementFactory.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileElementFactory.kt new file mode 100644 index 00000000000..70ca1ec7398 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileElementFactory.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2010-2020 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.idea.fir.low.level.api.file.structure + +import org.jetbrains.kotlin.fir.declarations.FirDeclaration +import org.jetbrains.kotlin.fir.declarations.FirFile +import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtNamedFunction + +internal object FileElementFactory { + /** + * should be consistent with [isReanalyzableContainer] + */ + fun createFileStructureElement( + firDeclaration: FirDeclaration, + ktDeclaration: KtDeclaration, + firFile: FirFile, + ): FileStructureElement = when { + ktDeclaration is KtNamedFunction && ktDeclaration.name != null && ktDeclaration.hasExplicitTypeOrUnit -> + IncrementallyReanalyzableFunction( + firFile, + ktDeclaration, + (firDeclaration as FirSimpleFunction).symbol, + ktDeclaration.modificationStamp + ) + + else -> NonLocalDeclarationFileStructureElement( + firFile, + firDeclaration, + ktDeclaration, + ) + } + + /** + * should be consistent with [createFileStructureElement] + */ + fun isReanalyzableContainer( + ktDeclaration: KtDeclaration, + ): Boolean = when { + ktDeclaration is KtNamedFunction && ktDeclaration.name != null && ktDeclaration.hasExplicitTypeOrUnit -> true + else -> false + } + + val KtNamedFunction.hasExplicitTypeOrUnit + get() = hasBlockBody() || typeReference != null +} \ 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/file/structure/FileStructure.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructure.kt index 8b8b0382b7b..f194d1a79f4 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructure.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructure.kt @@ -6,17 +6,13 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.file.structure import org.jetbrains.kotlin.diagnostics.Diagnostic -import org.jetbrains.kotlin.fir.containingClass import org.jetbrains.kotlin.fir.declarations.* -import org.jetbrains.kotlin.fir.resolve.toSymbol 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.lazy.resolve.FirLazyDeclarationResolver import org.jetbrains.kotlin.idea.fir.low.level.api.providers.firIdeProvider import org.jetbrains.kotlin.idea.fir.low.level.api.util.findSourceNonLocalFirDeclaration -import org.jetbrains.kotlin.idea.fir.low.level.api.util.hasExplicitTypeOrUnit -import org.jetbrains.kotlin.idea.fir.low.level.api.util.replaceFirst import org.jetbrains.kotlin.idea.util.getElementTextInContext import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject @@ -44,8 +40,8 @@ internal class FileStructure( val structureElement = structureElements.compute(declaration) { _, structureElement -> when { structureElement == null -> createStructureElement(declaration) - structureElement is WithInBlockModificationFileStructureElement && !structureElement.isUpToDate() -> { - createMappingsCopy(structureElement, declaration as KtNamedFunction) + structureElement is ReanalyzableStructureElement<*> && !structureElement.isUpToDate() -> { + structureElement.reanalyze(declaration as KtNamedFunction, moduleFileCache, firLazyDeclarationResolver, firIdeProvider) } else -> structureElement } @@ -72,52 +68,6 @@ internal class FileStructure( } } - private fun replaceFunction(from: FirSimpleFunction, to: FirSimpleFunction) { - val declarations = if (from.symbol.callableId.className == null) { - firFile.declarations as MutableList - } else { - val classLikeLookupTag = from.containingClass() - ?: error("Class name should not be null for non-top-level & non-local declarations") - val containingClass = classLikeLookupTag.toSymbol(firFile.session)?.fir as FirRegularClass - containingClass.declarations as MutableList - } - declarations.replaceFirst(from, to) - } - - private fun createMappingsCopy( - original: WithInBlockModificationFileStructureElement, - containerKtFunction: KtNamedFunction - ): WithInBlockModificationFileStructureElement { - val newFunction = firIdeProvider.buildFunctionWithBody(containerKtFunction) as FirSimpleFunction - val originalFunction = original.firSymbol.fir as FirSimpleFunction - - moduleFileCache.firFileLockProvider.withWriteLock(firFile) { - replaceFunction(originalFunction, newFunction) - } - - try { - firLazyDeclarationResolver.lazyResolveDeclaration( - newFunction, - moduleFileCache, - FirResolvePhase.BODY_RESOLVE, - checkPCE = true, - reresolveFile = true, - ) - return moduleFileCache.firFileLockProvider.withReadLock(firFile) { - WithInBlockModificationFileStructureElement( - firFile, - containerKtFunction, - newFunction.symbol, - containerKtFunction.modificationStamp, - ) - } - } catch (e: Throwable) { - moduleFileCache.firFileLockProvider.withWriteLock(firFile) { - replaceFunction(newFunction, originalFunction) - } - throw e - } - } private fun createDeclarationStructure(declaration: KtDeclaration): FileStructureElement { val firDeclaration = declaration.findSourceNonLocalFirDeclaration( @@ -133,23 +83,7 @@ internal class FileStructure( checkPCE = true ) return moduleFileCache.firFileLockProvider.withReadLock(firFile) { - when { - declaration is KtNamedFunction && declaration.hasExplicitTypeOrUnit -> { - WithInBlockModificationFileStructureElement( - firFile, - declaration, - (firDeclaration as FirSimpleFunction).symbol, - declaration.modificationStamp, - ) - } - else -> { - NonLocalDeclarationFileStructureElement( - firFile, - firDeclaration, - declaration, - ) - } - } + FileElementFactory.createFileStructureElement(firDeclaration, declaration, firFile) } } diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureElement.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureElement.kt index d12a281d3ae..ce9e23a2cbc 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureElement.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/file/structure/FileStructureElement.kt @@ -8,16 +8,19 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.file.structure import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.analysis.collectors.DiagnosticCollectorDeclarationAction -import org.jetbrains.kotlin.fir.declarations.FirDeclaration -import org.jetbrains.kotlin.fir.declarations.FirFile -import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction +import org.jetbrains.kotlin.fir.containingClass +import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.psi +import org.jetbrains.kotlin.fir.resolve.toSymbol +import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.FirIdeStructureElementDiagnosticsCollector -import org.jetbrains.kotlin.idea.fir.low.level.api.util.hasExplicitTypeOrUnit +import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache +import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver +import org.jetbrains.kotlin.idea.fir.low.level.api.providers.FirIdeProvider +import org.jetbrains.kotlin.idea.fir.low.level.api.util.ktDeclaration +import org.jetbrains.kotlin.idea.fir.low.level.api.util.replaceFirst import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfTypeTo -import org.jetbrains.kotlin.utils.addToStdlib.safeAs internal class FileStructureElementDiagnostics( private val map: Map> @@ -34,43 +37,90 @@ internal sealed class FileStructureElement { abstract val diagnostics: FileStructureElementDiagnostics } -internal class WithInBlockModificationFileStructureElement( - override val firFile: FirFile, - override val psi: KtFunction, - val firSymbol: FirFunctionSymbol<*>, - val timestamp: Long -) : FileStructureElement() { +internal sealed class ReanalyzableStructureElement : FileStructureElement() { + abstract override val psi: KT + abstract val firSymbol: AbstractFirBasedSymbol<*> + abstract val timestamp: Long - override val mappings: Map = - FirElementsRecorder.recordElementsFrom(firSymbol.fir, recorder) + /** + * Creates new declaration by [newKtDeclaration] which will serve as replacement of [firSymbol] + * Also, modify [firFile] & replace old version of declaration to a new one + */ + abstract fun reanalyze( + newKtDeclaration: KtNamedFunction, + cache: ModuleFileCache, + firLazyDeclarationResolver: FirLazyDeclarationResolver, + firIdeProvider: FirIdeProvider, + ): ReanalyzableStructureElement fun isUpToDate(): Boolean = psi.getModificationStamp() == timestamp override val diagnostics: FileStructureElementDiagnostics by lazy { - var inCurrentDeclaration = false - - FirIdeStructureElementDiagnosticsCollector.collectForStructureElement( - firFile, - onDeclarationEnter = { firDeclaration -> - when { - firDeclaration == firSymbol.fir -> { - inCurrentDeclaration = true - DiagnosticCollectorDeclarationAction.CHECK_CURRENT_DECLARATION_AND_CHECK_NESTED - } - inCurrentDeclaration -> DiagnosticCollectorDeclarationAction.CHECK_CURRENT_DECLARATION_AND_CHECK_NESTED - else -> DiagnosticCollectorDeclarationAction.SKIP_CURRENT_DECLARATION_AND_CHECK_NESTED - } - }, - onDeclarationExit = { declaration -> - if (declaration == firSymbol.fir) { - inCurrentDeclaration = false - } - } - ) + FirIdeStructureElementDiagnosticsCollector.collectForSingleDeclaration(firFile, firSymbol.fir as FirDeclaration) } companion object { - private val recorder = FirElementsRecorder() + val recorder = FirElementsRecorder() + } +} + +internal class IncrementallyReanalyzableFunction( + override val firFile: FirFile, + override val psi: KtNamedFunction, + override val firSymbol: FirFunctionSymbol<*>, + override val timestamp: Long +) : ReanalyzableStructureElement() { + override val mappings: Map = + FirElementsRecorder.recordElementsFrom(firSymbol.fir, recorder) + + private fun replaceFunction(from: FirSimpleFunction, to: FirSimpleFunction) { + val declarations = if (from.symbol.callableId.className == null) { + firFile.declarations as MutableList + } else { + val classLikeLookupTag = from.containingClass() + ?: error("Class name should not be null for non-top-level & non-local declarations") + val containingClass = classLikeLookupTag.toSymbol(firFile.session)?.fir as FirRegularClass + containingClass.declarations as MutableList + } + declarations.replaceFirst(from, to) + } + + override fun reanalyze( + newKtDeclaration: KtNamedFunction, + cache: ModuleFileCache, + firLazyDeclarationResolver: FirLazyDeclarationResolver, + firIdeProvider: FirIdeProvider, + ): IncrementallyReanalyzableFunction { + val newFunction = firIdeProvider.buildFunctionWithBody(newKtDeclaration) as FirSimpleFunction + val originalFunction = firSymbol.fir as FirSimpleFunction + + cache.firFileLockProvider.withWriteLock(firFile) { + replaceFunction(originalFunction, newFunction) + } + + //todo remap symbol under firFile write lock + try { + firLazyDeclarationResolver.lazyResolveDeclaration( + newFunction, + cache, + FirResolvePhase.BODY_RESOLVE, + checkPCE = true, + reresolveFile = true, + ) + return cache.firFileLockProvider.withReadLock(firFile) { + IncrementallyReanalyzableFunction( + firFile, + newKtDeclaration, + newFunction.symbol, + newKtDeclaration.modificationStamp, + ) + } + } catch (e: Throwable) { + cache.firFileLockProvider.withWriteLock(firFile) { + replaceFunction(newFunction, originalFunction) + } + throw e + } } } @@ -92,7 +142,7 @@ internal class NonLocalDeclarationFileStructureElement( inCurrentDeclaration = true DiagnosticCollectorDeclarationAction.CHECK_CURRENT_DECLARATION_AND_CHECK_NESTED } - (firDeclaration.psi as? KtNamedFunction)?.hasExplicitTypeOrUnit == true -> { + FileElementFactory.isReanalyzableContainer(firDeclaration.ktDeclaration) -> { DiagnosticCollectorDeclarationAction.SKIP } inCurrentDeclaration -> { @@ -113,7 +163,7 @@ internal class NonLocalDeclarationFileStructureElement( private val recorder = object : FirElementsRecorder() { override fun visitSimpleFunction(simpleFunction: FirSimpleFunction, data: MutableMap) { val psi = simpleFunction.psi as? KtNamedFunction ?: return super.visitSimpleFunction(simpleFunction, data) - if (!psi.hasExplicitTypeOrUnit || KtPsiUtil.isLocal(psi)) { + if (!FileElementFactory.isReanalyzableContainer(psi) || KtPsiUtil.isLocal(psi)) { super.visitSimpleFunction(simpleFunction, data) } } diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/util/psiUtils.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/util/psiUtils.kt deleted file mode 100644 index 732b5248f49..00000000000 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/util/psiUtils.kt +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright 2010-2020 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.idea.fir.low.level.api.util - -import org.jetbrains.kotlin.psi.KtNamedFunction - -internal val KtNamedFunction.hasExplicitTypeOrUnit - get() = hasBlockBody() || typeReference != null