181: compilation fix when access progressIndicator in ProgressManager by adding !!

This commit is contained in:
Nicolay Mitropolsky
2017-12-11 11:28:57 +03:00
committed by Nikolay Krasko
parent 395a817ada
commit f291873cf8
8 changed files with 1872 additions and 0 deletions
@@ -0,0 +1,216 @@
/*
* Copyright 2010-2015 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.
*/
package org.jetbrains.kotlin.idea.actions
import com.intellij.codeInsight.navigation.NavigationUtil
import com.intellij.ide.scratch.ScratchFileService
import com.intellij.ide.scratch.ScratchRootType
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.ex.MessagesEx
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileVisitor
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiErrorElement
import com.intellij.psi.PsiJavaFile
import com.intellij.psi.PsiManager
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.j2k.IdeaJavaToKotlinServices
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
import org.jetbrains.kotlin.idea.refactoring.toPsiFile
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.j2k.ConverterSettings
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
import org.jetbrains.kotlin.psi.KtFile
import java.io.File
import java.io.IOException
import java.util.*
class JavaToKotlinAction : AnAction() {
companion object {
private fun uniqueKotlinFileName(javaFile: VirtualFile): String {
val ioFile = File(javaFile.path.replace('/', File.separatorChar))
var i = 0
while (true) {
val fileName = javaFile.nameWithoutExtension + (if (i > 0) i else "") + ".kt"
if (!ioFile.resolveSibling(fileName).exists()) return fileName
i++
}
}
val title = "Convert Java to Kotlin"
private fun saveResults(javaFiles: List<PsiJavaFile>, convertedTexts: List<String>): List<VirtualFile> {
val result = ArrayList<VirtualFile>()
for ((psiFile, text) in javaFiles.zip(convertedTexts)) {
try {
val document = PsiDocumentManager.getInstance(psiFile.project).getDocument(psiFile)
if (document == null) {
MessagesEx.error(psiFile.project, "Failed to save conversion result: couldn't find document for " + psiFile.name).showLater()
continue
}
document.replaceString(0, document.textLength, text)
FileDocumentManager.getInstance().saveDocument(document)
val virtualFile = psiFile.virtualFile
if (ScratchRootType.getInstance().containsFile(virtualFile)) {
val mapping = ScratchFileService.getInstance().scratchesMapping
mapping.setMapping(virtualFile, KotlinFileType.INSTANCE.language)
}
else {
val fileName = uniqueKotlinFileName(virtualFile)
virtualFile.rename(this, fileName)
}
}
catch (e: IOException) {
MessagesEx.error(psiFile.project, e.message ?: "").showLater()
}
}
return result
}
fun convertFiles(javaFiles: List<PsiJavaFile>, project: Project, enableExternalCodeProcessing: Boolean = true): List<KtFile> {
var converterResult: JavaToKotlinConverter.FilesResult? = null
fun convert() {
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaJavaToKotlinServices)
converterResult = converter.filesToKotlin(javaFiles, J2kPostProcessor(formatCode = true), ProgressManager.getInstance().progressIndicator!!)
}
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(
::convert,
title,
true,
project)) return emptyList()
var externalCodeUpdate: (() -> Unit)? = null
if (enableExternalCodeProcessing && converterResult!!.externalCodeProcessing != null) {
val question = "Some code in the rest of your project may require corrections after performing this conversion. Do you want to find such code and correct it too?"
if (Messages.showOkCancelDialog(project, question, title, Messages.getQuestionIcon()) == Messages.OK) {
ProgressManager.getInstance().runProcessWithProgressSynchronously(
{
runReadAction {
externalCodeUpdate = converterResult!!.externalCodeProcessing!!.prepareWriteOperation(ProgressManager.getInstance().progressIndicator!!)
}
},
title,
true,
project)
}
}
return project.executeWriteCommand("Convert files from Java to Kotlin", null) {
CommandProcessor.getInstance().markCurrentCommandAsGlobal(project)
val newFiles = saveResults(javaFiles, converterResult!!.results)
externalCodeUpdate?.invoke()
PsiDocumentManager.getInstance(project).commitAllDocuments()
newFiles.singleOrNull()?.let {
FileEditorManager.getInstance(project).openFile(it, true)
}
newFiles.map { it.toPsiFile(project) as KtFile }
}
}
}
override fun actionPerformed(e: AnActionEvent) {
val javaFiles = selectedJavaFiles(e).filter { it.isWritable }.toList()
val project = CommonDataKeys.PROJECT.getData(e.dataContext)!!
val firstSyntaxError = javaFiles.asSequence().map { PsiTreeUtil.findChildOfType(it, PsiErrorElement::class.java) }.firstOrNull()
if (firstSyntaxError != null) {
val count = javaFiles.filter { PsiTreeUtil.hasErrorElements(it) }.count()
val question = firstSyntaxError.containingFile.name +
(if (count > 1) " and ${count - 1} more Java files" else " file") +
" contain syntax errors, the conversion result may be incorrect"
val okText = "Investigate Errors"
val cancelText = "Proceed with Conversion"
if (Messages.showOkCancelDialog(
project,
question,
title,
okText,
cancelText,
Messages.getWarningIcon()
) == Messages.OK) {
NavigationUtil.activateFileWithPsiElement(firstSyntaxError.navigationElement)
return
}
}
convertFiles(javaFiles, project)
}
override fun update(e: AnActionEvent) {
val virtualFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY) ?: return
val project = e.project ?: return
e.presentation.isEnabled = isAnyJavaFileSelected(project, virtualFiles)
}
private fun isAnyJavaFileSelected(project: Project, files: Array<VirtualFile>): Boolean {
val manager = PsiManager.getInstance(project)
if (files.any { manager.findFile(it) is PsiJavaFile && it.isWritable }) return true
return files.any { it.isDirectory && isAnyJavaFileSelected(project, it.children) }
}
private fun selectedJavaFiles(e: AnActionEvent): Sequence<PsiJavaFile> {
val virtualFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY) ?: return sequenceOf()
val project = e.project ?: return sequenceOf()
return allJavaFiles(virtualFiles, project)
}
private fun allJavaFiles(filesOrDirs: Array<VirtualFile>, project: Project): Sequence<PsiJavaFile> {
val manager = PsiManager.getInstance(project)
return allFiles(filesOrDirs)
.asSequence()
.mapNotNull { manager.findFile(it) as? PsiJavaFile }
}
private fun allFiles(filesOrDirs: Array<VirtualFile>): Collection<VirtualFile> {
val result = ArrayList<VirtualFile>()
for (file in filesOrDirs) {
VfsUtilCore.visitChildrenRecursively(file, object : VirtualFileVisitor<Unit>() {
override fun visitFile(file: VirtualFile): Boolean {
result.add(file)
return true
}
})
}
return result
}
}
@@ -0,0 +1,164 @@
/*
* 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.
*/
package org.jetbrains.kotlin.idea.actions.internal
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.ProgressManager.progress
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiModifier
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.load.java.isFromJava
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
class SearchNotPropertyCandidatesAction : AnAction() {
override fun actionPerformed(e: AnActionEvent?) {
val project = e?.project!!
val psiFile = e.getData(CommonDataKeys.PSI_FILE) as? KtFile ?: return
val desc = psiFile.findModuleDescriptor()
val result = Messages.showInputDialog("Enter package FqName", "Search for Not Property candidates", Messages.getQuestionIcon())
val packageDesc = try {
val fqName = FqName.fromSegments(result!!.split('.'))
desc.getPackage(fqName)
}
catch (e: Exception) {
return
}
ProgressManager.getInstance().runProcessWithProgressSynchronously(
{
runReadAction {
ProgressManager.getInstance().progressIndicator!!.isIndeterminate = true
processAllDescriptors(packageDesc, project)
}
},
"Searching for Not Property candidates",
true,
project)
}
private fun processAllDescriptors(desc: DeclarationDescriptor, project: Project) {
val processed = mutableSetOf<DeclarationDescriptor>()
var pFunctions = 0
val matchedDescriptors = mutableSetOf<FunctionDescriptor>()
fun recursive(desc: DeclarationDescriptor) {
if (desc in processed) return
progress("Step 1: Collecting ${processed.size}:$pFunctions:${matchedDescriptors.size}", "$desc")
when (desc) {
is ModuleDescriptor -> recursive(desc.getPackage(FqName("java")))
is ClassDescriptor -> desc.unsubstitutedMemberScope.getContributedDescriptors { true }.forEach(::recursive)
is PackageViewDescriptor -> desc.memberScope.getContributedDescriptors { true }.forEach(::recursive)
is FunctionDescriptor -> {
if (desc.isFromJava) {
val name = desc.fqNameUnsafe.shortName().asString()
if (name.length > 3 &&
((name.startsWith("get") && desc.valueParameters.isEmpty() && desc.returnType != null) ||
(name.startsWith("set") && desc.valueParameters.size == 1))) {
if (desc in matchedDescriptors) return
matchedDescriptors += desc
}
}
pFunctions++
return
}
}
processed += desc
}
recursive(desc)
val resultDescriptors = mutableSetOf<FunctionDescriptor>()
matchedDescriptors.flatMapTo(resultDescriptors) {
sequenceOf(it, *(it.overriddenDescriptors.toTypedArray())).asIterable()
}
println("Found ${resultDescriptors.size} accessors")
fun PsiMethod.isTrivial(): Boolean {
val t = this.text
val s = t.indexOf('{')
val e = t.lastIndexOf('}')
return if (s != e && s != -1) t.substring(s, e).lines().size <= 3 else true
}
val descriptorToPsiBinding = mutableMapOf<FunctionDescriptor, PsiMethod>()
var i = 0
resultDescriptors.forEach { desc ->
progress("Step 2: ${i++} of ${resultDescriptors.size}", "$desc")
val source = DescriptorToSourceUtilsIde.getAllDeclarations(project, desc)
.filterIsInstance<PsiMethod>()
.firstOrNull() ?: return@forEach
val abstract = source.modifierList.hasModifierProperty(PsiModifier.ABSTRACT)
if (!abstract) {
if (!source.isTrivial()) {
descriptorToPsiBinding[desc] = source
}
}
}
val nonTrivial = mutableSetOf<FunctionDescriptor>()
i = 0
descriptorToPsiBinding.forEach { t, u ->
progress("Step 3: ${i++} of ${descriptorToPsiBinding.size}", "$t")
val descriptors = t.overriddenDescriptors
var impl = false
descriptors.forEach {
val source = DescriptorToSourceUtilsIde.getAllDeclarations(project, it).filterIsInstance<PsiMethod>().firstOrNull()
if (source != null) {
if (source.body != null || source.hasModifierProperty(PsiModifier.ABSTRACT))
nonTrivial += it
impl = true
}
}
if (u.body != null)
if (!impl)
nonTrivial += t
}
nonTrivial.forEach(::println)
println("Non trivial count: ${nonTrivial.size}")
}
override fun update(e: AnActionEvent) {
if (!KotlinInternalMode.enabled) {
e.presentation.isVisible = false
e.presentation.isEnabled = false
}
else {
e.presentation.isVisible = true
e.presentation.isEnabled = e.getData(CommonDataKeys.PSI_FILE) is KtFile
}
}
}
@@ -0,0 +1,356 @@
/*
* 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.
*/
package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.core.*
import org.jetbrains.kotlin.idea.refactoring.CallableRefactoring
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.explicateReceiverOf
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.getAffectedCallables
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler
import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.references.KtSimpleReference
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.search.usagesSearch.searchReferencesOrMethodReferences
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.psi.typeRefHelpers.setReceiverTypeReference
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.getAbbreviatedTypeOrType
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getArgumentByParameterIndex
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
class ConvertFunctionTypeParameterToReceiverIntention : SelfTargetingRangeIntention<KtTypeReference>(
KtTypeReference::class.java,
"Convert function type parameter to receiver"
) {
class FunctionDefinitionInfo(element: KtFunction) : AbstractProcessableUsageInfo<KtFunction, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val function = element ?: return
val functionParameter = function.valueParameters.getOrNull(data.functionParameterIndex) ?: return
val functionType = functionParameter.typeReference?.typeElement as? KtFunctionType ?: return
val functionTypeParameterList = functionType.parameterList ?: return
val parameterToMove = functionTypeParameterList.parameters.getOrNull(data.typeParameterIndex) ?: return
val typeReferenceToMove = parameterToMove.typeReference ?: return
functionType.setReceiverTypeReference(typeReferenceToMove)
functionTypeParameterList.removeParameter(parameterToMove)
}
}
class ParameterCallInfo(element: KtCallExpression) : AbstractProcessableUsageInfo<KtCallExpression, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val callExpression = element ?: return
val argumentList = callExpression.valueArgumentList ?: return
val expressionToMove = argumentList.arguments.getOrNull(data.typeParameterIndex)?.getArgumentExpression() ?: return
val callWithReceiver = KtPsiFactory(callExpression).createExpressionByPattern("$0.$1", expressionToMove, callExpression) as KtQualifiedExpression
(callWithReceiver.selectorExpression as KtCallExpression).valueArgumentList!!.removeArgument(data.typeParameterIndex)
callExpression.replace(callWithReceiver)
}
}
class InternalReferencePassInfo(element: KtSimpleNameExpression) : AbstractProcessableUsageInfo<KtSimpleNameExpression, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val expression = element ?: return
val lambdaType = data.lambdaType
val validator = CollectingNameValidator()
val parameterNames = lambdaType.arguments
.dropLast(1)
.map { KotlinNameSuggester.suggestNamesByType(it.type, validator, "p").first() }
val receiver = parameterNames.getOrNull(data.typeParameterIndex) ?: return
val arguments = parameterNames.filter { it != receiver }
val adapterLambda = KtPsiFactory(expression).createLambdaExpression(
parameterNames.joinToString(),
"$receiver.${expression.text}(${arguments.joinToString()})"
)
expression.replaced(adapterLambda).let {
MoveLambdaOutsideParenthesesIntention.moveFunctionLiteralOutsideParenthesesIfPossible(it)
}
}
}
class LambdaInfo(element: KtExpression) : AbstractProcessableUsageInfo<KtExpression, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val expression = element ?: return
val context = expression.analyze(BodyResolveMode.PARTIAL)
val psiFactory = KtPsiFactory(expression)
if (expression is KtLambdaExpression || (expression !is KtSimpleNameExpression && expression !is KtCallableReferenceExpression)) {
expression.forEachDescendantOfType<KtThisExpression> {
if (it.getLabelName() != null) return@forEachDescendantOfType
val descriptor = context[BindingContext.REFERENCE_TARGET, it.instanceReference] ?: return@forEachDescendantOfType
it.replace(psiFactory.createExpression(explicateReceiverOf(descriptor)))
}
}
if (expression is KtLambdaExpression) {
expression.valueParameters.getOrNull(data.typeParameterIndex)?.let { parameterToConvert ->
val thisRefExpr = psiFactory.createThisExpression()
for (ref in ReferencesSearch.search(parameterToConvert, LocalSearchScope(expression))) {
(ref.element as? KtSimpleNameExpression)?.replace(thisRefExpr)
}
val lambda = expression.functionLiteral
lambda.valueParameterList!!.removeParameter(parameterToConvert)
if (lambda.valueParameters.isEmpty()) {
lambda.arrow?.delete()
}
}
return
}
val originalLambdaTypes = data.lambdaType
val originalParameterTypes = originalLambdaTypes.arguments.dropLast(1).map { it.type }
val calleeText = when (expression) {
is KtSimpleNameExpression -> expression.text
is KtCallableReferenceExpression -> "(${expression.text})"
else -> generateVariable(expression)
}
val parameterNameValidator = CollectingNameValidator(
if (expression !is KtCallableReferenceExpression) listOf(calleeText) else emptyList()
)
val parameterNamesWithReceiver = originalParameterTypes.mapIndexed { i, type ->
if (i != data.typeParameterIndex) KotlinNameSuggester.suggestNamesByType(type, parameterNameValidator, "p").first() else "this"
}
val parameterNames = parameterNamesWithReceiver.filter { it != "this" }
val body = psiFactory.createExpression(parameterNamesWithReceiver.joinToString(prefix = "$calleeText(", postfix = ")"))
val replacingLambda = psiFactory.buildExpression {
appendFixedText("{ ")
appendFixedText(parameterNames.joinToString())
appendFixedText(" -> ")
appendExpression(body)
appendFixedText(" }")
} as KtLambdaExpression
expression.replaced(replacingLambda).let {
MoveLambdaOutsideParenthesesIntention.moveFunctionLiteralOutsideParenthesesIfPossible(it)
}
}
private fun generateVariable(expression: KtExpression): String {
var baseCallee = ""
KotlinIntroduceVariableHandler.doRefactoring(project, null, expression, false, emptyList()) {
baseCallee = it.name!!
}
return baseCallee
}
}
private inner class Converter(
private val data: ConversionData
) : CallableRefactoring<CallableDescriptor>(data.function.project, data.functionDescriptor, text) {
override fun performRefactoring(descriptorsForChange: Collection<CallableDescriptor>) {
val callables = getAffectedCallables(project, descriptorsForChange)
val conflicts = MultiMap<PsiElement, String>()
val usages = ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>()
project.runSynchronouslyWithProgress("Looking for usages and conflicts...", true) {
runReadAction {
val progressStep = 1.0/callables.size
for ((i, callable) in callables.withIndex()) {
ProgressManager.getInstance().progressIndicator!!.fraction = (i + 1) * progressStep
if (callable !is PsiNamedElement) continue
if (!checkModifiable(callable)) {
val renderedCallable = RefactoringUIUtil.getDescription(callable, true).capitalize()
conflicts.putValue(callable, "Can't modify $renderedCallable")
}
usageLoop@ for (ref in callable.searchReferencesOrMethodReferences()) {
val refElement = ref.element ?: continue
when (ref) {
is KtSimpleReference<*> -> processExternalUsage(conflicts, refElement, usages)
is KtReference -> continue@usageLoop
else -> {
if (data.isFirstParameter) continue@usageLoop
conflicts.putValue(
refElement,
"Can't replace non-Kotlin reference with call expression: " + StringUtil.htmlEmphasize(refElement.text)
)
}
}
}
if (callable is KtFunction) {
usages += FunctionDefinitionInfo(callable)
processInternalUsages(callable, usages)
}
}
}
}
project.checkConflictsInteractively(conflicts) {
project.executeWriteCommand(text) {
val elementsToShorten = ArrayList<KtElement>()
usages.forEach { it.process(data, elementsToShorten) }
ShortenReferences.DEFAULT.process(elementsToShorten)
}
}
}
private fun processExternalUsage(
conflicts: MultiMap<PsiElement, String>,
refElement: PsiElement,
usages: ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>
) {
val callElement = refElement.getParentOfTypeAndBranch<KtCallElement> { calleeExpression }
if (callElement != null) {
val context = callElement.analyze(BodyResolveMode.PARTIAL)
val expressionToProcess = getArgumentExpressionToProcess(callElement, context) ?: return
if (!data.isFirstParameter
&& callElement is KtConstructorDelegationCall
&& expressionToProcess !is KtLambdaExpression
&& expressionToProcess !is KtSimpleNameExpression
&& expressionToProcess !is KtCallableReferenceExpression) {
conflicts.putValue(
expressionToProcess,
"Following expression won't be processed since refactoring can't preserve its semantics: ${expressionToProcess.text}"
)
return
}
if (!checkThisExpressionsAreExplicatable(conflicts, context, expressionToProcess)) return
if (data.isFirstParameter && expressionToProcess !is KtLambdaExpression) return
usages += LambdaInfo(expressionToProcess)
return
}
if (data.isFirstParameter) return
val callableReference = refElement.getParentOfTypeAndBranch<KtCallableReferenceExpression> { callableReference }
if (callableReference != null) {
conflicts.putValue(
refElement,
"Callable reference transformation is not supported: " + StringUtil.htmlEmphasize(callableReference.text)
)
return
}
}
private fun getArgumentExpressionToProcess(callElement: KtCallElement, context: BindingContext): KtExpression? {
return callElement
.getArgumentByParameterIndex(data.functionParameterIndex, context)
.singleOrNull()
?.getArgumentExpression()
?.let { KtPsiUtil.safeDeparenthesize(it) }
}
private fun checkThisExpressionsAreExplicatable(conflicts: MultiMap<PsiElement, String>, context: BindingContext, expressionToProcess: KtExpression): Boolean {
for (thisExpr in expressionToProcess.collectDescendantsOfType<KtThisExpression>()) {
if (thisExpr.getLabelName() != null) continue
val descriptor = context[BindingContext.REFERENCE_TARGET, thisExpr.instanceReference] ?: continue
if (explicateReceiverOf(descriptor) == "this") {
conflicts.putValue(
thisExpr,
"Following expression won't be processed since refactoring can't preserve its semantics: ${thisExpr.text}"
)
return false
}
}
return true
}
private fun processInternalUsages(callable: KtFunction, usages: ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>) {
val body = when (callable) {
is KtConstructor<*> -> callable.containingClassOrObject?.getBody()
else -> callable.bodyExpression
}
if (body != null) {
val functionParameter = callable.valueParameters.getOrNull(data.functionParameterIndex) ?: return
for (ref in ReferencesSearch.search(functionParameter, LocalSearchScope(body))) {
val element = ref.element as? KtSimpleNameExpression ?: continue
val callExpression = element.getParentOfTypeAndBranch<KtCallExpression> { calleeExpression }
if (callExpression != null) {
usages += ParameterCallInfo(callExpression)
}
else if (!data.isFirstParameter) {
usages += InternalReferencePassInfo(element)
}
}
}
}
}
class ConversionData(
val typeParameterIndex: Int,
val functionParameterIndex: Int,
val lambdaType: KotlinType,
val function: KtFunction
) {
val isFirstParameter: Boolean get() = typeParameterIndex == 0
val functionDescriptor by lazy { function.unsafeResolveToDescriptor() as FunctionDescriptor }
}
private fun KtTypeReference.getConversionData(): ConversionData? {
val parameter = parent as? KtParameter ?: return null
val functionType = parameter.getParentOfTypeAndBranch<KtFunctionType> { parameterList } ?: return null
if (functionType.receiverTypeReference != null) return null
val lambdaType = functionType.getAbbreviatedTypeOrType(functionType.analyze(BodyResolveMode.PARTIAL)) ?: return null
val containingParameter = (functionType.parent as? KtTypeReference)?.parent as? KtParameter ?: return null
val ownerFunction = containingParameter.ownerFunction as? KtFunction ?: return null
val typeParameterIndex = functionType.parameters.indexOf(parameter)
val functionParameterIndex = ownerFunction.valueParameters.indexOf(containingParameter)
return ConversionData(typeParameterIndex, functionParameterIndex, lambdaType, ownerFunction)
}
override fun startInWriteAction(): Boolean = false
override fun applicabilityRange(element: KtTypeReference): TextRange? {
val data = element.getConversionData() ?: return null
val elementBefore = data.function.valueParameters[data.functionParameterIndex].typeReference!!.typeElement as KtFunctionType
val elementAfter = elementBefore.copied().apply {
setReceiverTypeReference(element)
parameterList!!.removeParameter(data.typeParameterIndex)
}
text = "Convert '${elementBefore.text}' to '${elementAfter.text}'"
return element.textRange
}
override fun applyTo(element: KtTypeReference, editor: Editor?) {
element.getConversionData()?.let { Converter(it).run() }
}
}
@@ -0,0 +1,222 @@
/*
* 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.
*/
package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.*
import org.jetbrains.kotlin.idea.refactoring.CallableRefactoring
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.getAffectedCallables
import org.jetbrains.kotlin.idea.references.KtSimpleReference
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.search.usagesSearch.searchReferencesOrMethodReferences
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.psi.typeRefHelpers.setReceiverTypeReference
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.getAbbreviatedTypeOrType
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getArgumentByParameterIndex
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
class ConvertFunctionTypeReceiverToParameterIntention : SelfTargetingRangeIntention<KtTypeReference>(
KtTypeReference::class.java,
"Convert function type receiver to parameter"
) {
class ConversionData(
val functionParameterIndex: Int,
val lambdaReceiverType: KotlinType,
val function: KtFunction
) {
val functionDescriptor by lazy { function.unsafeResolveToDescriptor() as FunctionDescriptor }
}
class FunctionDefinitionInfo(element: KtFunction) : AbstractProcessableUsageInfo<KtFunction, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val function = element ?: return
val functionParameter = function.valueParameters.getOrNull(data.functionParameterIndex) ?: return
val functionType = functionParameter.typeReference?.typeElement as? KtFunctionType ?: return
val functionTypeParameterList = functionType.parameterList ?: return
val functionTypeReceiver = functionType.receiverTypeReference ?: return
val parameterToAdd = KtPsiFactory(project).createFunctionTypeParameter(functionTypeReceiver)
functionTypeParameterList.addParameterBefore(parameterToAdd, functionTypeParameterList.parameters.firstOrNull())
functionType.setReceiverTypeReference(null)
}
}
class ParameterCallInfo(element: KtCallExpression) : AbstractProcessableUsageInfo<KtCallExpression, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val callExpression = element ?: return
val qualifiedExpression = callExpression.getQualifiedExpressionForSelector() ?: return
val receiverExpression = qualifiedExpression.receiverExpression
val argumentList = callExpression.getOrCreateValueArgumentList()
argumentList.addArgumentBefore(KtPsiFactory(project).createArgument(receiverExpression), argumentList.arguments.firstOrNull())
qualifiedExpression.replace(callExpression)
}
}
class LambdaInfo(element: KtLambdaExpression) : AbstractProcessableUsageInfo<KtLambdaExpression, ConversionData>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val lambda = element?.functionLiteral ?: return
val context = lambda.analyze(BodyResolveMode.PARTIAL)
val psiFactory = KtPsiFactory(project)
val validator = CollectingNameValidator(
lambda.valueParameters.mapNotNull { it.name },
NewDeclarationNameValidator(lambda.bodyExpression!!, null, NewDeclarationNameValidator.Target.VARIABLES)
)
val newParameterName = KotlinNameSuggester.suggestNamesByType(data.lambdaReceiverType, validator, "p").first()
val newParameterRefExpression = psiFactory.createExpression(newParameterName)
lambda.forEachDescendantOfType<KtThisExpression> {
val thisTarget = context[BindingContext.REFERENCE_TARGET, it.instanceReference] ?: return@forEachDescendantOfType
if (DescriptorToSourceUtilsIde.getAnyDeclaration(project, thisTarget) == lambda) {
it.replace(newParameterRefExpression)
}
}
val lambdaParameterList = lambda.getOrCreateParameterList()
val parameterToAdd = psiFactory.createLambdaParameterList(newParameterName).parameters.first()
lambdaParameterList.addParameterBefore(parameterToAdd, lambdaParameterList.parameters.firstOrNull())
}
}
private inner class Converter(
private val data: ConversionData
) : CallableRefactoring<CallableDescriptor>(data.function.project, data.functionDescriptor, text) {
override fun performRefactoring(descriptorsForChange: Collection<CallableDescriptor>) {
val callables = getAffectedCallables(project, descriptorsForChange)
val conflicts = MultiMap<PsiElement, String>()
val usages = ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>()
project.runSynchronouslyWithProgress("Looking for usages and conflicts...", true) {
runReadAction {
val progressStep = 1.0/callables.size
for ((i, callable) in callables.withIndex()) {
ProgressManager.getInstance().progressIndicator!!.fraction = (i + 1) * progressStep
if (callable !is KtFunction) continue
if (!checkModifiable(callable)) {
val renderedCallable = RefactoringUIUtil.getDescription(callable, true).capitalize()
conflicts.putValue(callable, "Can't modify $renderedCallable")
}
for (ref in callable.searchReferencesOrMethodReferences()) {
if (ref !is KtSimpleReference<*>) continue
processExternalUsage(ref, usages)
}
usages += FunctionDefinitionInfo(callable)
processInternalUsages(callable, usages)
}
}
}
project.checkConflictsInteractively(conflicts) {
project.executeWriteCommand(text) {
val elementsToShorten = ArrayList<KtElement>()
usages.forEach { it.process(data, elementsToShorten) }
ShortenReferences.DEFAULT.process(elementsToShorten)
}
}
}
private fun processExternalUsage(ref: KtSimpleReference<*>, usages: java.util.ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>) {
val callElement = ref.element.getParentOfTypeAndBranch<KtCallElement> { calleeExpression } ?: return
val context = callElement.analyze(BodyResolveMode.PARTIAL)
val expressionToProcess = callElement
.getArgumentByParameterIndex(data.functionParameterIndex, context)
.singleOrNull()
?.getArgumentExpression()
?.let { KtPsiUtil.safeDeparenthesize(it) }
?: return
if (expressionToProcess is KtLambdaExpression) {
usages += LambdaInfo(expressionToProcess)
}
}
private fun processInternalUsages(callable: KtFunction, usages: java.util.ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>) {
val body = when (callable) {
is KtConstructor<*> -> callable.containingClassOrObject?.getBody()
else -> callable.bodyExpression
}
if (body != null) {
val functionParameter = callable.valueParameters.getOrNull(data.functionParameterIndex) ?: return
for (ref in ReferencesSearch.search(functionParameter, LocalSearchScope(body))) {
val element = ref.element as? KtSimpleNameExpression ?: continue
val callExpression = element.getParentOfTypeAndBranch<KtCallExpression> { calleeExpression } ?: continue
usages += ParameterCallInfo(callExpression)
}
}
}
}
private fun KtTypeReference.getConversionData(): ConversionData? {
val functionTypeReceiver = parent as? KtFunctionTypeReceiver ?: return null
val functionType = functionTypeReceiver.parent as? KtFunctionType ?: return null
val lambdaReceiverType = functionType
.getAbbreviatedTypeOrType(functionType.analyze(BodyResolveMode.PARTIAL))
?.getReceiverTypeFromFunctionType()
?: return null
val containingParameter = (functionType.parent as? KtTypeReference)?.parent as? KtParameter ?: return null
val ownerFunction = containingParameter.ownerFunction as? KtFunction ?: return null
val functionParameterIndex = ownerFunction.valueParameters.indexOf(containingParameter)
return ConversionData(functionParameterIndex, lambdaReceiverType, ownerFunction)
}
override fun startInWriteAction(): Boolean = false
override fun applicabilityRange(element: KtTypeReference): TextRange? {
val data = element.getConversionData() ?: return null
val elementBefore = data.function.valueParameters[data.functionParameterIndex].typeReference!!.typeElement as KtFunctionType
val elementAfter = elementBefore.copied().apply {
parameterList!!.addParameterBefore(
KtPsiFactory(element).createFunctionTypeParameter(element),
parameterList!!.parameters.firstOrNull()
)
setReceiverTypeReference(null)
}
text = "Convert '${elementBefore.text}' to '${elementAfter.text}'"
return element.textRange
}
override fun applyTo(element: KtTypeReference, editor: Editor?) {
element.getConversionData()?.let { Converter(it).run() }
}
}
@@ -0,0 +1,201 @@
/*
* Copyright 2010-2015 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.
*/
package org.jetbrains.kotlin.idea.intentions
import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.*
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.refactoring.*
import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.siblings
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.utils.findFunction
import java.util.*
class ConvertPropertyToFunctionIntention : SelfTargetingIntention<KtProperty>(KtProperty::class.java, "Convert property to function"), LowPriorityAction {
private inner class Converter(
project: Project,
descriptor: CallableDescriptor
): CallableRefactoring<CallableDescriptor>(project, descriptor, text) {
private val newName: String = JvmAbi.getterName(callableDescriptor.name.asString())
private fun convertProperty(originalProperty: KtProperty, psiFactory: KtPsiFactory) {
val property = originalProperty.copy() as KtProperty
val getter = property.getter
val sampleFunction = psiFactory.createFunction("fun foo() {\n\n}")
property.valOrVarKeyword.replace(sampleFunction.funKeyword!!)
property.addAfter(psiFactory.createParameterList("()"), property.nameIdentifier)
if (property.initializer == null) {
if (getter != null) {
val dropGetterTo = (getter.equalsToken ?: getter.bodyExpression)
?.siblings(forward = false, withItself = false)
?.firstOrNull { it !is PsiWhiteSpace }
getter.deleteChildRange(getter.firstChild, dropGetterTo)
val dropPropertyFrom = getter
.siblings(forward = false, withItself = false)
.first { it !is PsiWhiteSpace }
.nextSibling
property.deleteChildRange(dropPropertyFrom, getter.prevSibling)
}
}
property.setName(newName)
originalProperty.replace(psiFactory.createFunction(property.text))
}
override fun performRefactoring(descriptorsForChange: Collection<CallableDescriptor>) {
val propertyName = callableDescriptor.name.asString()
val nameChanged = propertyName != newName
val getterName = JvmAbi.getterName(callableDescriptor.name.asString())
val conflicts = MultiMap<PsiElement, String>()
val callables = getAffectedCallables(project, descriptorsForChange)
val kotlinRefsToReplaceWithCall = ArrayList<KtSimpleNameExpression>()
val refsToRename = ArrayList<PsiReference>()
val javaRefsToReplaceWithCall = ArrayList<PsiReferenceExpression>()
project.runSynchronouslyWithProgress("Looking for usages and conflicts...", true) {
runReadAction {
val progressStep = 1.0/callables.size
for ((i, callable) in callables.withIndex()) {
ProgressManager.getInstance().progressIndicator!!.fraction = (i + 1) * progressStep
if (callable !is PsiNamedElement) continue
if (!checkModifiable(callable)) {
val renderedCallable = RefactoringUIUtil.getDescription(callable, true).capitalize()
conflicts.putValue(callable, "Can't modify $renderedCallable")
}
if (callable is KtProperty) {
callableDescriptor.getContainingScope()
?.findFunction(callableDescriptor.name, NoLookupLocation.FROM_IDE) { it.valueParameters.isEmpty() }
?.let { DescriptorToSourceUtilsIde.getAnyDeclaration(project, it) }
?.let { reportDeclarationConflict(conflicts, it) { "$it already exists" } }
}
else if (callable is PsiMethod) {
callable.containingClass
?.findMethodsByName(propertyName, true)
// as is necessary here: see KT-10386
?.firstOrNull { it.parameterList.parametersCount == 0 && !callables.contains(it.namedUnwrappedElement as PsiElement?) }
?.let { reportDeclarationConflict(conflicts, it) { "$it already exists" } }
}
val usages = ReferencesSearch.search(callable)
for (usage in usages) {
if (usage is KtReference) {
if (usage is KtSimpleNameReference) {
val expression = usage.expression
if (expression.getCall(expression.analyze(BodyResolveMode.PARTIAL)) != null
&& expression.getStrictParentOfType<KtCallableReferenceExpression>() == null) {
kotlinRefsToReplaceWithCall.add(expression)
}
else if (nameChanged) {
refsToRename.add(usage)
}
}
else {
val refElement = usage.element
conflicts.putValue(
refElement,
"Unrecognized reference will be skipped: " + StringUtil.htmlEmphasize(refElement.text)
)
}
continue
}
val refElement = usage.element
if (refElement.text.endsWith(getterName)) continue
if (usage is PsiJavaReference) {
if (usage.resolve() is PsiField && usage is PsiReferenceExpression) {
javaRefsToReplaceWithCall.add(usage)
}
continue
}
conflicts.putValue(
refElement,
"Can't replace foreign reference with call expression: " + StringUtil.htmlEmphasize(refElement.text)
)
}
}
}
}
project.checkConflictsInteractively(conflicts) {
project.executeWriteCommand(text) {
val kotlinPsiFactory = KtPsiFactory(project)
val javaPsiFactory = PsiElementFactory.SERVICE.getInstance(project)
val newKotlinCallExpr = kotlinPsiFactory.createExpression("$newName()")
kotlinRefsToReplaceWithCall.forEach { it.replace(newKotlinCallExpr) }
refsToRename.forEach { it.handleElementRename(newName) }
javaRefsToReplaceWithCall.forEach {
val getterRef = it.handleElementRename(newName)
getterRef.replace(javaPsiFactory.createExpressionFromText("${getterRef.text}()", null))
}
callables.forEach {
when (it) {
is KtProperty -> convertProperty(it, kotlinPsiFactory)
is PsiMethod -> it.name = newName
}
}
}
}
}
}
override fun startInWriteAction(): Boolean = false
override fun isApplicableTo(element: KtProperty, caretOffset: Int): Boolean {
val identifier = element.nameIdentifier ?: return false
if (!identifier.textRange.containsOffset(caretOffset)) return false
return element.delegate == null && !element.isVar && !element.isLocal && (element.initializer == null || element.getter == null)
}
override fun applyTo(element: KtProperty, editor: Editor?) {
val descriptor = element.resolveToDescriptorIfAny(BodyResolveMode.FULL) as? CallableDescriptor ?: return
Converter(element.project, descriptor).run()
}
}
@@ -0,0 +1,182 @@
/*
* Copyright 2010-2017 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.
*/
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiNamedElement
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.OverridingUtil
import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.substitutions.getTypeSubstitutor
import org.jetbrains.kotlin.util.findCallableMemberBySignature
import org.jetbrains.kotlin.utils.DFS
import org.jetbrains.kotlin.utils.ifEmpty
import java.util.ArrayList
import kotlin.collections.HashMap
import kotlin.collections.LinkedHashSet
import kotlin.collections.filter
class ChangeSuspendInHierarchyFix(
element: KtNamedFunction,
private val addModifier: Boolean
) : KotlinQuickFixAction<KtNamedFunction>(element) {
override fun getFamilyName(): String {
return if (addModifier) {
"Add 'suspend' modifier to all functions in hierarchy"
} else {
"Remove 'suspend' modifier from all functions in hierarchy"
}
}
override fun getText() = familyName
override fun startInWriteAction() = false
private fun findAllFunctionToProcess(project: Project): Set<KtNamedFunction> {
val result = LinkedHashSet<KtNamedFunction>()
val progressIndicator = ProgressManager.getInstance().progressIndicator!!
val function = element ?: return emptySet()
val functionDescriptor = function.unsafeResolveToDescriptor() as FunctionDescriptor
val baseFunctionDescriptors = functionDescriptor.findTopMostOverriddables()
baseFunctionDescriptors.forEach { baseFunctionDescriptor ->
val baseClassDescriptor = baseFunctionDescriptor.containingDeclaration as? ClassDescriptor ?: return@forEach
val baseClass = DescriptorToSourceUtilsIde.getAnyDeclaration(project, baseClassDescriptor) ?: return@forEach
val name = (baseClass as? PsiNamedElement)?.name ?: return@forEach
progressIndicator.text = "Looking for class $name inheritors..."
val classes = listOf(baseClass) + HierarchySearchRequest(baseClass, baseClass.useScope).searchInheritors()
classes.mapNotNullTo(result) {
val subClass = it.unwrapped as? KtClassOrObject ?: return@mapNotNullTo null
val classDescriptor = subClass.unsafeResolveToDescriptor() as ClassDescriptor
val substitutor = getTypeSubstitutor(baseClassDescriptor.defaultType, classDescriptor.defaultType)
?: return@mapNotNullTo null
val signatureInSubClass = baseFunctionDescriptor.substitute(substitutor) as FunctionDescriptor
val subFunctionDescriptor = classDescriptor.findCallableMemberBySignature(signatureInSubClass, true)
?: return@mapNotNullTo null
subFunctionDescriptor.source.getPsi() as? KtNamedFunction
}
}
return result
}
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val functions = project.runSynchronouslyWithProgress("Analyzing class hierarchy...", true) {
runReadAction { findAllFunctionToProcess(project) }
} ?: return
runWriteAction {
functions.forEach {
if (addModifier) {
it.addModifier(KtTokens.SUSPEND_KEYWORD)
}
else {
it.removeModifier(KtTokens.SUSPEND_KEYWORD)
}
}
}
}
companion object : KotlinIntentionActionsFactory() {
fun FunctionDescriptor.findTopMostOverriddables(): List<FunctionDescriptor> {
val overridablesCache = HashMap<FunctionDescriptor, List<FunctionDescriptor>>()
fun FunctionDescriptor.getOverridables(): List<FunctionDescriptor> {
return overridablesCache.getOrPut(this) {
val classDescriptor = containingDeclaration as? ClassDescriptorWithResolutionScopes ?: return emptyList()
DescriptorUtils.getSuperclassDescriptors(classDescriptor).flatMap { superClassDescriptor ->
if (superClassDescriptor !is ClassDescriptorWithResolutionScopes) return@flatMap emptyList<FunctionDescriptor>()
val candidates = superClassDescriptor.unsubstitutedMemberScope.getContributedFunctions(name, NoLookupLocation.FROM_IDE)
val substitutor = getTypeSubstitutor(superClassDescriptor.defaultType, classDescriptor.defaultType)
?: return@flatMap emptyList<FunctionDescriptor>()
candidates.filter {
val signature = it.substitute(substitutor) as FunctionDescriptor
classDescriptor.findCallableMemberBySignature(signature, true) == this
}
}
}
}
return DFS.dfs(
listOf(this),
{ it?.getOverridables() ?: emptyList() },
object : DFS.CollectingNodeHandler<FunctionDescriptor, FunctionDescriptor, ArrayList<FunctionDescriptor>>(ArrayList()) {
override fun afterChildren(current: FunctionDescriptor) {
if (current.getOverridables().isEmpty()) {
result.add(current)
}
}
})
}
private fun Collection<DeclarationDescriptor>.getOverridables(
currentDescriptor: FunctionDescriptor
): List<DeclarationDescriptor> {
val currentClassDescriptor = currentDescriptor.containingDeclaration as? ClassDescriptor ?: return emptyList()
return filter {
if (it !is FunctionDescriptor || it == currentDescriptor) return@filter false
if (it.isSuspend == currentDescriptor.isSuspend) return@filter false
val containingClassDescriptor = it.containingDeclaration as? ClassDescriptor ?: return@filter false
if (!currentClassDescriptor.isSubclassOf(containingClassDescriptor)) return@filter false
val substitutor = getTypeSubstitutor(
containingClassDescriptor.defaultType,
currentClassDescriptor.defaultType
) ?: return@filter false
val signatureInCurrentClass = it.substitute(substitutor) ?: return@filter false
OverridingUtil.DEFAULT.isOverridableBy(signatureInCurrentClass, currentDescriptor, null).result ==
OverridingUtil.OverrideCompatibilityInfo.Result.CONFLICT
}
}
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
val currentFunction = diagnostic.psiElement as? KtNamedFunction ?: return emptyList()
val currentDescriptor = currentFunction.unsafeResolveToDescriptor() as FunctionDescriptor
Errors.CONFLICTING_OVERLOADS.cast(diagnostic).a.getOverridables(currentDescriptor).ifEmpty { return emptyList() }
return listOf(
ChangeSuspendInHierarchyFix(currentFunction, true),
ChangeSuspendInHierarchyFix(currentFunction, false)
)
}
}
}
@@ -0,0 +1,97 @@
/*
* Copyright 2010-2017 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.
*/
package org.jetbrains.kotlin.idea.refactoring.move.moveClassesOrPackages
import com.intellij.openapi.progress.ProgressManager
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiPackage
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.MoveDestination
import com.intellij.usageView.UsageInfo
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.idea.refactoring.move.createMoveUsageInfoIfPossible
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.KotlinDirectoryMoveTarget
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.analyzeConflictsInFile
import org.jetbrains.kotlin.idea.search.projectScope
import org.jetbrains.kotlin.idea.stubindex.KotlinExactPackagesIndex
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
class KotlinAwareDelegatingMoveDestination(
private val delegate: MoveDestination,
private val targetPackage: PsiPackage?,
private val targetDirectory: PsiDirectory?
) : MoveDestination by delegate {
override fun analyzeModuleConflicts(
elements: MutableCollection<PsiElement>,
conflicts: MultiMap<PsiElement, String>,
usages: Array<out UsageInfo>
) {
delegate.analyzeModuleConflicts(elements, conflicts, usages)
if (targetPackage == null || targetDirectory == null) return
val project = targetDirectory.project
val moveTarget = KotlinDirectoryMoveTarget(FqName(targetPackage.qualifiedName), targetDirectory)
val packagesIndex = KotlinExactPackagesIndex.getInstance()
val directoriesToMove = elements.flatMapTo(LinkedHashSet<PsiDirectory>()) {
(it as? PsiPackage)?.directories?.toList() ?: emptyList()
}
val projectScope = project.projectScope()
val filesToProcess = elements.flatMapTo(LinkedHashSet<KtFile>()) {
if (it is PsiPackage) packagesIndex[it.qualifiedName, project, projectScope] else emptyList()
}
val extraElementsForReferenceSearch = LinkedHashSet<PsiElement>()
val extraElementCollector = object : PsiRecursiveElementWalkingVisitor() {
override fun visitElement(element: PsiElement) {
if (element is KtNamedDeclaration && element.hasModifier(KtTokens.INTERNAL_KEYWORD)) {
element.parentsWithSelf.lastOrNull { it is KtNamedDeclaration }?.let { extraElementsForReferenceSearch += it }
stopWalking()
}
super.visitElement(element)
}
}
filesToProcess.flatMap {it.declarations}.forEach { it.accept(extraElementCollector) }
val progressIndicator = ProgressManager.getInstance().progressIndicator!!
progressIndicator.pushState()
val extraUsages = ArrayList<UsageInfo>()
try {
progressIndicator.text = "Looking for Usages"
for ((index, element) in extraElementsForReferenceSearch.withIndex()) {
progressIndicator.fraction = (index + 1)/extraElementsForReferenceSearch.size.toDouble()
ReferencesSearch.search(element, projectScope).mapNotNullTo(extraUsages) { ref ->
createMoveUsageInfoIfPossible(ref, element, true, false)
}
}
}
finally {
progressIndicator.popState()
}
filesToProcess.forEach {
analyzeConflictsInFile(it, extraUsages, moveTarget, directoriesToMove, conflicts) {}
}
}
}