diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/findUsages/KotlinFindUsagesSupportFirImpl.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/findUsages/KotlinFindUsagesSupportFirImpl.kt index 67573259170..b10233b1ff1 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/findUsages/KotlinFindUsagesSupportFirImpl.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/findUsages/KotlinFindUsagesSupportFirImpl.kt @@ -5,11 +5,26 @@ package org.jetbrains.kotlin.idea.findUsages +import com.intellij.ide.IdeBundle import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.Messages import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod import com.intellij.psi.PsiReference import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.core.util.showYesNoCancelDialog +import org.jetbrains.kotlin.idea.frontend.api.analyseInModalWindow +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtNamedSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithKind +import org.jetbrains.kotlin.idea.refactoring.CHECK_SUPER_METHODS_YES_NO_DIALOG +import org.jetbrains.kotlin.idea.refactoring.formatPsiClass +import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext class KotlinFindUsagesSupportFirImpl : KotlinFindUsagesSupport { override fun isCallReceiverRefersToCompanionObject(element: KtElement, companionObject: KtObjectDeclaration): Boolean { @@ -33,7 +48,65 @@ class KotlinFindUsagesSupportFirImpl : KotlinFindUsagesSupport { } override fun checkSuperMethods(declaration: KtDeclaration, ignore: Collection?, actionString: String): List { - return listOf(declaration) + + if (!declaration.hasModifier(KtTokens.OVERRIDE_KEYWORD)) return listOf(declaration) + + data class AnalyzedModel( + val declaredClassRender: String, + val overriddenDeclarationsAndRenders: Map + ) + + fun getClassDescription(overriddenElement: PsiElement, containingSymbol: KtSymbolWithKind?): String = + when (overriddenElement) { + is KtNamedFunction, is KtProperty, is KtParameter -> (containingSymbol as? KtNamedSymbol)?.name?.asString() ?: "Unknown" //TODO render symbols + is PsiMethod -> { + val psiClass = overriddenElement.containingClass ?: error("Invalid element: ${overriddenElement.text}") + formatPsiClass(psiClass, markAsJava = true, inCode = false) + } + else -> error("Unexpected element: ${overriddenElement.getElementTextWithContext()}") + }.let { " $it\n" } + + + val analyzeResult = analyseInModalWindow(declaration, KotlinBundle.message("find.usages.progress.text.declaration.superMethods")) { + (declaration.getSymbol() as? KtCallableSymbol)?.let { callableSymbol -> + ((callableSymbol as? KtSymbolWithKind)?.getContainingSymbol() as? KtClassOrObjectSymbol)?.let { containingClass -> + val overriddenSymbols = callableSymbol.getOverriddenSymbols(containingClass) + + val renderToPsi = overriddenSymbols.mapNotNull { + it.psi?.let { psi -> + psi to getClassDescription(psi, (it as? KtSymbolWithKind)?.getContainingSymbol()) + } + } + + val filteredDeclarations = + if (ignore != null) renderToPsi.filter { ignore.contains(it.first) } else renderToPsi + + val renderedClass = containingClass.name.asString() //TODO render class + + AnalyzedModel(renderedClass, filteredDeclarations.toMap()) + } + } + } ?: return listOf(declaration) + + if (analyzeResult.overriddenDeclarationsAndRenders.isEmpty()) return listOf(declaration) + + val message = KotlinBundle.message( + "override.declaration.x.overrides.y.in.class.list", + analyzeResult.declaredClassRender, + "\n${analyzeResult.overriddenDeclarationsAndRenders.values.joinToString(separator = "")}", + actionString + ) + + val exitCode = showYesNoCancelDialog( + CHECK_SUPER_METHODS_YES_NO_DIALOG, + declaration.project, message, IdeBundle.message("title.warning"), Messages.getQuestionIcon(), Messages.YES + ) + + return when (exitCode) { + Messages.YES -> listOf(declaration) + analyzeResult.overriddenDeclarationsAndRenders.keys + Messages.NO -> listOf(declaration) + else -> emptyList() + } } override fun sourcesAndLibraries(delegate: GlobalSearchScope, project: Project): GlobalSearchScope { diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt index 39cf8f56b13..6b972a6507e 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt @@ -41,12 +41,14 @@ abstract class KtAnalysisSession(override val token: ValidityToken) : ValidityTo protected abstract val symbolProvider: KtSymbolProvider protected abstract val callResolver: KtCallResolver protected abstract val completionCandidateChecker: KtCompletionCandidateChecker - + protected abstract val symbolDeclarationOverridesProvider: KtSymbolDeclarationOverridesProvider /// TODO: get rid of @Deprecated("Used only in completion now, temporary") abstract fun createContextDependentCopy(): KtAnalysisSession + fun KtCallableSymbol.getOverriddenSymbols(containingDeclaration: KtClassOrObjectSymbol): List = + symbolDeclarationOverridesProvider.getOverriddenSymbols(this, containingDeclaration) fun KtExpression.getSmartCasts(): Collection = smartCastProvider.getSmartCastedToTypes(this) diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtSymbolDeclarationOverridesProvider.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtSymbolDeclarationOverridesProvider.kt new file mode 100644 index 00000000000..17b38c1d3ba --- /dev/null +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtSymbolDeclarationOverridesProvider.kt @@ -0,0 +1,23 @@ +/* + * 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.frontend.api.components + +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbol + +abstract class KtSymbolDeclarationOverridesProvider : KtAnalysisSessionComponent() { + /** + * Returns symbols that overridden by requested + */ + abstract fun getOverriddenSymbols( + callableSymbol: T, + containingDeclaration: KtClassOrObjectSymbol + ): List + + //abstract fun getOverriddenSymbols(callableSymbol: KtCallableSymbol, containingDeclaration: KtClassOrObjectSymbol): List +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtFirAnalysisSession.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtFirAnalysisSession.kt index 6a7719d602a..bc2612b5029 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtFirAnalysisSession.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtFirAnalysisSession.kt @@ -42,7 +42,8 @@ private constructor( override val symbolProvider: KtSymbolProvider = KtFirSymbolProvider(this, firResolveState.firIdeLibrariesSession.firSymbolProvider, firResolveState, firSymbolBuilder, token) override val completionCandidateChecker: KtCompletionCandidateChecker by threadLocal { KtFirCompletionCandidateChecker(this, token) } - + override val symbolDeclarationOverridesProvider: KtSymbolDeclarationOverridesProvider + by threadLocal { KtFirSymbolDeclarationOverridesProvider(this, token) } override fun createContextDependentCopy(): KtAnalysisSession { check(!isContextSession) { "Cannot create context-dependent copy of KtAnalysis session from a context dependent one" } diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolDeclarationOverridesProvider.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolDeclarationOverridesProvider.kt new file mode 100644 index 00000000000..b8351acc019 --- /dev/null +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolDeclarationOverridesProvider.kt @@ -0,0 +1,67 @@ +/* + * 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.frontend.api.fir.components + +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.resolve.ScopeSession +import org.jetbrains.kotlin.fir.scopes.* +import org.jetbrains.kotlin.idea.frontend.api.ValidityToken +import org.jetbrains.kotlin.idea.frontend.api.components.KtSymbolDeclarationOverridesProvider +import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirClassOrObjectSymbol +import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.* + +internal class KtFirSymbolDeclarationOverridesProvider( + override val analysisSession: KtFirAnalysisSession, + override val token: ValidityToken, +) : KtSymbolDeclarationOverridesProvider(), KtFirAnalysisSessionComponent { + + private fun FirTypeScope.processCallableByName(declaration: FirDeclaration) = when (declaration) { + is FirSimpleFunction -> processFunctionsByName(declaration.name) { } + is FirProperty -> processPropertiesByName(declaration.name) { } + else -> error { " Invalid FIR symbol to process " } + } + + private fun FirTypeScope.processOverriddenDeclarations( + declaration: FirDeclaration, + processor: (FirCallableDeclaration<*>) -> ProcessorAction + ) = when (declaration) { + is FirSimpleFunction -> processOverriddenFunctions(declaration.symbol) { processor.invoke(it.fir) } + is FirProperty -> processOverriddenProperties(declaration.symbol) { processor.invoke(it.fir) } + else -> error { " Invalid FIR symbol to process " } + } + + override fun getOverriddenSymbols( + callableSymbol: T, + containingDeclaration: KtClassOrObjectSymbol + ): List { + + check(callableSymbol is KtFirSymbol<*>) + check(containingDeclaration is KtFirClassOrObjectSymbol) + + return callableSymbol.firRef.withFir(FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE) { firCallableElement -> + + containingDeclaration.firRef.withFir(FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE) { containingDeclaration -> + + val firTypeScope = containingDeclaration.unsubstitutedScope( + containingDeclaration.session, + ScopeSession() + ) + + val overriddenElement = mutableSetOf() + firTypeScope.processCallableByName(firCallableElement) + firTypeScope.processOverriddenDeclarations(firCallableElement) { overriddenDeclaration -> + val ktSymbol = analysisSession.firSymbolBuilder.buildCallableSymbol(overriddenDeclaration) + overriddenElement.add(ktSymbol) + ProcessorAction.NEXT + } + + overriddenElement.toList() + } + } + } +} \ No newline at end of file