From 7382d7c7d61fd8c86e631a325586803d9cd90333 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Fri, 20 Jan 2017 20:36:00 +0300 Subject: [PATCH] Extract Function: Add 'suspend' modifier if extracted function contains suspend-calls #KT-14704 Fixed --- .../ExtractableCodeDescriptor.kt | 9 +++++---- .../extractableAnalysisUtil.kt | 20 +++++++++++++++++-- .../extractionEngine/extractorUtil.kt | 3 ++- .../extractionEngine/inferParameterInfo.kt | 6 +++++- .../extractFunction/basic/suspendCall.kt | 10 ++++++++++ .../basic/suspendCall.kt.after | 14 +++++++++++++ .../introduce/ExtractionTestGenerated.java | 6 ++++++ 7 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 idea/testData/refactoring/extractFunction/basic/suspendCall.kt create mode 100644 idea/testData/refactoring/extractFunction/basic/suspendCall.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt index 599aa6f4bcb..5bd0fb6ab0e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractableCodeDescriptor.kt @@ -33,10 +33,9 @@ import org.jetbrains.kotlin.idea.references.KtSimpleNameReference.ShorteningMode import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.approximateFlexibleTypes -import org.jetbrains.kotlin.idea.util.isAnnotatedNotNull -import org.jetbrains.kotlin.idea.util.isAnnotatedNullable import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiRange import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.lexer.KtKeywordToken import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElement @@ -360,7 +359,8 @@ data class ExtractableCodeDescriptor( val typeParameters: List, val replacementMap: MultiMap, val controlFlow: ControlFlow, - val returnType: KotlinType + val returnType: KotlinType, + val modifiers: List = emptyList() ) { val name: String get() = suggestedNames.firstOrNull() ?: "" val duplicates: List by lazy { findDuplicates() } @@ -396,7 +396,8 @@ fun ExtractableCodeDescriptor.copy( typeParameters, newReplacementMap, controlFlow.copy(oldToNewParameters), - returnType ?: this.returnType) + returnType ?: this.returnType, + modifiers) } enum class ExtractionTarget(val targetName: String) { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt index 31b91ff9dbb..77f9a690d6c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt @@ -39,6 +39,7 @@ import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverseFollowingInstruction import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade @@ -56,6 +57,7 @@ import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.approximateWithResolvableType import org.jetbrains.kotlin.idea.util.getResolutionScope import org.jetbrains.kotlin.idea.util.isResolvableInScope +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.renderer.DescriptorRenderer @@ -63,6 +65,7 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsStatement import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.checker.KotlinTypeChecker @@ -637,12 +640,24 @@ fun ExtractionData.performAnalysis(): AnalysisResult { val modifiedVarDescriptorsWithExpressions = localInstructions.getModifiedVarDescriptors(bindingContext) + val virtualBlock = createTemporaryCodeBlock() + val targetScope = targetSibling.getResolutionScope(bindingContext, commonParent.getResolutionFacade()) - val paramsInfo = inferParametersInfo(commonParent, pseudocode, bindingContext, targetScope, modifiedVarDescriptorsWithExpressions.keys) + val paramsInfo = inferParametersInfo( + virtualBlock, + commonParent, + pseudocode, + bindingContext, + targetScope, + modifiedVarDescriptorsWithExpressions.keys + ) if (paramsInfo.errorMessage != null) { return AnalysisResult(null, Status.CRITICAL_ERROR, listOf(paramsInfo.errorMessage!!)) } + val virtualContext = virtualBlock.analyze(BodyResolveMode.PARTIAL) + val isSuspendExpected = virtualContext.diagnostics.all().any { it.factory == Errors.ILLEGAL_SUSPEND_FUNCTION_CALL } + val messages = ArrayList() val modifiedVarDescriptorsForControlFlow = HashMap(modifiedVarDescriptorsWithExpressions) @@ -699,7 +714,8 @@ fun ExtractionData.performAnalysis(): AnalysisResult { paramsInfo.typeParameters.sortedBy { it.originalDeclaration.name!! }, paramsInfo.replacementMap, if (messages.isEmpty()) controlFlow else controlFlow.toDefault(), - returnType + returnType, + if (isSuspendExpected) listOf(KtTokens.SUSPEND_KEYWORD) else emptyList() ), if (messages.isEmpty()) Status.SUCCESS else Status.NON_CRITICAL_ERROR, messages diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt index 23a915e057e..36bebd76d32 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt @@ -65,7 +65,8 @@ private fun buildSignature(config: ExtractionGeneratorConfiguration, renderer: D else -> CallableBuilder.Target.READ_ONLY_PROPERTY } return CallableBuilder(builderTarget).apply { - modifier(config.descriptor.visibility) + val modifiers = listOf(config.descriptor.visibility) + config.descriptor.modifiers.map { it.value } + modifier(modifiers.joinToString(separator = " ")) typeParams( config.descriptor.typeParameters.map { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt index e5a029531f2..29fbb105f14 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt @@ -26,6 +26,8 @@ import org.jetbrains.kotlin.cfg.pseudocode.getExpectedTypePredicate import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.InstructionWithReceivers import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.core.KotlinNameSuggester import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator @@ -45,6 +47,7 @@ import org.jetbrains.kotlin.resolve.calls.tasks.isSynthesizedInvoke import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver @@ -66,6 +69,7 @@ internal class ParametersInfo { } internal fun ExtractionData.inferParametersInfo( + virtualBlock: KtBlockExpression, commonParent: PsiElement, pseudocode: Pseudocode, bindingContext: BindingContext, @@ -76,7 +80,7 @@ internal fun ExtractionData.inferParametersInfo( val extractedDescriptorToParameter = HashMap() - for (refInfo in getBrokenReferencesInfo(createTemporaryCodeBlock())) { + for (refInfo in getBrokenReferencesInfo(virtualBlock)) { val ref = refInfo.refExpr val selector = (ref.parent as? KtCallExpression) ?: ref diff --git a/idea/testData/refactoring/extractFunction/basic/suspendCall.kt b/idea/testData/refactoring/extractFunction/basic/suspendCall.kt new file mode 100644 index 00000000000..71f4f1eb319 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/basic/suspendCall.kt @@ -0,0 +1,10 @@ +fun async(f: suspend () -> Unit) {} + +suspend fun await() {} + +// SIBLING: +fun main(args: Array) { + async { + await() + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/basic/suspendCall.kt.after b/idea/testData/refactoring/extractFunction/basic/suspendCall.kt.after new file mode 100644 index 00000000000..9840a6291c4 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/basic/suspendCall.kt.after @@ -0,0 +1,14 @@ +fun async(f: suspend () -> Unit) {} + +suspend fun await() {} + +// SIBLING: +fun main(args: Array) { + async { + __dummyTestFun__() + } +} + +private suspend fun __dummyTestFun__() { + await() +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java index a8f3d17e5bd..7cefc632fe2 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java @@ -1096,6 +1096,12 @@ public class ExtractionTestGenerated extends AbstractExtractionTest { doExtractFunctionTest(fileName); } + @TestMetadata("suspendCall.kt") + public void testSuspendCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/basic/suspendCall.kt"); + doExtractFunctionTest(fileName); + } + @TestMetadata("topLevelValUnderSmartCast.kt") public void testTopLevelValUnderSmartCast() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/basic/topLevelValUnderSmartCast.kt");