FIR IDE: Move fir resolving functionality from idea module to idea-frontend-fir

This commit is contained in:
Ilya Kirillov
2020-05-24 15:01:37 +03:00
parent 003827a4f2
commit 45ef0e1b50
26 changed files with 181 additions and 41 deletions
@@ -1,53 +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.caches.trackers
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.SimpleModificationTracker
import com.intellij.pom.tree.TreeAspect
import org.jetbrains.kotlin.idea.KotlinLanguage
/**
* Tested in OutOfBlockModificationTestGenerated
*/
// FIX ME WHEN BUNCH 193 REMOVED
class KotlinCodeBlockModificationListener(
project: Project,
treeAspect: TreeAspect
) : KotlinCodeBlockModificationListenerCompat(project) {
init {
init(
treeAspect,
incOCBCounter = { ktFile ->
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
perModuleOutOfCodeBlockTrackerUpdater.onKotlinPhysicalFileOutOfBlockChange(ktFile, true)
},
kotlinOutOfCodeBlockTrackerProducer = {
SimpleModificationTracker()
},
psiModificationTrackerListener = {
@Suppress("UnstableApiUsage")
val kotlinTrackerInternalIDECount =
modificationTrackerImpl.forLanguage(KotlinLanguage.INSTANCE).modificationCount
if (kotlinModificationTracker == kotlinTrackerInternalIDECount) {
// Some update that we are not sure is from Kotlin language, as Kotlin language tracker wasn't changed
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
} else {
kotlinModificationTracker = kotlinTrackerInternalIDECount
}
perModuleOutOfCodeBlockTrackerUpdater.onPsiModificationTrackerUpdate()
}
)
}
companion object {
fun getInstance(project: Project): KotlinCodeBlockModificationListener =
project.getComponent(KotlinCodeBlockModificationListener::class.java)
}
}
@@ -1,50 +0,0 @@
/*
* 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.idea.caches.trackers
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.SimpleModificationTracker
import com.intellij.pom.tree.TreeAspect
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.util.application.getServiceSafe
import org.jetbrains.kotlin.psi.KtFile
/**
* Tested in OutOfBlockModificationTestGenerated
*/
// FIX ME WHEN BUNCH 193 REMOVED
class KotlinCodeBlockModificationListener(project: Project) : KotlinCodeBlockModificationListenerCompat(project) {
init {
init(
TreeAspect.getInstance(project),
incOCBCounter = { ktFile ->
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
perModuleOutOfCodeBlockTrackerUpdater.onKotlinPhysicalFileOutOfBlockChange(ktFile, true)
},
kotlinOutOfCodeBlockTrackerProducer = {
SimpleModificationTracker()
},
psiModificationTrackerListener = {
val kotlinTrackerInternalIDECount =
modificationTrackerImpl.forLanguage(KotlinLanguage.INSTANCE).modificationCount
if (kotlinModificationTracker == kotlinTrackerInternalIDECount) {
// Some update that we are not sure is from Kotlin language, as Kotlin language tracker wasn't changed
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
} else {
kotlinModificationTracker = kotlinTrackerInternalIDECount
}
perModuleOutOfCodeBlockTrackerUpdater.onPsiModificationTrackerUpdate()
}
)
}
companion object {
fun getInstance(project: Project): KotlinCodeBlockModificationListener = project.getServiceSafe()
}
}
@@ -1,360 +0,0 @@
/*
* 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.idea.caches.trackers
import com.intellij.lang.ASTNode
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.ModificationTracker
import com.intellij.openapi.util.SimpleModificationTracker
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.pom.PomManager
import com.intellij.pom.PomModelAspect
import com.intellij.pom.event.PomModelEvent
import com.intellij.pom.event.PomModelListener
import com.intellij.pom.tree.TreeAspect
import com.intellij.pom.tree.events.TreeChangeEvent
import com.intellij.pom.tree.events.impl.ChangeInfoImpl
import com.intellij.psi.*
import com.intellij.psi.impl.PsiManagerImpl
import com.intellij.psi.impl.PsiModificationTrackerImpl
import com.intellij.psi.impl.PsiTreeChangeEventImpl
import com.intellij.psi.impl.PsiTreeChangeEventImpl.PsiEventType.CHILD_MOVED
import com.intellij.psi.impl.PsiTreeChangeEventImpl.PsiEventType.PROPERTY_CHANGED
import com.intellij.psi.impl.PsiTreeChangePreprocessor
import com.intellij.psi.util.PsiModificationTracker
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
val KOTLIN_CONSOLE_KEY = Key.create<Boolean>("kotlin.console")
/**
* Tested in OutOfBlockModificationTestGenerated
*/
// FIX ME WHEN BUNCH 193 REMOVED
abstract class KotlinCodeBlockModificationListenerCompat(protected val project: Project) : PsiTreeChangePreprocessor {
protected val modificationTrackerImpl: PsiModificationTrackerImpl =
PsiModificationTracker.SERVICE.getInstance(project) as PsiModificationTrackerImpl
@Volatile
protected var kotlinModificationTracker: Long = 0
protected lateinit var kotlinOutOfCodeBlockTrackerImpl: SimpleModificationTracker
lateinit var kotlinOutOfCodeBlockTracker: ModificationTracker
internal val perModuleOutOfCodeBlockTrackerUpdater = KotlinModuleOutOfCodeBlockModificationTracker.Updater(project)
protected fun init(
treeAspect: TreeAspect,
incOCBCounter: (KtFile) -> Unit,
psiModificationTrackerListener: PsiModificationTracker.Listener,
kotlinOutOfCodeBlockTrackerProducer: () -> SimpleModificationTracker,
isLanguageTrackerEnabled: Boolean = true,
) {
kotlinOutOfCodeBlockTrackerImpl = kotlinOutOfCodeBlockTrackerProducer()
kotlinOutOfCodeBlockTracker = kotlinOutOfCodeBlockTrackerImpl
val model = PomManager.getModel(project)
val messageBusConnection = project.messageBus.connect(project)
model.addModelListener(object : PomModelListener {
override fun isAspectChangeInteresting(aspect: PomModelAspect): Boolean {
return aspect == treeAspect
}
override fun modelChanged(event: PomModelEvent) {
val changeSet = event.getChangeSet(treeAspect) as TreeChangeEvent? ?: return
val ktFile = changeSet.rootElement.psi.containingFile as? KtFile ?: return
incFileModificationCount(ktFile)
val changedElements = changeSet.changedElements
// skip change if it contains only virtual/fake change
if (changedElements.isNotEmpty()) {
// ignore formatting (whitespaces etc)
if (isFormattingChange(changeSet) || isCommentChange(changeSet)) return
}
val inBlockElements = inBlockModifications(changedElements)
val physical = ktFile.isPhysical
if (inBlockElements.isEmpty()) {
messageBusConnection.deliverImmediately()
if (physical && !isReplLine(ktFile.virtualFile) && ktFile !is KtTypeCodeFragment) {
incOCBCounter(ktFile)
}
ktFile.incOutOfBlockModificationCount()
} else if (physical) {
inBlockElements.forEach { it.containingKtFile.addInBlockModifiedItem(it) }
}
}
})
if (isLanguageTrackerEnabled) {
(PsiManager.getInstance(project) as PsiManagerImpl).addTreeChangePreprocessor(this)
}
messageBusConnection.subscribe(PsiModificationTracker.TOPIC, psiModificationTrackerListener)
}
override fun treeChanged(event: PsiTreeChangeEventImpl) {
if (!PsiModificationTrackerImpl.canAffectPsi(event)) {
return
}
// Copy logic from PsiModificationTrackerImpl.treeChanged(). Some out-of-code-block events are written to language modification
// tracker in PsiModificationTrackerImpl but don't have correspondent PomModelEvent. Increase kotlinOutOfCodeBlockTracker
// manually if needed.
val outOfCodeBlock = when (event.code) {
PROPERTY_CHANGED ->
event.propertyName === PsiTreeChangeEvent.PROP_UNLOADED_PSI || event.propertyName === PsiTreeChangeEvent.PROP_ROOTS
CHILD_MOVED -> event.oldParent is PsiDirectory || event.newParent is PsiDirectory
else -> event.parent is PsiDirectory
}
if (outOfCodeBlock) {
kotlinOutOfCodeBlockTrackerImpl.incModificationCount()
}
}
companion object {
private fun isReplLine(file: VirtualFile): Boolean {
return file.getUserData(KOTLIN_CONSOLE_KEY) == true
}
private fun incFileModificationCount(file: KtFile) {
val tracker = file.getUserData(PER_FILE_MODIFICATION_TRACKER)
?: file.putUserDataIfAbsent(PER_FILE_MODIFICATION_TRACKER, SimpleModificationTracker())
tracker.incModificationCount()
}
private fun inBlockModifications(elements: Array<ASTNode>): List<KtElement> {
// When a code fragment is reparsed, Intellij doesn't do an AST diff and considers the entire
// contents to be replaced, which is represented in a POM event as an empty list of changed elements
return elements.map { element ->
val modificationScope = getInsideCodeBlockModificationScope(element.psi) ?: return emptyList()
modificationScope.blockDeclaration
}
}
private fun isSpecificChange(changeSet: TreeChangeEvent, precondition: (ASTNode?) -> Boolean): Boolean =
changeSet.changedElements.all { changedElement ->
val changesByElement = changeSet.getChangesByElement(changedElement)
changesByElement.affectedChildren.all { affectedChild ->
if (!precondition(affectedChild)) return@all false
val changeByChild = changesByElement.getChangeByChild(affectedChild)
return@all if (changeByChild is ChangeInfoImpl) {
val oldChild = changeByChild.oldChild
precondition(oldChild)
} else false
}
}
private fun isCommentChange(changeSet: TreeChangeEvent): Boolean =
isSpecificChange(changeSet) { it is PsiComment || it is KDoc }
private fun isFormattingChange(changeSet: TreeChangeEvent): Boolean =
isSpecificChange(changeSet) { it is PsiWhiteSpace }
/**
* Has to be aligned with [getInsideCodeBlockModificationScope] :
*
* result of analysis has to be reflected in dirty scope,
* the only difference is whitespaces and comments
*/
fun getInsideCodeBlockModificationDirtyScope(element: PsiElement): PsiElement? {
if (!element.isPhysical) return null
// dirty scope for whitespaces and comments is the element itself
if (element is PsiWhiteSpace || element is PsiComment || element is KDoc) return element
return getInsideCodeBlockModificationScope(element)?.blockDeclaration ?: null
}
fun getInsideCodeBlockModificationScope(element: PsiElement): BlockModificationScopeElement? {
val lambda = element.getTopmostParentOfType<KtLambdaExpression>()
if (lambda is KtLambdaExpression) {
lambda.getTopmostParentOfType<KtSuperTypeCallEntry>()?.getTopmostParentOfType<KtClassOrObject>()?.let {
return BlockModificationScopeElement(it, it)
}
}
val blockDeclaration =
KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) as? KtDeclaration ?: return null
// KtPsiUtil.getTopmostParentOfType<KtClassOrObject>(element) as? KtDeclaration ?: return null
// should not be local declaration
if (KtPsiUtil.isLocal(blockDeclaration))
return null
when (blockDeclaration) {
is KtNamedFunction -> {
// if (blockDeclaration.visibilityModifierType()?.toVisibility() == Visibilities.PRIVATE) {
// topClassLikeDeclaration(blockDeclaration)?.let {
// return BlockModificationScopeElement(it, it)
// }
// }
if (blockDeclaration.hasBlockBody()) {
// case like `fun foo(): String {...<caret>...}`
return blockDeclaration.bodyExpression
?.takeIf { it.isAncestor(element) }
?.let { BlockModificationScopeElement(blockDeclaration, it) }
} else if (blockDeclaration.hasDeclaredReturnType()) {
// case like `fun foo(): String = b<caret>labla`
return blockDeclaration.initializer
?.takeIf { it.isAncestor(element) }
?.let { BlockModificationScopeElement(blockDeclaration, it) }
}
}
is KtProperty -> {
// if (blockDeclaration.visibilityModifierType()?.toVisibility() == Visibilities.PRIVATE) {
// topClassLikeDeclaration(blockDeclaration)?.let {
// return BlockModificationScopeElement(it, it)
// }
// }
if (blockDeclaration.typeReference != null) {
val accessors =
blockDeclaration.accessors.map { it.initializer ?: it.bodyExpression }
val accessorList = if (blockDeclaration.initializer.isAncestor(element) &&
// call expression changes in property initializer are OCB, see KT-38443
KtPsiUtil.getTopmostParentOfTypes(element, KtCallExpression::class.java) == null
) {
accessors + blockDeclaration.initializer
} else {
accessors
}
for (accessor in accessorList) {
accessor?.takeIf {
it.isAncestor(element) &&
// adding annotations to accessor is the same as change contract of property
(element !is KtAnnotated || element.annotationEntries.isEmpty())
}
?.let { expression ->
val declaration =
KtPsiUtil.getTopmostParentOfTypes(blockDeclaration, KtClassOrObject::class.java) as? KtElement ?:
// ktFile to check top level property declarations
return null
return BlockModificationScopeElement(declaration, expression)
}
}
}
}
is KtScriptInitializer -> {
return (blockDeclaration.body as? KtCallExpression)
?.lambdaArguments
?.lastOrNull()
?.getLambdaExpression()
?.takeIf { it.isAncestor(element) }
?.let { BlockModificationScopeElement(blockDeclaration, it) }
}
is KtClassInitializer -> {
blockDeclaration
.takeIf { it.isAncestor(element) }
?.let { ktClassInitializer ->
(PsiTreeUtil.getParentOfType(blockDeclaration, KtClassOrObject::class.java))?.let {
return BlockModificationScopeElement(it, ktClassInitializer)
}
}
}
is KtSecondaryConstructor -> {
blockDeclaration
?.takeIf {
it.bodyExpression?.isAncestor(element) ?: false || it.getDelegationCallOrNull()?.isAncestor(element) ?: false
}?.let { ktConstructor ->
PsiTreeUtil.getParentOfType(blockDeclaration, KtClassOrObject::class.java)?.let {
return BlockModificationScopeElement(it, ktConstructor)
}
}
}
// is KtClassOrObject -> {
// return when (element) {
// is KtProperty, is KtNamedFunction -> {
// if ((element as? KtModifierListOwner)?.visibilityModifierType()?.toVisibility() == Visibilities.PRIVATE)
// BlockModificationScopeElement(blockDeclaration, blockDeclaration) else null
// }
// else -> null
// }
// }
else -> throw IllegalStateException()
}
return null
}
data class BlockModificationScopeElement(val blockDeclaration: KtElement, val element: KtElement)
fun isBlockDeclaration(declaration: KtDeclaration): Boolean {
return BLOCK_DECLARATION_TYPES.any { it.isInstance(declaration) }
}
private val BLOCK_DECLARATION_TYPES = arrayOf<Class<out KtDeclaration>>(
KtProperty::class.java,
KtNamedFunction::class.java,
KtClassInitializer::class.java,
KtSecondaryConstructor::class.java,
KtScriptInitializer::class.java
)
}
}
private val PER_FILE_MODIFICATION_TRACKER = Key<SimpleModificationTracker>("FILE_OUT_OF_BLOCK_MODIFICATION_COUNT")
val KtFile.perFileModificationTracker: ModificationTracker
get() = putUserDataIfAbsent(PER_FILE_MODIFICATION_TRACKER, SimpleModificationTracker())
private val FILE_OUT_OF_BLOCK_MODIFICATION_COUNT = Key<Long>("FILE_OUT_OF_BLOCK_MODIFICATION_COUNT")
val KtFile.outOfBlockModificationCount: Long by NotNullableUserDataProperty(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, 0)
private fun KtFile.incOutOfBlockModificationCount() {
clearInBlockModifications()
val count = getUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT) ?: 0
putUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, count + 1)
}
/**
* inBlockModifications is a collection of block elements those have in-block modifications
*/
private val IN_BLOCK_MODIFICATIONS = Key<MutableCollection<KtElement>>("IN_BLOCK_MODIFICATIONS")
private val FILE_IN_BLOCK_MODIFICATION_COUNT = Key<Long>("FILE_IN_BLOCK_MODIFICATION_COUNT")
val KtFile.inBlockModificationCount: Long by NotNullableUserDataProperty(FILE_IN_BLOCK_MODIFICATION_COUNT, 0)
val KtFile.inBlockModifications: Collection<KtElement>
get() {
val collection = getUserData(IN_BLOCK_MODIFICATIONS)
return collection ?: emptySet()
}
private fun KtFile.addInBlockModifiedItem(element: KtElement) {
val collection = putUserDataIfAbsent(IN_BLOCK_MODIFICATIONS, mutableSetOf())
synchronized(collection) {
collection.add(element)
}
val count = getUserData(FILE_IN_BLOCK_MODIFICATION_COUNT) ?: 0
putUserData(FILE_IN_BLOCK_MODIFICATION_COUNT, count + 1)
}
fun KtFile.clearInBlockModifications() {
val collection = getUserData(IN_BLOCK_MODIFICATIONS)
collection?.let {
synchronized(it) {
it.clear()
}
}
}
@@ -1,24 +0,0 @@
/*
* 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.idea.caches.trackers
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.ModificationTracker
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.kotlin.analyzer.KotlinModificationTrackerService
import org.jetbrains.kotlin.psi.KtFile
class KotlinIDEModificationTrackerService(project: Project) :
KotlinModificationTrackerService() {
override val modificationTracker: ModificationTracker = PsiModificationTracker.SERVICE.getInstance(project)
override val outOfBlockModificationTracker: ModificationTracker =
KotlinCodeBlockModificationListener.getInstance(project).kotlinOutOfCodeBlockTracker
override fun fileModificationTracker(file: KtFile): ModificationTracker =
file.perFileModificationTracker
}
@@ -1,118 +0,0 @@
/*
* 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.idea.caches.trackers
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.util.ModificationTracker
import com.intellij.util.CommonProcessors
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.caches.project.cacheByClassInvalidatingOnRootModifications
import org.jetbrains.kotlin.psi.KtFile
class KotlinModuleOutOfCodeBlockModificationTracker private constructor(private val module: Module, private val updater: Updater) :
ModificationTracker {
constructor(module: Module) :
this(module, KotlinCodeBlockModificationListener.getInstance(module.project).perModuleOutOfCodeBlockTrackerUpdater)
private val kotlinOutOfCodeBlockTracker = KotlinCodeBlockModificationListener.getInstance(module.project).kotlinOutOfCodeBlockTracker
private val dependencies by lazy {
// Avoid implicit capturing for this to make CachedValueStabilityChecker happy
val module = module
module.cacheByClassInvalidatingOnRootModifications(KeyForCachedDependencies::class.java) {
HashSet<Module>().also { resultModuleSet ->
ModuleRootManager.getInstance(module).orderEntries().recursively().forEachModule(
CommonProcessors.CollectProcessor(resultModuleSet)
)
}
}
}
object KeyForCachedDependencies
override fun getModificationCount(): Long {
val currentGlobalCount = kotlinOutOfCodeBlockTracker.modificationCount
if (updater.hasPerModuleModificationCounts()) {
val selfCount = updater.getModificationCount(module)
if (selfCount == currentGlobalCount) return selfCount
var maxCount = selfCount
for (dependency in dependencies) {
val depCount = updater.getModificationCount(dependency)
if (depCount == currentGlobalCount) return currentGlobalCount
if (depCount > maxCount) maxCount = depCount
}
return maxCount
}
return currentGlobalCount
}
companion object {
@TestOnly
fun getModificationCount(module: Module): Long {
val updater = KotlinCodeBlockModificationListener.getInstance(module.project).perModuleOutOfCodeBlockTrackerUpdater
return updater.getModificationCount(module)
}
}
internal class Updater(project: Project) {
private val kotlinOfOfCodeBlockTracker by lazy {
KotlinCodeBlockModificationListener.getInstance(project).kotlinOutOfCodeBlockTracker
}
private val perModuleModCount = mutableMapOf<Module, Long>()
private var lastAffectedModule: Module? = null
private var lastAffectedModuleModCount = -1L
// All modifications since that count are known to be single-module modifications reflected in
// perModuleModCount map
private var perModuleChangesHighWatermark: Long? = null
internal fun getModificationCount(module: Module): Long {
return perModuleModCount[module] ?: perModuleChangesHighWatermark ?: kotlinOfOfCodeBlockTracker.modificationCount
}
internal fun hasPerModuleModificationCounts() = perModuleChangesHighWatermark != null
internal fun onKotlinPhysicalFileOutOfBlockChange(ktFile: KtFile, immediateUpdatesProcess: Boolean) {
lastAffectedModule = ModuleUtil.findModuleForPsiElement(ktFile)
lastAffectedModuleModCount = kotlinOfOfCodeBlockTracker.modificationCount
if (immediateUpdatesProcess) {
onPsiModificationTrackerUpdate(0)
}
}
internal fun onPsiModificationTrackerUpdate(customIncrement: Int = 0) {
val newModCount = kotlinOfOfCodeBlockTracker.modificationCount
val affectedModule = lastAffectedModule
if (affectedModule != null && newModCount == lastAffectedModuleModCount + customIncrement) {
if (perModuleChangesHighWatermark == null) {
perModuleChangesHighWatermark = lastAffectedModuleModCount
}
perModuleModCount[affectedModule] = newModCount
} else {
// Some updates were not processed in our code so they probably came from other languages. Invalidate all.
clean()
}
}
private fun clean() {
perModuleChangesHighWatermark = null
lastAffectedModule = null
perModuleModCount.clear()
}
}
}
@@ -1,44 +0,0 @@
/*
* 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.idea.fir
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector
import org.jetbrains.kotlin.fir.analysis.collectors.registerAllComponents
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnostic
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirPsiDiagnostic
import org.jetbrains.kotlin.psi.KtElement
class FirIdeDiagnosticsCollector(session: FirSession, private val resolveState: FirModuleResolveState) : AbstractDiagnosticCollector(session) {
init {
registerAllComponents()
}
private inner class Reporter : DiagnosticReporter() {
override fun report(diagnostic: FirDiagnostic<*>?) {
if (diagnostic !is FirPsiDiagnostic<*>) return
val psi = diagnostic.element.psi as? KtElement ?: return
resolveState.record(psi, diagnostic.asPsiBasedDiagnostic())
}
}
private lateinit var reporter: Reporter
override fun initializeCollector() {
reporter = Reporter()
}
override fun getCollectedDiagnostics(): Iterable<FirDiagnostic<*>> {
// Not necessary in IDE
return emptyList()
}
override fun runCheck(block: (DiagnosticReporter) -> Unit) {
block(reporter)
}
}
@@ -1,61 +0,0 @@
/*
* 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.idea.fir
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.fir.FirModuleBasedSession
import org.jetbrains.kotlin.fir.analysis.registerCheckersComponent
import org.jetbrains.kotlin.fir.java.FirProjectSessionProvider
import org.jetbrains.kotlin.fir.java.JavaSymbolProvider
import org.jetbrains.kotlin.fir.registerCommonComponents
import org.jetbrains.kotlin.fir.registerResolveComponents
import org.jetbrains.kotlin.fir.resolve.calls.jvm.registerJvmCallConflictResolverFactory
import org.jetbrains.kotlin.fir.resolve.firProvider
import org.jetbrains.kotlin.fir.resolve.providers.FirProvider
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProvider
import org.jetbrains.kotlin.fir.resolve.providers.impl.FirCompositeSymbolProvider
import org.jetbrains.kotlin.fir.resolve.scopes.wrapScopeWithJvmMapped
import org.jetbrains.kotlin.fir.scopes.KotlinScopeProvider
class FirIdeJavaModuleBasedSession(
moduleInfo: ModuleInfo,
sessionProvider: FirProjectSessionProvider
) : FirModuleBasedSession(moduleInfo, sessionProvider) {
companion object {
fun create(
project: Project,
moduleInfo: ModuleInfo,
sessionProvider: FirProjectSessionProvider,
scope: GlobalSearchScope
): FirIdeJavaModuleBasedSession {
return FirIdeJavaModuleBasedSession(moduleInfo, sessionProvider).apply {
registerCommonComponents()
registerResolveComponents()
registerCheckersComponent()
registerJvmCallConflictResolverFactory()
val firIdeProvider = FirIdeProvider(project, scope, this, KotlinScopeProvider(::wrapScopeWithJvmMapped))
register(FirProvider::class, firIdeProvider)
register(FirIdeProvider::class, firIdeProvider)
register(
FirSymbolProvider::class,
FirCompositeSymbolProvider(
listOf(
firProvider,
JavaSymbolProvider(this, sessionProvider.project, scope),
FirIdeModuleDependenciesSymbolProvider(this)
)
) as FirSymbolProvider
)
}
}
}
}
@@ -1,48 +0,0 @@
/*
* 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.idea.fir
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProvider
import org.jetbrains.kotlin.fir.resolve.providers.impl.FirDependenciesSymbolProviderImpl
import org.jetbrains.kotlin.fir.dependenciesWithoutSelf
import org.jetbrains.kotlin.fir.resolve.firSymbolProvider
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
import org.jetbrains.kotlin.fir.java.FirLibrarySession
import org.jetbrains.kotlin.fir.java.FirProjectSessionProvider
import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo
import org.jetbrains.kotlin.idea.caches.project.isLibraryClasses
import org.jetbrains.kotlin.idea.caches.resolve.IDEPackagePartProvider
class FirIdeModuleDependenciesSymbolProvider(
session: FirIdeJavaModuleBasedSession
) : FirDependenciesSymbolProviderImpl(session) {
override val dependencyProviders: List<FirSymbolProvider> by lazy {
val moduleInfo = session.moduleInfo
val sessionProvider = session.sessionProvider ?: return@lazy emptyList<FirSymbolProvider>()
val project = sessionProvider.project
val stateService = FirIdeResolveStateService.getInstance(project)
moduleInfo.dependenciesWithoutSelf().filterIsInstance<IdeaModuleInfo>().mapNotNull { dependencyInfo ->
val resolveState = stateService.getResolveState(dependencyInfo)
val dependencySession = when {
dependencyInfo is ModuleSourceInfo -> {
resolveState.getSession(project, dependencyInfo)
}
dependencyInfo.isLibraryClasses() -> {
val dependencyScope = dependencyInfo.contentScope()
FirLibrarySession.create(
dependencyInfo, sessionProvider as FirProjectSessionProvider,
dependencyScope, project, IDEPackagePartProvider(dependencyScope)
)
}
else -> return@mapNotNull null
}
dependencySession.firSymbolProvider
}.toList()
}
}
@@ -1,166 +0,0 @@
/*
* 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.idea.fir
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.builder.RawFirBuilder
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.resolve.providers.FirProvider
import org.jetbrains.kotlin.fir.resolve.providers.FirProviderInternals
import org.jetbrains.kotlin.fir.resolve.providers.impl.FirProviderImpl
import org.jetbrains.kotlin.fir.scopes.FirScope
import org.jetbrains.kotlin.fir.scopes.KotlinScopeProvider
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.idea.stubindex.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
class FirIdeProvider(
val project: Project,
val scope: GlobalSearchScope,
val session: FirSession,
kotlinScopeProvider: KotlinScopeProvider
) : FirProvider() {
private val cacheProvider = FirProviderImpl(session, kotlinScopeProvider)
data class FirFileWithStamp(val file: FirFile, val stamp: Long)
private val files = mutableMapOf<KtFile, FirFileWithStamp>()
override val isPhasedFirAllowed: Boolean
get() = true
override fun getFirClassifierByFqName(classId: ClassId): FirClassLikeDeclaration<*>? {
return cacheProvider.getFirClassifierByFqName(classId) ?: run {
try {
val classes = KotlinFullClassNameIndex.getInstance().get(classId.asSingleFqName().asString(), project, scope)
val ktClass = classes.firstOrNull {
classId.packageFqName == it.containingKtFile.packageFqName
} ?: return null // TODO: what if two of them?
val ktFile = ktClass.containingKtFile
getOrBuildFile(ktFile)
cacheProvider.getFirClassifierByFqName(classId)
} catch (e: ProcessCanceledException) {
return null
}
}
}
fun getOrBuildFile(ktFile: KtFile): FirFile {
val modificationStamp = ktFile.modificationStamp
files[ktFile]?.let { (firFile, stamp) ->
if (stamp == modificationStamp) {
return firFile
}
}
return synchronized(ktFile) {
var fileWithStamp = files[ktFile]
if (fileWithStamp != null && fileWithStamp.stamp == modificationStamp) {
fileWithStamp.file
} else {
val file = RawFirBuilder(session, cacheProvider.kotlinScopeProvider, stubMode = false).buildFirFile(ktFile)
cacheProvider.recordFile(file)
fileWithStamp = FirFileWithStamp(file, modificationStamp)
files[ktFile] = fileWithStamp
file
}
}
}
fun getFile(ktFile: KtFile): FirFile? {
val (firFile, stamp) = files[ktFile] ?: return null
if (stamp == ktFile.modificationStamp) return firFile
return null
}
override fun getClassLikeSymbolByFqName(classId: ClassId): FirClassLikeSymbol<*>? {
return getFirClassifierByFqName(classId)?.symbol
}
override fun getTopLevelCallableSymbols(packageFqName: FqName, name: Name): List<FirCallableSymbol<*>> {
val packagePrefix = if (packageFqName.isRoot) "" else "$packageFqName."
return try {
val topLevelFunctions = KotlinTopLevelFunctionFqnNameIndex.getInstance()["$packagePrefix$name", project, scope]
val topLevelProperties = KotlinTopLevelPropertyFqnNameIndex.getInstance()["$packagePrefix$name", project, scope]
topLevelFunctions.forEach { getOrBuildFile(it.containingKtFile) }
topLevelProperties.forEach { getOrBuildFile(it.containingKtFile) }
cacheProvider.getTopLevelCallableSymbols(packageFqName, name)
} catch (e: ProcessCanceledException) {
emptyList()
}
}
override fun getFirClassifierContainerFile(fqName: ClassId): FirFile {
getFirClassifierByFqName(fqName) // Necessary to ensure cacheProvider contains this classifier
return cacheProvider.getFirClassifierContainerFile(fqName)
}
override fun getFirClassifierContainerFileIfAny(fqName: ClassId): FirFile? {
getFirClassifierByFqName(fqName) // Necessary to ensure cacheProvider contains this classifier
return cacheProvider.getFirClassifierContainerFileIfAny(fqName)
}
override fun getFirClassifierContainerFile(symbol: FirClassLikeSymbol<*>): FirFile {
return getFirClassifierContainerFileIfAny(symbol)
?: error("Couldn't find container for ${symbol.classId}")
}
override fun getFirClassifierContainerFileIfAny(symbol: FirClassLikeSymbol<*>): FirFile? {
val psi = symbol.fir.source?.psi
if (psi is KtElement) {
return try {
val ktFile = psi.containingKtFile
getOrBuildFile(ktFile)
} catch (e: ProcessCanceledException) {
null
}
}
return getFirClassifierContainerFileIfAny(symbol.classId)
}
override fun getFirCallableContainerFile(symbol: FirCallableSymbol<*>): FirFile? {
return cacheProvider.getFirCallableContainerFile(symbol)
}
override fun getFirFilesByPackage(fqName: FqName): List<FirFile> {
return try {
val files = KotlinExactPackagesIndex.getInstance()[fqName.asString(), project, scope]
files.forEach { getOrBuildFile(it) }
cacheProvider.getFirFilesByPackage(fqName)
} catch (e: ProcessCanceledException) {
emptyList()
}
}
override fun getNestedClassifierScope(classId: ClassId): FirScope? {
getFirClassifierByFqName(classId)
return cacheProvider.getNestedClassifierScope(classId)
}
@FirProviderInternals
override fun recordGeneratedClass(owner: FirAnnotatedDeclaration, klass: FirRegularClass) {
// TODO: check that this implementation is correct
cacheProvider.recordGeneratedClass(owner, klass)
}
@FirProviderInternals
override fun recordGeneratedMember(owner: FirAnnotatedDeclaration, klass: FirDeclaration) {
// TODO: check that this implementation is correct
cacheProvider.recordGeneratedMember(owner, klass)
}
}
val FirSession.firIdeProvider: FirIdeProvider by FirSession.sessionComponentAccessor()
@@ -1,64 +0,0 @@
/*
* 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.idea.fir
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.ModificationTracker
import org.jetbrains.kotlin.analyzer.KotlinModificationTrackerService
import org.jetbrains.kotlin.analyzer.TrackableModuleInfo
import org.jetbrains.kotlin.fir.java.FirProjectSessionProvider
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
interface FirIdeResolveStateService {
companion object {
fun getInstance(project: Project): FirIdeResolveStateService =
ServiceManager.getService(project, FirIdeResolveStateService::class.java)!!
}
val fallbackModificationTracker: ModificationTracker?
fun getResolveState(moduleInfo: IdeaModuleInfo): FirModuleResolveState
}
private class FirModuleData(val state: FirModuleResolveState, val modificationTracker: ModificationTracker?) {
val modificationCount: Long = modificationTracker?.modificationCount ?: Long.MIN_VALUE
fun isOutOfDate(): Boolean {
val currentModCount = modificationTracker?.modificationCount
return currentModCount != null && currentModCount > modificationCount
}
}
class FirIdeResolveStateServiceImpl(val project: Project) : FirIdeResolveStateService {
private val stateCache = mutableMapOf<IdeaModuleInfo, FirModuleData>()
private fun createResolveState(): FirModuleResolveState {
val provider = FirProjectSessionProvider(project)
return FirModuleResolveStateImpl(provider)
}
private fun createModuleData(moduleInfo: IdeaModuleInfo): FirModuleData {
val state = createResolveState()
val modificationTracker = (moduleInfo as? TrackableModuleInfo)?.createModificationTracker() ?: fallbackModificationTracker
return FirModuleData(state, modificationTracker)
}
// TODO: multi thread protection
override fun getResolveState(moduleInfo: IdeaModuleInfo): FirModuleResolveState {
var moduleData = stateCache.getOrPut(moduleInfo) {
createModuleData(moduleInfo)
}
if (moduleData.isOutOfDate()) {
moduleData = createModuleData(moduleInfo)
stateCache[moduleInfo] = moduleData
}
return moduleData.state
}
override val fallbackModificationTracker: ModificationTracker? =
KotlinModificationTrackerService.getInstance(project).outOfBlockModificationTracker
}
@@ -1,112 +0,0 @@
/*
* 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.idea.fir
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnostic
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirPsiDiagnostic
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.extensions.BunchOfRegisteredExtensions
import org.jetbrains.kotlin.fir.extensions.extensionService
import org.jetbrains.kotlin.fir.extensions.registerExtensions
import org.jetbrains.kotlin.fir.java.FirProjectSessionProvider
import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
interface FirModuleResolveState {
val sessionProvider: FirProjectSessionProvider
fun getSession(psi: KtElement): FirSession {
val moduleInfo = psi.getModuleInfo() as ModuleSourceInfo
return getSession(psi.project, moduleInfo)
}
fun getSession(project: Project, moduleInfo: ModuleSourceInfo): FirSession {
sessionProvider.getSession(moduleInfo)?.let { return it }
return synchronized(moduleInfo.module) {
val session = sessionProvider.getSession(moduleInfo) ?: FirIdeJavaModuleBasedSession.create(
project, moduleInfo, sessionProvider, moduleInfo.contentScope()
).also { moduleBasedSession ->
sessionProvider.sessionCache[moduleInfo] = moduleBasedSession
}
session.also {
it.extensionService.registerExtensions(BunchOfRegisteredExtensions.empty())
}
}
}
operator fun get(psi: KtElement): FirElement?
fun getDiagnostics(psi: KtElement): List<Diagnostic>
fun hasDiagnosticsForFile(file: KtFile): Boolean
fun record(psi: KtElement, fir: FirElement)
fun record(psi: KtElement, diagnostic: Diagnostic)
fun setDiagnosticsForFile(file: KtFile, fir: FirFile, diagnostics: Iterable<FirDiagnostic<*>> = emptyList())
}
class FirModuleResolveStateImpl(override val sessionProvider: FirProjectSessionProvider) : FirModuleResolveState {
private val cache = mutableMapOf<KtElement, FirElement>()
private val diagnosticCache = mutableMapOf<KtElement, MutableList<Diagnostic>>()
private val diagnosedFiles = mutableMapOf<KtFile, Long>()
override fun get(psi: KtElement): FirElement? = cache[psi]
override fun getDiagnostics(psi: KtElement): List<Diagnostic> {
return diagnosticCache[psi] ?: emptyList()
}
override fun hasDiagnosticsForFile(file: KtFile): Boolean {
val previousStamp = diagnosedFiles[file] ?: return false
if (file.modificationStamp == previousStamp) {
return true
}
diagnosedFiles.remove(file)
file.accept(object : PsiElementVisitor() {
override fun visitElement(element: PsiElement) {
cache.remove(element)
diagnosticCache.remove(element)
element.acceptChildren(this)
super.visitElement(element)
}
})
return false
}
override fun record(psi: KtElement, fir: FirElement) {
cache[psi] = fir
}
override fun record(psi: KtElement, diagnostic: Diagnostic) {
val list = diagnosticCache.getOrPut(psi) { mutableListOf() }
list += diagnostic
}
override fun setDiagnosticsForFile(file: KtFile, fir: FirFile, diagnostics: Iterable<FirDiagnostic<*>>) {
for (diagnostic in diagnostics) {
require(diagnostic is FirPsiDiagnostic<*>)
val psi = diagnostic.element.psi as? KtElement ?: continue
record(psi, diagnostic.asPsiBasedDiagnostic())
}
diagnosedFiles[file] = file.modificationStamp
}
}
fun KtElement.firResolveState(): FirModuleResolveState =
FirIdeResolveStateService.getInstance(project).getResolveState(getModuleInfo())
@@ -1,25 +0,0 @@
/*
* 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.idea.fir
import com.intellij.openapi.util.registry.Registry
// Just a wrapper to see whether resolve works via FIR or not
object FirResolution {
private const val optionName = "kotlin.use.fir.resolution"
private val initialEnabledValue: Boolean by lazy {
Registry.`is`(optionName, /* defaultValue = */ true)
}
private var changedEnabledValue: Boolean? = null
var enabled: Boolean
get() = changedEnabledValue ?: initialEnabledValue
set(value) {
changedEnabledValue = value
}
}
@@ -1,286 +0,0 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. 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
import com.intellij.openapi.progress.ProgressIndicatorProvider
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.references.*
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.providers.FirProvider
import org.jetbrains.kotlin.fir.resolve.providers.getClassDeclaredCallableSymbols
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirBodyResolveTransformer
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.createReturnTypeCalculatorForIDE
import org.jetbrains.kotlin.fir.resolve.transformers.runResolve
import org.jetbrains.kotlin.fir.scopes.impl.FirPackageMemberScope
import org.jetbrains.kotlin.fir.symbols.CallableId
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.types.FirErrorTypeRef
import org.jetbrains.kotlin.fir.types.FirUserTypeRef
import org.jetbrains.kotlin.fir.visitors.CompositeTransformResult
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
import org.jetbrains.kotlin.fir.visitors.compose
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.util.containingNonLocalDeclaration
private val FirResolvePhase.stubMode: Boolean
get() = this <= FirResolvePhase.DECLARATIONS
private fun KtClassOrObject.relativeFqName(): FqName {
val className = this.nameAsSafeName
val parentFqName = this.containingClassOrObject?.relativeFqName()
return parentFqName?.child(className) ?: FqName.topLevel(className)
}
private fun FirFile.findCallableMember(
provider: FirProvider, callableMember: KtCallableDeclaration,
packageFqName: FqName, klassFqName: FqName?, declName: Name
): FirCallableDeclaration<*> {
if (klassFqName != null) {
return provider.getClassDeclaredCallableSymbols(ClassId(packageFqName, klassFqName, false), declName)
.find { symbol: FirCallableSymbol<*> ->
symbol.fir.psi == callableMember
}?.fir ?: error("Cannot find FIR callable declaration ${CallableId(packageFqName, klassFqName, declName)}")
}
// NB: not sure it's correct to use member scope provider from here (because of possible changes)
val memberScope = FirPackageMemberScope(this.packageFqName, session)
var result: FirCallableDeclaration<*>? = null
val processor = { symbol: FirCallableSymbol<*> ->
val fir = symbol.fir
if (result == null && fir.psi == callableMember) {
result = fir
}
}
if (callableMember is KtNamedFunction || callableMember is KtConstructor<*>) {
memberScope.processFunctionsByName(declName, processor)
} else {
memberScope.processPropertiesByName(declName, processor)
}
return result
?: error("Cannot find FIR callable declaration ${CallableId(packageFqName, klassFqName, declName)}")
}
fun KtCallableDeclaration.getOrBuildFir(
state: FirModuleResolveState,
phase: FirResolvePhase = FirResolvePhase.DECLARATIONS
): FirCallableDeclaration<*> {
val session = state.getSession(this)
val file = this.containingKtFile
val packageFqName = file.packageFqName
val klassFqName = this.containingClassOrObject?.relativeFqName()
val declName = this.nameAsSafeName
val firProvider = session.firIdeProvider
val firFile = firProvider.getOrBuildFile(file)
val firMemberSymbol = firFile.findCallableMember(firProvider, this, packageFqName, klassFqName, declName).symbol
val firMemberDeclaration = firMemberSymbol.fir
if (firMemberDeclaration.resolvePhase >= phase) {
return firMemberDeclaration
}
synchronized(firFile) {
firMemberDeclaration.runResolve(firFile, firProvider, phase, state)
}
return firMemberDeclaration
}
fun KtClassOrObject.getOrBuildFir(
state: FirModuleResolveState,
phase: FirResolvePhase = FirResolvePhase.DECLARATIONS
): FirMemberDeclaration {
val session = state.getSession(this)
val file = this.containingKtFile
val packageFqName = file.packageFqName
val klassFqName = this.relativeFqName()
val firProvider = session.firIdeProvider
val firFile = firProvider.getOrBuildFile(file)
val firClassOrEnumEntry = if (this is KtEnumEntry) {
val firEnumClass = firProvider.getFirClassifierByFqName(ClassId(packageFqName, klassFqName.parent(), false)) as FirRegularClass
firEnumClass.declarations.first { it is FirEnumEntry && it.name == this.nameAsSafeName } as FirMemberDeclaration
} else {
firProvider.getFirClassifierByFqName(ClassId(packageFqName, klassFqName, false)) as FirRegularClass
}
if (firClassOrEnumEntry.resolvePhase >= phase) {
return firClassOrEnumEntry
}
synchronized(firFile) {
firClassOrEnumEntry.runResolve(firFile, firProvider, phase, state)
}
return firClassOrEnumEntry
}
private fun KtFile.getOrBuildRawFirFile(state: FirModuleResolveState): Pair<FirIdeProvider, FirFile> {
val session = state.getSession(this)
val firProvider = session.firIdeProvider
return firProvider to firProvider.getOrBuildFile(this)
}
fun KtFile.getOrBuildFir(
state: FirModuleResolveState,
phase: FirResolvePhase = FirResolvePhase.DECLARATIONS
): FirFile {
val (firProvider, firFile) = getOrBuildRawFirFile(state)
if (phase <= FirResolvePhase.DECLARATIONS && firFile.resolvePhase >= phase) {
return firFile
}
synchronized(firFile) {
firFile.runResolve(firFile, firProvider, phase, state)
}
return firFile
}
fun KtFile.getOrBuildFirWithDiagnostics(state: FirModuleResolveState): FirFile {
val (_, firFile) = getOrBuildRawFirFile(state)
val currentResolvePhase = firFile.resolvePhase
if (currentResolvePhase < FirResolvePhase.BODY_RESOLVE) {
synchronized(firFile) {
firFile.runResolve(toPhase = FirResolvePhase.BODY_RESOLVE, fromPhase = currentResolvePhase)
}
}
ProgressIndicatorProvider.checkCanceled() // ???
if (state.hasDiagnosticsForFile(this)) return firFile
FirIdeDiagnosticsCollector(firFile.session, state).collectDiagnostics(firFile)
state.setDiagnosticsForFile(this, firFile)
return firFile
}
private fun FirDeclaration.runResolve(
file: FirFile,
firProvider: FirIdeProvider,
toPhase: FirResolvePhase,
state: FirModuleResolveState
) {
val nonLazyPhase = minOf(toPhase, FirResolvePhase.DECLARATIONS)
file.runResolve(toPhase = nonLazyPhase, fromPhase = this.resolvePhase)
if (toPhase <= nonLazyPhase) return
val designation = mutableListOf<FirDeclaration>(file)
if (this !is FirFile) {
val id = when (this) {
is FirCallableDeclaration<*> -> {
this.symbol.callableId.classId
}
is FirRegularClass -> {
this.symbol.classId
}
else -> error("Unsupported: ${render()}")
}
val outerClasses = generateSequence(id) { classId ->
classId.outerClassId
}.mapTo(mutableListOf()) { firProvider.getFirClassifierByFqName(it)!! }
designation += outerClasses.asReversed()
if (this is FirCallableDeclaration<*>) {
designation += this
}
}
if (designation.all { it.resolvePhase >= toPhase }) {
return
}
val scopeSession = ScopeSession()
val transformer = FirDesignatedBodyResolveTransformerForIDE(
designation.iterator(), state.getSession(psi as KtElement),
scopeSession,
implicitTypeOnly = toPhase == FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE
)
file.transform<FirFile, ResolutionMode>(transformer, ResolutionMode.ContextDependent)
}
private class FirDesignatedBodyResolveTransformerForIDE(
private val designation: Iterator<FirElement>,
session: FirSession,
scopeSession: ScopeSession,
implicitTypeOnly: Boolean
) : FirBodyResolveTransformer(
session,
phase = FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE,
implicitTypeOnly = implicitTypeOnly,
scopeSession = scopeSession,
returnTypeCalculator = createReturnTypeCalculatorForIDE(session, scopeSession)
) {
override fun transformDeclarationContent(declaration: FirDeclaration, data: ResolutionMode): CompositeTransformResult<FirDeclaration> {
if (designation.hasNext()) {
designation.next().visitNoTransform(this, data)
return declaration.compose()
}
return super.transformDeclarationContent(declaration, data)
}
}
fun KtElement.getOrBuildFir(
state: FirModuleResolveState,
phase: FirResolvePhase = FirResolvePhase.BODY_RESOLVE
): FirElement {
val containerFir: FirDeclaration =
when (val container = this.containingNonLocalDeclaration()) {
is KtCallableDeclaration -> container.getOrBuildFir(state, phase)
is KtClassOrObject -> container.getOrBuildFir(state, phase)
null -> containingKtFile.getOrBuildFir(state, phase)
else -> error("Unsupported: ${container.text}")
}
val psi = when (this) {
is KtPropertyDelegate -> this.expression ?: this
else -> this
}
return state[this] ?: run {
containerFir.accept(object : FirVisitorVoid() {
override fun visitElement(element: FirElement) {
(element.psi as? KtElement)?.let {
state.record(it, element)
}
element.acceptChildren(this)
}
override fun visitReference(reference: FirReference) {}
override fun visitControlFlowGraphReference(controlFlowGraphReference: FirControlFlowGraphReference) {}
override fun visitNamedReference(namedReference: FirNamedReference) {}
override fun visitResolvedNamedReference(resolvedNamedReference: FirResolvedNamedReference) {}
override fun visitDelegateFieldReference(delegateFieldReference: FirDelegateFieldReference) {}
override fun visitBackingFieldReference(backingFieldReference: FirBackingFieldReference) {}
override fun visitSuperReference(superReference: FirSuperReference) {}
override fun visitThisReference(thisReference: FirThisReference) {}
override fun visitErrorTypeRef(errorTypeRef: FirErrorTypeRef) {}
override fun visitUserTypeRef(userTypeRef: FirUserTypeRef) {
userTypeRef.acceptChildren(this)
}
})
var current: PsiElement? = psi
while (current is KtElement) {
val mappedFir = state[current]
if (mappedFir != null) {
if (current != this) {
state.record(current, mappedFir)
}
return mappedFir
}
current = current.parent
}
error("FirElement is not found for: $text")
}
}
@@ -1,17 +1,6 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.highlighter
@@ -59,6 +48,7 @@ open class KotlinPsiChecker : Annotator, HighlightRangeExtension {
if (!KotlinHighlightingUtil.shouldHighlight(file)) return
//todo move all fir stuff to fir plugin
if (FirResolution.enabled) {
annotateElementUsingFrontendIR(element, file, holder)
} else {
@@ -1,164 +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.references
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.references.*
import org.jetbrains.kotlin.fir.resolve.firSymbolProvider
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl
import org.jetbrains.kotlin.fir.types.ConeLookupTagBasedType
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
import org.jetbrains.kotlin.idea.fir.firResolveState
import org.jetbrains.kotlin.idea.fir.getOrBuildFir
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtPackageDirective
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
object FirReferenceResolveHelper {
fun FirResolvedTypeRef.toTargetPsi(session: FirSession): PsiElement? {
val type = type as? ConeLookupTagBasedType ?: return null
return (type.lookupTag.toSymbol(session) as? AbstractFirBasedSymbol<*>)?.fir?.psi
}
fun ClassId.toTargetPsi(session: FirSession, calleeReference: FirReference? = null): PsiElement? {
val classLikeDeclaration = ConeClassLikeLookupTagImpl(this).toSymbol(session)?.fir
if (classLikeDeclaration is FirRegularClass) {
if (calleeReference is FirResolvedNamedReference) {
val callee = calleeReference.resolvedSymbol.fir as? FirCallableMemberDeclaration
// TODO: check callee owner directly?
if (callee !is FirConstructor && callee?.isStatic != true) {
classLikeDeclaration.companionObject?.let { return it.psi }
}
}
}
return classLikeDeclaration?.psi
}
fun FirReference.toTargetPsi(session: FirSession): PsiElement? {
return when (this) {
is FirResolvedNamedReference -> {
resolvedSymbol.fir.psi
}
is FirResolvedCallableReference -> {
resolvedSymbol.fir.psi
}
is FirThisReference -> {
boundSymbol?.fir?.psi
}
is FirSuperReference -> {
(superTypeRef as? FirResolvedTypeRef)?.toTargetPsi(session)
}
else -> {
null
}
}
}
fun resolveToPsiElements(ref: AbstractKtReference<KtElement>): Collection<PsiElement> {
val expression = ref.expression
val state = expression.firResolveState()
val session = state.getSession(expression)
when (val fir = expression.getOrBuildFir(state)) {
is FirResolvable -> {
return listOfNotNull(fir.calleeReference.toTargetPsi(session))
}
is FirResolvedTypeRef -> {
return listOfNotNull(fir.toTargetPsi(session))
}
is FirResolvedQualifier -> {
val classId = fir.classId ?: return emptyList()
// Distinguish A.foo() from A(.Companion).foo()
// Make expression.parent as? KtDotQualifiedExpression local function
var parent = expression.parent as? KtDotQualifiedExpression
while (parent != null) {
val selectorExpression = parent.selectorExpression ?: break
if (selectorExpression === expression) {
parent = parent.parent as? KtDotQualifiedExpression
continue
}
val parentFir = selectorExpression.getOrBuildFir(state)
if (parentFir is FirQualifiedAccess) {
return listOfNotNull(classId.toTargetPsi(session, parentFir.calleeReference))
}
parent = parent.parent as? KtDotQualifiedExpression
}
return listOfNotNull(classId.toTargetPsi(session))
}
is FirAnnotationCall -> {
val type = fir.typeRef as? FirResolvedTypeRef ?: return emptyList()
return listOfNotNull(type.toTargetPsi(session))
}
is FirResolvedImport -> {
var parent = expression.parent
while (parent is KtDotQualifiedExpression) {
if (parent.selectorExpression !== expression) {
// Special: package reference in the middle of import directive
// import a.<caret>b.c.SomeClass
// TODO: return reference to PsiPackage
return listOf(expression)
}
parent = parent.parent
}
val classId = fir.resolvedClassId
if (classId != null) {
return listOfNotNull(classId.toTargetPsi(session))
}
val name = fir.importedName ?: return emptyList()
val symbolProvider = session.firSymbolProvider
return symbolProvider.getTopLevelCallableSymbols(fir.packageFqName, name).mapNotNull { it.fir.psi } +
listOfNotNull(symbolProvider.getClassLikeSymbolByFqName(ClassId(fir.packageFqName, name))?.fir?.psi)
}
is FirFile -> {
if (expression.getNonStrictParentOfType<KtPackageDirective>() != null) {
// Special: package reference in the middle of package directive
return listOf(expression)
}
return listOfNotNull(fir.psi)
}
is FirArrayOfCall -> {
// We can't yet find PsiElement for arrayOf, intArrayOf, etc.
return emptyList()
}
is FirErrorNamedReference -> {
return emptyList()
}
else -> {
// Handle situation when we're in the middle/beginning of qualifier
// <caret>A.B.C.foo() or A.<caret>B.C.foo()
// NB: in this case we get some parent FIR, like FirBlock, FirProperty, FirFunction or the like
var parent = expression.parent as? KtDotQualifiedExpression
var unresolvedCounter = 1
while (parent != null) {
val selectorExpression = parent.selectorExpression ?: break
if (selectorExpression === expression) {
parent = parent.parent as? KtDotQualifiedExpression
continue
}
val parentFir = selectorExpression.getOrBuildFir(state)
if (parentFir is FirResolvedQualifier) {
var classId = parentFir.classId
while (unresolvedCounter > 0) {
unresolvedCounter--
classId = classId?.outerClassId
}
return listOfNotNull(classId?.toTargetPsi(session))
}
parent = parent.parent as? KtDotQualifiedExpression
unresolvedCounter++
}
return emptyList()
}
}
}
}
@@ -24,9 +24,6 @@ import org.jetbrains.kotlin.fir.types.ConeLookupTagBasedType
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.fir.FirResolution
import org.jetbrains.kotlin.idea.fir.firResolveState
import org.jetbrains.kotlin.idea.fir.getOrBuildFir
import org.jetbrains.kotlin.idea.util.application.runWithCancellationCheck
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
@@ -66,10 +63,6 @@ object KotlinDescriptorsBasedReferenceResolver : ResolveCache.PolyVariantResolve
class KotlinResolveResult(element: PsiElement) : PsiElementResolveResult(element)
private fun resolveToPsiElements(ref: KtDescriptorsBasedReference): Collection<PsiElement> {
if (FirResolution.enabled) {
@Suppress("UNCHECKED_CAST")
return FirReferenceResolveHelper.resolveToPsiElements(ref as AbstractKtReference<KtElement>)
}
val bindingContext = ref.element.analyze(BodyResolveMode.PARTIAL)
return resolveToPsiElements(ref, bindingContext, ref.getTargetDescriptors(bindingContext))
}
@@ -45,6 +45,9 @@ class KtSimpleNameReferenceDescriptorsImpl(
override fun doCanBeReferenceTo(candidateTarget: PsiElement): Boolean =
canBeReferenceTo(candidateTarget)
override fun isReferenceToWithoutExtensionChecking(candidateTarget: PsiElement): Boolean =
matchesTarget(candidateTarget)
override fun getTargetDescriptors(context: BindingContext): Collection<DeclarationDescriptor> {
return SmartList<DeclarationDescriptor>().apply {
// Replace Java property with its accessor(s)