Extract Function: Add 'suspend' modifier if extracted function contains suspend-calls

#KT-14704 Fixed
This commit is contained in:
Alexey Sedunov
2017-01-20 20:36:00 +03:00
parent 41f5a3e864
commit 7382d7c7d6
7 changed files with 60 additions and 8 deletions
@@ -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<TypeParameter>,
val replacementMap: MultiMap<KtSimpleNameExpression, Replacement>,
val controlFlow: ControlFlow,
val returnType: KotlinType
val returnType: KotlinType,
val modifiers: List<KtKeywordToken> = emptyList()
) {
val name: String get() = suggestedNames.firstOrNull() ?: ""
val duplicates: List<DuplicateInfo> 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) {
@@ -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<ErrorMessage>()
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
@@ -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 {
@@ -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<DeclarationDescriptor, MutableParameter>()
for (refInfo in getBrokenReferencesInfo(createTemporaryCodeBlock())) {
for (refInfo in getBrokenReferencesInfo(virtualBlock)) {
val ref = refInfo.refExpr
val selector = (ref.parent as? KtCallExpression) ?: ref
@@ -0,0 +1,10 @@
fun async(f: suspend () -> Unit) {}
suspend fun await() {}
// SIBLING:
fun main(args: Array<String>) {
async {
<selection>await()</selection>
}
}
@@ -0,0 +1,14 @@
fun async(f: suspend () -> Unit) {}
suspend fun await() {}
// SIBLING:
fun main(args: Array<String>) {
async {
__dummyTestFun__()
}
}
private suspend fun __dummyTestFun__() {
await()
}
@@ -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");