From f291873cf8385fbe514cc1dc2a92d8016f6e517c Mon Sep 17 00:00:00 2001 From: Nicolay Mitropolsky Date: Mon, 11 Dec 2017 11:28:57 +0300 Subject: [PATCH] 181: compilation fix when access `progressIndicator` in `ProgressManager` by adding `!!` --- .../evaluate/KotlinCodeFragmentFactory.kt.181 | 434 ++++++++++++++++++ .../idea/actions/JavaToKotlinAction.kt.181 | 216 +++++++++ .../SearchNotPropertyCandidatesAction.kt.181 | 164 +++++++ ...ionTypeParameterToReceiverIntention.kt.181 | 356 ++++++++++++++ ...ionTypeReceiverToParameterIntention.kt.181 | 222 +++++++++ .../ConvertPropertyToFunctionIntention.kt.181 | 201 ++++++++ .../ChangeSuspendInHierarchyFix.kt.181 | 182 ++++++++ ...otlinAwareDelegatingMoveDestination.kt.181 | 97 ++++ 8 files changed, 1872 insertions(+) create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt.181 create mode 100644 idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt.181 create mode 100644 idea/src/org/jetbrains/kotlin/idea/actions/internal/SearchNotPropertyCandidatesAction.kt.181 create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/ConvertFunctionTypeParameterToReceiverIntention.kt.181 create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/ConvertFunctionTypeReceiverToParameterIntention.kt.181 create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/ConvertPropertyToFunctionIntention.kt.181 create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeSuspendInHierarchyFix.kt.181 create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveClassesOrPackages/KotlinAwareDelegatingMoveDestination.kt.181 diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt.181 b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt.181 new file mode 100644 index 00000000000..81dd87c3f28 --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt.181 @@ -0,0 +1,434 @@ +/* + * 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.debugger.evaluate + +import com.intellij.debugger.DebuggerManagerEx +import com.intellij.debugger.engine.evaluation.CodeFragmentFactory +import com.intellij.debugger.engine.evaluation.CodeFragmentKind +import com.intellij.debugger.engine.evaluation.TextWithImports +import com.intellij.debugger.engine.events.DebuggerCommandImpl +import com.intellij.debugger.impl.DebuggerContextImpl +import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl +import com.intellij.ide.highlighter.JavaFileType +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Key +import com.intellij.psi.* +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.PsiTypesUtil +import com.intellij.util.IncorrectOperationException +import com.intellij.util.concurrency.Semaphore +import com.intellij.xdebugger.XDebuggerManager +import com.intellij.xdebugger.impl.XDebugSessionImpl +import com.intellij.xdebugger.impl.ui.tree.ValueMarkup +import com.sun.jdi.* +import org.jetbrains.annotations.TestOnly +import org.jetbrains.eval4j.jdi.asValue +import org.jetbrains.kotlin.asJava.classes.KtLightClass +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils +import org.jetbrains.kotlin.idea.debugger.KotlinEditorTextProvider +import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor +import org.jetbrains.kotlin.idea.refactoring.j2k +import org.jetbrains.kotlin.idea.refactoring.j2kText +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand +import org.jetbrains.kotlin.idea.versions.getKotlinJvmRuntimeMarkerClass +import org.jetbrains.kotlin.j2k.AfterConversionPass +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext +import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.makeNullable +import java.util.concurrent.atomic.AtomicReference + +class KotlinCodeFragmentFactory : CodeFragmentFactory() { + override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment { + val contextElement = getWrappedContextElement(project, context) + if (contextElement == null) { + LOG.warn("CodeFragment with null context created:\noriginalContext = ${context?.getElementTextWithContext()}") + } + val codeFragment = if (item.kind == CodeFragmentKind.EXPRESSION) { + KtExpressionCodeFragment( + project, + "fragment.kt", + item.text, + initImports(item.imports), + contextElement + ) + } else { + KtBlockCodeFragment( + project, + "fragment.kt", + item.text, + initImports(item.imports), + contextElement + ) + } + + codeFragment.putCopyableUserData(KtCodeFragment.RUNTIME_TYPE_EVALUATOR, { expression: KtExpression -> + val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context + val debuggerSession = debuggerContext.debuggerSession + if (debuggerSession == null || debuggerContext.suspendContext == null) { + null + } else { + val semaphore = Semaphore() + semaphore.down() + val nameRef = AtomicReference() + val worker = object : KotlinRuntimeTypeEvaluator( + null, expression, debuggerContext, ProgressManager.getInstance().progressIndicator!! + ) { + override fun typeCalculationFinished(type: KotlinType?) { + nameRef.set(type) + semaphore.up() + } + } + + debuggerContext.debugProcess?.managerThread?.invoke(worker) + + for (i in 0..50) { + ProgressManager.checkCanceled() + if (semaphore.waitFor(20)) break + } + + nameRef.get() + } + }) + + if (contextElement != null && contextElement !is KtElement) { + codeFragment.putCopyableUserData(KtCodeFragment.FAKE_CONTEXT_FOR_JAVA_FILE, { + val emptyFile = createFakeFileWithJavaContextElement("", contextElement) + + val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context + val debuggerSession = debuggerContext.debuggerSession + if ((debuggerSession == null || debuggerContext.suspendContext == null) && !ApplicationManager.getApplication().isUnitTestMode) { + LOG.warn("Couldn't create fake context element for java file, debugger isn't paused on breakpoint") + return@putCopyableUserData emptyFile + } + + val frameDescriptor = getFrameInfo(contextElement, debuggerContext) + if (frameDescriptor == null) { + LOG.warn("Couldn't get info about 'this' and local variables for ${debuggerContext.sourcePosition.file.name}:${debuggerContext.sourcePosition.line}") + return@putCopyableUserData emptyFile + } + + val receiverTypeReference = + frameDescriptor.thisObject?.let { createKotlinProperty(project, "this_0", it.type().name(), it) }?.typeReference + val receiverTypeText = receiverTypeReference?.let { "${it.text}." } ?: "" + + val kotlinVariablesText = + frameDescriptor.visibleVariables.entries.associate { it.key.name() to it.value }.kotlinVariablesAsText(project) + + val fakeFunctionText = "fun ${receiverTypeText}_java_locals_debug_fun_() {\n$kotlinVariablesText\n}" + + val fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement) + val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction + val fakeContext = (fakeFunction?.bodyExpression as? KtBlockExpression)?.statements?.lastOrNull() + + return@putCopyableUserData wrapContextIfNeeded(project, contextElement, fakeContext) ?: emptyFile + }) + } + + return codeFragment + } + + private fun getFrameInfo(contextElement: PsiElement?, debuggerContext: DebuggerContextImpl): FrameInfo? { + val semaphore = Semaphore() + semaphore.down() + + var frameInfo: FrameInfo? = null + + val worker = object : DebuggerCommandImpl() { + override fun action() { + try { + val frame = if (ApplicationManager.getApplication().isUnitTestMode) + contextElement?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.frameProxy?.stackFrame + else + debuggerContext.frameProxy?.stackFrame + + val visibleVariables = frame?.let { + val values = it.getValues(it.visibleVariables()) + values.filterValues { it != null } + } ?: emptyMap() + + frameInfo = FrameInfo(frame?.thisObject(), visibleVariables) + } catch (ignored: AbsentInformationException) { + // Debug info unavailable + } finally { + semaphore.up() + } + } + } + + debuggerContext.debugProcess?.managerThread?.invoke(worker) + + for (i in 0..50) { + if (semaphore.waitFor(20)) break + } + + return frameInfo + } + + private class FrameInfo(val thisObject: Value?, val visibleVariables: Map) + + private fun initImports(imports: String?): String? { + if (imports != null && !imports.isEmpty()) { + return imports.split(KtCodeFragment.IMPORT_SEPARATOR) + .mapNotNull { fixImportIfNeeded(it) } + .joinToString(KtCodeFragment.IMPORT_SEPARATOR) + } + return null + } + + private fun fixImportIfNeeded(import: String): String? { + // skip arrays + if (import.endsWith("[]")) { + return fixImportIfNeeded(import.removeSuffix("[]").trim()) + } + + // skip primitive types + if (PsiTypesUtil.boxIfPossible(import) != import) { + return null + } + return import + } + + private fun getWrappedContextElement(project: Project, context: PsiElement?): PsiElement? { + val newContext = getContextElement(context) + if (newContext !is KtElement) return newContext + return wrapContextIfNeeded(project, context, newContext) + } + + override fun createPresentationCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment { + val kotlinCodeFragment = createCodeFragment(item, context, project) + if (PsiTreeUtil.hasErrorElements(kotlinCodeFragment) && kotlinCodeFragment is KtExpressionCodeFragment) { + val javaExpression = try { + PsiElementFactory.SERVICE.getInstance(project).createExpressionFromText(item.text, context) + } catch (e: IncorrectOperationException) { + null + } + + val importList = try { + kotlinCodeFragment.importsAsImportList()?.let { + (PsiFileFactory.getInstance(project).createFileFromText( + "dummy.java", JavaFileType.INSTANCE, it.text + ) as? PsiJavaFile)?.importList + } + } catch (e: IncorrectOperationException) { + null + } + + if (javaExpression != null && !PsiTreeUtil.hasErrorElements(javaExpression)) { + var convertedFragment: KtExpressionCodeFragment? = null + project.executeWriteCommand("Convert java expression to kotlin in Evaluate Expression") { + try { + val newText = javaExpression.j2kText() + val newImports = importList?.j2kText() + if (newText != null) { + convertedFragment = KtExpressionCodeFragment( + project, + kotlinCodeFragment.name, + newText, + newImports, + kotlinCodeFragment.context + ) + + AfterConversionPass(project, J2kPostProcessor(formatCode = false)).run(convertedFragment!!, range = null) + } + } catch (e: Throwable) { + // ignored because text can be invalid + LOG.error("Couldn't convert expression:\n`${javaExpression.text}`", e) + } + } + return convertedFragment ?: kotlinCodeFragment + } + } + return kotlinCodeFragment + } + + override fun isContextAccepted(contextElement: PsiElement?): Boolean { + return when { + // PsiCodeBlock -> DummyHolder -> originalElement + contextElement is PsiCodeBlock -> isContextAccepted(contextElement.context?.context) + contextElement == null -> false + contextElement.language == KotlinFileType.INSTANCE.language -> true + contextElement.language == JavaFileType.INSTANCE.language -> { + getKotlinJvmRuntimeMarkerClass(contextElement.project, contextElement.resolveScope) != null + } + else -> false + } + } + + override fun getFileType(): KotlinFileType = KotlinFileType.INSTANCE + + override fun getEvaluatorBuilder() = KotlinEvaluationBuilder + + companion object { + private val LOG = Logger.getInstance(this::class.java) + + val LABEL_VARIABLE_VALUE_KEY: Key = Key.create("_label_variable_value_key_") + + private const val DEBUG_LABEL_SUFFIX: String = "_DebugLabel" + + @TestOnly + val DEBUG_CONTEXT_FOR_TESTS: Key = Key.create("DEBUG_CONTEXT_FOR_TESTS") + + fun getContextElement(elementAt: PsiElement?): PsiElement? { + if (elementAt == null) return null + + if (elementAt is PsiCodeBlock) { + return getContextElement(elementAt.context?.context) + } + + if (elementAt is KtLightClass) { + return getContextElement(elementAt.kotlinOrigin) + } + + val containingFile = elementAt.containingFile + if (containingFile is PsiJavaFile) return elementAt + if (containingFile !is KtFile) return null + + // elementAt can be PsiWhiteSpace when codeFragment is created from line start offset (in case of first opening EE window) + val lineStartOffset = if (elementAt is PsiWhiteSpace || elementAt is PsiComment) { + PsiTreeUtil.skipSiblingsForward(elementAt, PsiWhiteSpace::class.java, PsiComment::class.java)?.textOffset + ?: elementAt.textOffset + } else { + elementAt.textOffset + } + + fun KtElement.takeIfAcceptedAsCodeFragmentContext() = takeIf { KotlinEditorTextProvider.isAcceptedAsCodeFragmentContext(it) } + + PsiTreeUtil.findElementOfClassAtOffset(containingFile, lineStartOffset, KtExpression::class.java, false) + ?.takeIfAcceptedAsCodeFragmentContext() + ?.let { return CodeInsightUtils.getTopmostElementAtOffset(it, lineStartOffset, KtExpression::class.java) } + + KotlinEditorTextProvider.findExpressionInner(elementAt, true) + ?.takeIfAcceptedAsCodeFragmentContext() + ?.let { return it } + + return containingFile + } + + //internal for tests + fun createCodeFragmentForLabeledObjects(project: Project, markupMap: Map<*, ValueMarkup>): Pair> { + @Suppress("UNCHECKED_CAST") + val variables = markupMap.entries.associate { + val (value, markup) = it + "${markup.text}$DEBUG_LABEL_SUFFIX" to value as? Value + }.filterValues { it != null } as Map + + return variables.kotlinVariablesAsText(project) to variables + } + + private fun Map.kotlinVariablesAsText(project: Project): String { + val sb = StringBuilder() + + val psiNameHelper = PsiNameHelper.getInstance(project) + for ((variableName, variableValue) in entries) { + if (!psiNameHelper.isIdentifier(variableName)) continue + + val variableTypeName = variableValue.type()?.name() ?: continue + + val kotlinProperty = createKotlinProperty(project, variableName, variableTypeName, variableValue) ?: continue + + sb.append("${kotlinProperty.text}\n") + } + + sb.append("val _debug_context_val = 1\n") + + return sb.toString() + } + + private fun createKotlinProperty(project: Project, variableName: String, variableTypeName: String, value: Value): KtProperty? { + val actualClassDescriptor = value.asValue().asmType.getClassDescriptor(GlobalSearchScope.allScope(project)) + if (actualClassDescriptor != null && actualClassDescriptor.defaultType.arguments.isEmpty()) { + val renderedType = IdeDescriptorRenderers.SOURCE_CODE.renderType(actualClassDescriptor.defaultType.makeNullable()) + return KtPsiFactory(project).createProperty(variableName.quoteIfNeeded(), renderedType, false) + } + + fun String.addArraySuffix() = if (value is ArrayReference) this + "[]" else this + + val className = variableTypeName.replace("$", ".").substringBefore("[]") + val classType = PsiType.getTypeByName(className, project, GlobalSearchScope.allScope(project)) + val type = (if (value !is PrimitiveValue && classType.resolve() == null) + CommonClassNames.JAVA_LANG_OBJECT + else + className).addArraySuffix() + + val field = PsiElementFactory.SERVICE.getInstance(project) + .createField(variableName, PsiType.getTypeByName(type, project, GlobalSearchScope.allScope(project))) + val ktField = field.j2k() as? KtProperty + ktField?.modifierList?.delete() + return ktField + } + } + + private fun wrapContextIfNeeded(project: Project, originalContext: PsiElement?, newContext: KtElement?): KtElement? { + val markupMap: Map<*, ValueMarkup>? = + if (ApplicationManager.getApplication().isUnitTestMode) + NodeDescriptorImpl.getMarkupMap(originalContext?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.debugProcess) + else + (XDebuggerManager.getInstance(project).currentSession as? XDebugSessionImpl)?.valueMarkers?.allMarkers + + if (markupMap == null || markupMap.isEmpty()) return newContext + + val (text, labels) = createCodeFragmentForLabeledObjects(project, markupMap) + if (text.isEmpty()) return newContext + + return createWrappingContext(text, labels, newContext, project) + } + + private fun createFakeFileWithJavaContextElement(funWithLocalVariables: String, javaContext: PsiElement): KtFile { + val javaFile = javaContext.containingFile as? PsiJavaFile + + val sb = StringBuilder() + + javaFile?.packageName?.takeUnless { it.isBlank() }?.let { + sb.append("package ").append(it.quoteIfNeeded()).append("\n") + } + + javaFile?.importList?.let { sb.append(it.text).append("\n") } + + sb.append(funWithLocalVariables) + + return KtPsiFactory(javaContext.project).createAnalyzableFile("fakeFileForJavaContextInDebugger.kt", sb.toString(), javaContext) + } + + // internal for test + private fun createWrappingContext( + newFragmentText: String, + labels: Map, + originalContext: KtElement?, + project: Project + ): KtElement? { + val codeFragment = KtPsiFactory(project).createBlockCodeFragment(newFragmentText, originalContext) + + codeFragment.accept(object : KtTreeVisitorVoid() { + override fun visitProperty(property: KtProperty) { + val reference = labels[property.name] + if (reference != null) { + property.putUserData(LABEL_VARIABLE_VALUE_KEY, reference) + } + } + }) + + return codeFragment.getContentElement().statements.lastOrNull() + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt.181 b/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt.181 new file mode 100644 index 00000000000..690f8e1a0e9 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt.181 @@ -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, convertedTexts: List): List { + val result = ArrayList() + 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, project: Project, enableExternalCodeProcessing: Boolean = true): List { + 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): 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 { + 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, project: Project): Sequence { + val manager = PsiManager.getInstance(project) + return allFiles(filesOrDirs) + .asSequence() + .mapNotNull { manager.findFile(it) as? PsiJavaFile } + } + + private fun allFiles(filesOrDirs: Array): Collection { + val result = ArrayList() + for (file in filesOrDirs) { + VfsUtilCore.visitChildrenRecursively(file, object : VirtualFileVisitor() { + override fun visitFile(file: VirtualFile): Boolean { + result.add(file) + return true + } + }) + } + return result + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/internal/SearchNotPropertyCandidatesAction.kt.181 b/idea/src/org/jetbrains/kotlin/idea/actions/internal/SearchNotPropertyCandidatesAction.kt.181 new file mode 100644 index 00000000000..998844a3b11 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/actions/internal/SearchNotPropertyCandidatesAction.kt.181 @@ -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() + var pFunctions = 0 + val matchedDescriptors = mutableSetOf() + + 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() + 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() + + + var i = 0 + resultDescriptors.forEach { desc -> + progress("Step 2: ${i++} of ${resultDescriptors.size}", "$desc") + val source = DescriptorToSourceUtilsIde.getAllDeclarations(project, desc) + .filterIsInstance() + .firstOrNull() ?: return@forEach + val abstract = source.modifierList.hasModifierProperty(PsiModifier.ABSTRACT) + if (!abstract) { + if (!source.isTrivial()) { + descriptorToPsiBinding[desc] = source + } + } + } + + val nonTrivial = mutableSetOf() + 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().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 + } + } + +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertFunctionTypeParameterToReceiverIntention.kt.181 b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertFunctionTypeParameterToReceiverIntention.kt.181 new file mode 100644 index 00000000000..b1f10a8a30c --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertFunctionTypeParameterToReceiverIntention.kt.181 @@ -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::class.java, + "Convert function type parameter to receiver" +) { + class FunctionDefinitionInfo(element: KtFunction) : AbstractProcessableUsageInfo(element) { + override fun process(data: ConversionData, elementsToShorten: MutableList) { + 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(element) { + override fun process(data: ConversionData, elementsToShorten: MutableList) { + 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(element) { + override fun process(data: ConversionData, elementsToShorten: MutableList) { + 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(element) { + override fun process(data: ConversionData, elementsToShorten: MutableList) { + 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 { + 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(data.function.project, data.functionDescriptor, text) { + override fun performRefactoring(descriptorsForChange: Collection) { + val callables = getAffectedCallables(project, descriptorsForChange) + + val conflicts = MultiMap() + + val usages = ArrayList>() + + 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() + usages.forEach { it.process(data, elementsToShorten) } + ShortenReferences.DEFAULT.process(elementsToShorten) + } + } + } + + private fun processExternalUsage( + conflicts: MultiMap, + refElement: PsiElement, + usages: ArrayList> + ) { + val callElement = refElement.getParentOfTypeAndBranch { 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 { 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, context: BindingContext, expressionToProcess: KtExpression): Boolean { + for (thisExpr in expressionToProcess.collectDescendantsOfType()) { + 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>) { + 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 { 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 { 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() } + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertFunctionTypeReceiverToParameterIntention.kt.181 b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertFunctionTypeReceiverToParameterIntention.kt.181 new file mode 100644 index 00000000000..f9b059be672 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertFunctionTypeReceiverToParameterIntention.kt.181 @@ -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::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(element) { + override fun process(data: ConversionData, elementsToShorten: MutableList) { + 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(element) { + override fun process(data: ConversionData, elementsToShorten: MutableList) { + 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(element) { + override fun process(data: ConversionData, elementsToShorten: MutableList) { + 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 { + 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(data.function.project, data.functionDescriptor, text) { + override fun performRefactoring(descriptorsForChange: Collection) { + val callables = getAffectedCallables(project, descriptorsForChange) + + val conflicts = MultiMap() + + val usages = ArrayList>() + + 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() + usages.forEach { it.process(data, elementsToShorten) } + ShortenReferences.DEFAULT.process(elementsToShorten) + } + } + } + + private fun processExternalUsage(ref: KtSimpleReference<*>, usages: java.util.ArrayList>) { + val callElement = ref.element.getParentOfTypeAndBranch { 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>) { + 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 { 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() } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertPropertyToFunctionIntention.kt.181 b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertPropertyToFunctionIntention.kt.181 new file mode 100644 index 00000000000..93d464dc37a --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertPropertyToFunctionIntention.kt.181 @@ -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::class.java, "Convert property to function"), LowPriorityAction { + private inner class Converter( + project: Project, + descriptor: CallableDescriptor + ): CallableRefactoring(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) { + val propertyName = callableDescriptor.name.asString() + val nameChanged = propertyName != newName + val getterName = JvmAbi.getterName(callableDescriptor.name.asString()) + val conflicts = MultiMap() + val callables = getAffectedCallables(project, descriptorsForChange) + val kotlinRefsToReplaceWithCall = ArrayList() + val refsToRename = ArrayList() + val javaRefsToReplaceWithCall = ArrayList() + + 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() == 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() + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeSuspendInHierarchyFix.kt.181 b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeSuspendInHierarchyFix.kt.181 new file mode 100644 index 00000000000..8e39006566e --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeSuspendInHierarchyFix.kt.181 @@ -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(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 { + val result = LinkedHashSet() + + 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 { + val overridablesCache = HashMap>() + + fun FunctionDescriptor.getOverridables(): List { + return overridablesCache.getOrPut(this) { + val classDescriptor = containingDeclaration as? ClassDescriptorWithResolutionScopes ?: return emptyList() + DescriptorUtils.getSuperclassDescriptors(classDescriptor).flatMap { superClassDescriptor -> + if (superClassDescriptor !is ClassDescriptorWithResolutionScopes) return@flatMap emptyList() + val candidates = superClassDescriptor.unsubstitutedMemberScope.getContributedFunctions(name, NoLookupLocation.FROM_IDE) + val substitutor = getTypeSubstitutor(superClassDescriptor.defaultType, classDescriptor.defaultType) + ?: return@flatMap emptyList() + 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>(ArrayList()) { + override fun afterChildren(current: FunctionDescriptor) { + if (current.getOverridables().isEmpty()) { + result.add(current) + } + } + }) + } + + private fun Collection.getOverridables( + currentDescriptor: FunctionDescriptor + ): List { + 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 { + 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) + ) + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveClassesOrPackages/KotlinAwareDelegatingMoveDestination.kt.181 b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveClassesOrPackages/KotlinAwareDelegatingMoveDestination.kt.181 new file mode 100644 index 00000000000..e1d388c5a6a --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveClassesOrPackages/KotlinAwareDelegatingMoveDestination.kt.181 @@ -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, + conflicts: MultiMap, + usages: Array + ) { + 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()) { + (it as? PsiPackage)?.directories?.toList() ?: emptyList() + } + val projectScope = project.projectScope() + val filesToProcess = elements.flatMapTo(LinkedHashSet()) { + if (it is PsiPackage) packagesIndex[it.qualifiedName, project, projectScope] else emptyList() + } + + val extraElementsForReferenceSearch = LinkedHashSet() + 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() + 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) {} + } + } +} \ No newline at end of file