Extract Function: Add 'suspend' modifier if extracted function contains suspend-calls
#KT-14704 Fixed
This commit is contained in:
+5
-4
@@ -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) {
|
||||
|
||||
+18
-2
@@ -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
|
||||
|
||||
+2
-1
@@ -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 {
|
||||
|
||||
+5
-1
@@ -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()
|
||||
}
|
||||
+6
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user