Skip to content

Commit

Permalink
FIR IDE: add property support for incremental analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
darthorimar committed Nov 27, 2020
1 parent 953dba8 commit 76c0dc7
Show file tree
Hide file tree
Showing 21 changed files with 290 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.FirTowerDataC
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.sessions.FirIdeSourcesSession
import org.jetbrains.kotlin.idea.fir.low.level.api.util.ktDeclaration
import org.jetbrains.kotlin.idea.util.getElementTextInContext
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtElement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.element.builder
import com.intellij.psi.PsiElement
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver
import org.jetbrains.kotlin.idea.fir.low.level.api.annotations.ThreadSafe
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.file.structure.FileStructureCache
import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.FileStructureElement
Expand Down Expand Up @@ -104,6 +101,9 @@ internal fun PsiElement.getNonLocalContainingInBodyDeclarationWith(): KtNamedDec
getNonLocalContainingOrThisDeclaration { declaration ->
when (declaration) {
is KtNamedFunction -> declaration.bodyExpression?.isAncestor(this) == true
is KtProperty -> declaration.initializer?.isAncestor(this) == true ||
declaration.getter?.isAncestor(this) == true ||
declaration.setter?.isAncestor(this) == true
else -> false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ 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.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtProperty

internal object FileElementFactory {
/**
Expand All @@ -20,13 +22,19 @@ internal object FileElementFactory {
ktDeclaration: KtDeclaration,
firFile: FirFile,
): FileStructureElement = when {
ktDeclaration is KtNamedFunction && ktDeclaration.name != null && ktDeclaration.hasExplicitTypeOrUnit ->
ReanalyzableFunctionStructureElement(
firFile,
ktDeclaration,
(firDeclaration as FirSimpleFunction).symbol,
ktDeclaration.modificationStamp
)
ktDeclaration is KtNamedFunction && ktDeclaration.isReanalyzableContainer() -> ReanalyzableFunctionStructureElement(
firFile,
ktDeclaration,
(firDeclaration as FirSimpleFunction).symbol,
ktDeclaration.modificationStamp
)

ktDeclaration is KtProperty && ktDeclaration.isReanalyzableContainer() -> ReanalyzablePropertyStructureElement(
firFile,
ktDeclaration,
(firDeclaration as FirProperty).symbol,
ktDeclaration.modificationStamp
)

else -> NonReanalyzableDeclarationStructureElement(
firFile,
Expand All @@ -40,11 +48,18 @@ internal object FileElementFactory {
*/
fun isReanalyzableContainer(
ktDeclaration: KtDeclaration,
): Boolean = when {
ktDeclaration is KtNamedFunction && ktDeclaration.name != null && ktDeclaration.hasExplicitTypeOrUnit -> true
): Boolean = when (ktDeclaration) {
is KtNamedFunction -> ktDeclaration.isReanalyzableContainer()
is KtProperty -> ktDeclaration.isReanalyzableContainer()
else -> false
}

private fun KtNamedFunction.isReanalyzableContainer() =
name != null && hasExplicitTypeOrUnit

private fun KtProperty.isReanalyzableContainer() =
name != null && typeReference != null

private val KtNamedFunction.hasExplicitTypeOrUnit
get() = hasBlockBody() || typeReference != null
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ internal class FileStructure(
}

private fun getStructureElementForDeclaration(declaration: KtAnnotated): FileStructureElement {
@Suppress("CANNOT_CHECK_FOR_ERASED")

This comment has been minimized.

Copy link
@mglukhikh

mglukhikh Dec 2, 2020

Contributor

I'd recommend to get rid of this error suppression by rewriting code

val structureElement = structureElements.compute(declaration) { _, structureElement ->
when {
structureElement == null -> createStructureElement(declaration)
structureElement is ReanalyzableStructureElement<*> && !structureElement.isUpToDate() -> {
structureElement.reanalyze(declaration as KtNamedFunction, moduleFileCache, firLazyDeclarationResolver, firIdeProvider)
structureElement is ReanalyzableStructureElement<KtDeclaration> && !structureElement.isUpToDate() -> {
structureElement.reanalyze(declaration as KtDeclaration, moduleFileCache, firLazyDeclarationResolver, firIdeProvider)
}
else -> structureElement
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import org.jetbrains.kotlin.fir.analysis.collectors.DiagnosticCollectorDeclarati
import org.jetbrains.kotlin.fir.containingClass
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.realPsi
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.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.idea.fir.low.level.api.diagnostics.FirIdeStructureElementDiagnosticsCollector
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache
import org.jetbrains.kotlin.idea.fir.low.level.api.lazy.resolve.FirLazyDeclarationResolver
Expand All @@ -40,16 +40,16 @@ internal sealed class FileStructureElement {
}

internal sealed class ReanalyzableStructureElement<KT : KtDeclaration> : FileStructureElement() {
abstract override val psi: KT
abstract override val psi: KtDeclaration
abstract val firSymbol: AbstractFirBasedSymbol<*>
abstract val timestamp: Long

/**
* Creates new declaration by [newKtDeclaration] which will serve as replacement of [firSymbol]
* Also, modify [firFile] & replace old version of declaration to a new one
* Also, modify [firFile] & replace old version of declaration with a new one
*/
abstract fun reanalyze(
newKtDeclaration: KtNamedFunction,
newKtDeclaration: KT,
cache: ModuleFileCache,
firLazyDeclarationResolver: FirLazyDeclarationResolver,
firIdeProvider: FirIdeProvider,
Expand All @@ -75,18 +75,6 @@ internal class ReanalyzableFunctionStructureElement(
override val mappings: Map<KtElement, FirElement> =
FirElementsRecorder.recordElementsFrom(firSymbol.fir, recorder)

private fun replaceFunction(from: FirSimpleFunction, to: FirSimpleFunction) {
val declarations = if (from.symbol.callableId.className == null) {
firFile.declarations as MutableList<FirDeclaration>
} 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<FirDeclaration>
}
declarations.replaceFirst(from, to)
}

override fun reanalyze(
newKtDeclaration: KtNamedFunction,
cache: ModuleFileCache,
Expand All @@ -96,32 +84,60 @@ internal class ReanalyzableFunctionStructureElement(
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 {
return FileStructureUtil.withDeclarationReplaced(firFile, cache, originalFunction, newFunction) {
firLazyDeclarationResolver.lazyResolveDeclaration(
newFunction,
cache,
FirResolvePhase.BODY_RESOLVE,
checkPCE = true,
reresolveFile = true,
)
return cache.firFileLockProvider.withReadLock(firFile) {
cache.firFileLockProvider.withReadLock(firFile) {
ReanalyzableFunctionStructureElement(
firFile,
newKtDeclaration,
newFunction.symbol,
newKtDeclaration.modificationStamp,
)
}
} catch (e: Throwable) {
cache.firFileLockProvider.withWriteLock(firFile) {
replaceFunction(newFunction, originalFunction)
}
}
}

internal class ReanalyzablePropertyStructureElement(
override val firFile: FirFile,
override val psi: KtProperty,
override val firSymbol: FirPropertySymbol,
override val timestamp: Long
) : ReanalyzableStructureElement<KtProperty>() {
override val mappings: Map<KtElement, FirElement> =
FirElementsRecorder.recordElementsFrom(firSymbol.fir, recorder)

override fun reanalyze(
newKtDeclaration: KtProperty,
cache: ModuleFileCache,
firLazyDeclarationResolver: FirLazyDeclarationResolver,
firIdeProvider: FirIdeProvider,
): ReanalyzablePropertyStructureElement {
val newProperty = firIdeProvider.buildPropertyWithBody(newKtDeclaration)
val originalProperty = firSymbol.fir

return FileStructureUtil.withDeclarationReplaced(firFile, cache, originalProperty, newProperty) {
firLazyDeclarationResolver.lazyResolveDeclaration(
newProperty,
cache,
FirResolvePhase.BODY_RESOLVE,
checkPCE = true,
reresolveFile = true,
)
cache.firFileLockProvider.withReadLock(firFile) {
ReanalyzablePropertyStructureElement(
firFile,
newKtDeclaration,
newProperty.symbol,
newKtDeclaration.modificationStamp,
)
}
throw e
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@

package org.jetbrains.kotlin.idea.fir.low.level.api.file.structure

import org.jetbrains.kotlin.fir.containingClass
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.idea.fir.low.level.api.file.builder.ModuleFileCache
import org.jetbrains.kotlin.idea.fir.low.level.api.util.replaceFirst
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject

Expand All @@ -15,4 +23,32 @@ internal object FileStructureUtil {
ktDeclaration.containingClassOrObject is KtEnumEntry -> false
else -> !KtPsiUtil.isLocal(ktDeclaration)
}

fun replaceDeclaration(firFile: FirFile, from: FirCallableDeclaration<*>, to: FirCallableDeclaration<*>) {
val declarations = if (from.symbol.callableId.className == null) {
firFile.declarations as MutableList<FirDeclaration>
} 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<FirDeclaration>
}
declarations.replaceFirst(from, to)
}

inline fun <R> withDeclarationReplaced(
firFile: FirFile,
cache: ModuleFileCache,
from: FirCallableDeclaration<*>,
to: FirCallableDeclaration<*>,
action: () -> R,
): R {
cache.firFileLockProvider.withWriteLock(firFile) { replaceDeclaration(firFile, from, to) }
return try {
action()
} catch (e: Throwable) {
cache.firFileLockProvider.withWriteLock(firFile) { replaceDeclaration(firFile, to, from) }
throw e
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class X {/* NonReanalyzableDeclarationStructureElement */
var x: Int/* ReanalyzablePropertyStructureElement */
get() = field
set(value) {
field = value
}

val y = 42/* NonReanalyzableDeclarationStructureElement */

var z: Int = 15/* ReanalyzablePropertyStructureElement */
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
fun foo() {/* ReanalyzableFunctionStructureElement */
var x: Int
}
class A {/* NonReanalyzableDeclarationStructureElement */
fun q() {/* ReanalyzableFunctionStructureElement */
val y = 42
}
}
class B {/* NonReanalyzableDeclarationStructureElement */
class C {/* NonReanalyzableDeclarationStructureElement */
fun u() {/* ReanalyzableFunctionStructureElement */
var z: Int = 15
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
var x: Int/* ReanalyzablePropertyStructureElement */
get() = field
set(value) {
field = value
}

val y = 42/* NonReanalyzableDeclarationStructureElement */

var z: Int = 15/* ReanalyzablePropertyStructureElement */
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
val x: Int get() = y<caret>

// OUT_OF_BLOCK: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
val x: Int
get() = y<caret>

// OUT_OF_BLOCK: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
val x: Int = y<caret>

// OUT_OF_BLOCK: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
val x: Int
set(value) = y<caret>

// OUT_OF_BLOCK: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
val x get() = y<caret>

// OUT_OF_BLOCK: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
val x = y<caret>

// OUT_OF_BLOCK: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
val x
get() = 1
set(value) {
<caret>
}

// OUT_OF_BLOCK: true
// TODO should not be out of block
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.file.structure

import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.util.parentOfType
import junit.framework.Assert
import org.jetbrains.kotlin.idea.fir.low.level.api.FirModuleResolveStateImpl
Expand Down Expand Up @@ -53,8 +54,13 @@ abstract class AbstractFileStructureAndOutOfBlockModificationTrackerConsistencyT
)
}

private fun KtFile.findElementAtCaret(): KtElement =
findElementAt(myFixture.caretOffset)!!.parentOfType()!!
private fun KtFile.findElementAtCaret(): KtElement {
val element = when (val elementAtOffset = findElementAt(myFixture.caretOffset)) {
is PsiWhiteSpace -> findElementAt((myFixture.caretOffset - 1).coerceAtLeast(0))
else -> elementAtOffset
}
return element!!.parentOfType()!!
}

private fun getStructureElementForKtElement(element: KtElement): Triple<FileStructureElement, FileStructure, FirModuleResolveState> {
val moduleResolveState = element.getResolveState() as FirModuleResolveStateImpl
Expand Down
Loading

0 comments on commit 76c0dc7

Please sign in to comment.