Introduce Variable: Add type argument to initializer if they can't be inferred from the context
#KT-5466 Fixed
This commit is contained in:
@@ -41,7 +41,9 @@ import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.codeInsight.shorten.performDelayedShortening
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.addTypeArgumentsIfNeeded
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.checkConflictsInteractively
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.getQualifiedTypeArgumentList
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.PackageNameInfo
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.lazilyProcessInternalReferencesToUpdateOnPackageNameChange
|
||||
@@ -55,8 +57,6 @@ import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getCallWithAssert
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.ErrorUtils
|
||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
||||
@@ -156,7 +156,7 @@ class KotlinInlineValHandler : InlineActionHandler() {
|
||||
?: return reportAmbiguousAssignment(project, editor, name, assignments)
|
||||
}
|
||||
|
||||
val typeArgumentsForCall = getTypeArgumentsStringForCall(initializer)
|
||||
val typeArgumentsForCall = getQualifiedTypeArgumentList(initializer)
|
||||
val parametersForFunctionLiteral = getParametersForFunctionLiteral(initializer)
|
||||
|
||||
val referencesInOriginalFile = referenceExpressions.filter { it.containingFile == file }
|
||||
@@ -217,7 +217,9 @@ class KotlinInlineValHandler : InlineActionHandler() {
|
||||
declaration.delete()
|
||||
|
||||
if (inlinedExpressions.isNotEmpty()) {
|
||||
typeArgumentsForCall?.let { addTypeArguments(it, inlinedExpressions) }
|
||||
if (typeArgumentsForCall != null) {
|
||||
inlinedExpressions.forEach { addTypeArgumentsIfNeeded(it, typeArgumentsForCall) }
|
||||
}
|
||||
|
||||
parametersForFunctionLiteral?.let { addFunctionLiteralParameterTypes(it, inlinedExpressions) }
|
||||
|
||||
@@ -348,35 +350,4 @@ class KotlinInlineValHandler : InlineActionHandler() {
|
||||
hasCantInferParameter || hasUnresolvedItOrThis
|
||||
}
|
||||
}
|
||||
|
||||
private fun addTypeArguments(typeArguments: String, inlinedExpressions: List<KtExpression>) {
|
||||
val containingFile = inlinedExpressions.first().getContainingKtFile()
|
||||
val callsToAddArguments = inlinedExpressions.mapNotNull {
|
||||
val context = it.analyze(BodyResolveMode.PARTIAL)
|
||||
val call = it.getCallWithAssert(context)
|
||||
val callElement = call.callElement
|
||||
if (callElement is KtCallExpression &&
|
||||
hasIncompleteTypeInferenceDiagnostic(call, context) &&
|
||||
call.typeArgumentList == null) callElement else null
|
||||
}
|
||||
|
||||
val psiFactory = KtPsiFactory(containingFile)
|
||||
for (call in callsToAddArguments) {
|
||||
call.addAfter(psiFactory.createTypeArguments("<$typeArguments>"), call.calleeExpression)
|
||||
ShortenReferences.DEFAULT.process(call.typeArgumentList!!)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTypeArgumentsStringForCall(initializer: KtExpression): String? {
|
||||
val context = initializer.analyze(BodyResolveMode.PARTIAL)
|
||||
val call = initializer.getResolvedCall(context) ?: return null
|
||||
val typeArgumentMap = call.typeArguments
|
||||
val typeArguments = call.candidateDescriptor.typeParameters.mapNotNull { typeArgumentMap[it] }
|
||||
return typeArguments.joinToString { IdeDescriptorRenderers.SOURCE_CODE_FOR_TYPE_ARGUMENTS.renderType(it) }
|
||||
}
|
||||
|
||||
private fun hasIncompleteTypeInferenceDiagnostic(call: Call, context: BindingContext): Boolean {
|
||||
val callee = call.calleeExpression ?: return false
|
||||
return context.diagnostics.forElement(callee).any { it.factory == Errors.TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER }
|
||||
}
|
||||
}
|
||||
|
||||
+29
-7
@@ -42,9 +42,7 @@ import org.jetbrains.kotlin.idea.analysis.computeTypeInfoInContext
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||
import org.jetbrains.kotlin.idea.core.*
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.Pass
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.chooseContainerElementIfNecessary
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.removeTemplateEntryBracesIfPossible
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.*
|
||||
import org.jetbrains.kotlin.idea.intentions.ConvertToBlockBodyIntention
|
||||
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringBundle
|
||||
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtil
|
||||
@@ -411,7 +409,9 @@ object KotlinIntroduceVariableHandler : KotlinIntroduceHandlerBase() {
|
||||
project: Project,
|
||||
editor: Editor,
|
||||
declaration: KtDestructuringDeclaration,
|
||||
suggestedNames: List<Collection<String>>) {
|
||||
suggestedNames: List<Collection<String>>,
|
||||
postProcess: (KtDeclaration) -> Unit
|
||||
) {
|
||||
StartMarkAction.canStart(project)?.let { return }
|
||||
|
||||
val builder = TemplateBuilderImpl(declaration)
|
||||
@@ -441,6 +441,9 @@ object KotlinIntroduceVariableHandler : KotlinIntroduceHandlerBase() {
|
||||
}
|
||||
|
||||
override fun templateFinished(template: Template?, brokenOff: Boolean) {
|
||||
if (!brokenOff) {
|
||||
postProcess(declaration)
|
||||
}
|
||||
finishMarkAction()
|
||||
}
|
||||
|
||||
@@ -518,12 +521,24 @@ object KotlinIntroduceVariableHandler : KotlinIntroduceHandlerBase() {
|
||||
return showErrorHint(project, editor, KotlinRefactoringBundle.message("cannot.refactor.expression.has.unit.type"))
|
||||
}
|
||||
|
||||
val typeArgumentList = getQualifiedTypeArgumentList(KtPsiUtil.safeDeparenthesize(physicalExpression))
|
||||
|
||||
val isInplaceAvailable = editor != null
|
||||
&& editor.settings.isVariableInplaceRenameEnabled
|
||||
&& !ApplicationManager.getApplication().isUnitTestMode
|
||||
|
||||
val allOccurrences = occurrencesToReplace ?: expression.findOccurrences(occurrenceContainer)
|
||||
|
||||
fun postProcess(declaration: KtDeclaration) {
|
||||
if (typeArgumentList == null) return
|
||||
val initializer = when (declaration) {
|
||||
is KtProperty -> declaration.initializer
|
||||
is KtDestructuringDeclaration -> declaration.initializer
|
||||
else -> null
|
||||
} ?: return
|
||||
runWriteAction { addTypeArgumentsIfNeeded(initializer, typeArgumentList) }
|
||||
}
|
||||
|
||||
val callback = Pass<OccurrencesChooser.ReplaceChoice> { replaceChoice ->
|
||||
val allReplaces = when (replaceChoice) {
|
||||
OccurrencesChooser.ReplaceChoice.ALL -> allOccurrences
|
||||
@@ -545,6 +560,7 @@ object KotlinIntroduceVariableHandler : KotlinIntroduceHandlerBase() {
|
||||
calculateAnchor(commonParent, commonContainer, allReplaces),
|
||||
NewDeclarationNameValidator.Target.VARIABLES
|
||||
)
|
||||
|
||||
val suggestedNames = if (componentFunctions.isNotEmpty()) {
|
||||
val collectingValidator = CollectingNameValidator(filter = validator)
|
||||
componentFunctions.map { suggestNamesForComponent(it, project, collectingValidator) }
|
||||
@@ -556,10 +572,12 @@ object KotlinIntroduceVariableHandler : KotlinIntroduceHandlerBase() {
|
||||
validator,
|
||||
"value").singletonList()
|
||||
}
|
||||
|
||||
val introduceVariableContext = IntroduceVariableContext(
|
||||
expression, suggestedNames, allReplaces, commonContainer, commonParent,
|
||||
replaceOccurrence, noTypeInference, expressionType, componentFunctions, bindingContext, resolutionFacade
|
||||
)
|
||||
|
||||
project.executeCommand(INTRODUCE_VARIABLE, null) {
|
||||
runWriteAction { introduceVariableContext.runRefactoring() }
|
||||
|
||||
@@ -573,7 +591,10 @@ object KotlinIntroduceVariableHandler : KotlinIntroduceHandlerBase() {
|
||||
editor.caretModel.moveToOffset(property.textOffset)
|
||||
editor.selectionModel.removeSelection()
|
||||
|
||||
if (!isInplaceAvailable) return@executeCommand
|
||||
if (!isInplaceAvailable) {
|
||||
postProcess(property)
|
||||
return@executeCommand
|
||||
}
|
||||
|
||||
PsiDocumentManager.getInstance(project).commitDocument(editor.document)
|
||||
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.document)
|
||||
@@ -590,12 +611,13 @@ object KotlinIntroduceVariableHandler : KotlinIntroduceHandlerBase() {
|
||||
expressionType,
|
||||
noTypeInference,
|
||||
project,
|
||||
editor
|
||||
editor,
|
||||
::postProcess
|
||||
).startInplaceIntroduceTemplate()
|
||||
}
|
||||
|
||||
is KtDestructuringDeclaration -> {
|
||||
executeMultiDeclarationTemplate(project, editor, property, suggestedNames)
|
||||
executeMultiDeclarationTemplate(project, editor, property, suggestedNames, ::postProcess)
|
||||
}
|
||||
|
||||
else -> throw AssertionError("Unexpected declaration: ${property.getElementTextWithContext()}")
|
||||
|
||||
+4
-4
@@ -33,9 +33,7 @@ import org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractKotlinInplaceIntroducer
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import java.awt.BorderLayout
|
||||
@@ -50,7 +48,8 @@ public class KotlinVariableInplaceIntroducer(
|
||||
val expressionType: KotlinType?,
|
||||
val noTypeInference: Boolean,
|
||||
project: Project,
|
||||
editor: Editor
|
||||
editor: Editor,
|
||||
private val postProcess: (KtDeclaration) -> Unit
|
||||
): AbstractKotlinInplaceIntroducer<KtProperty>(
|
||||
addedVariable,
|
||||
originalExpression,
|
||||
@@ -155,5 +154,6 @@ public class KotlinVariableInplaceIntroducer(
|
||||
it.replace(replacement)
|
||||
}
|
||||
}
|
||||
postProcess(addedVariable)
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,7 @@ import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getJavaMemberDescriptor
|
||||
@@ -71,7 +72,9 @@ import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
||||
import org.jetbrains.kotlin.idea.core.getPackage
|
||||
import org.jetbrains.kotlin.idea.intentions.RemoveCurlyBracesFromTemplateIntention
|
||||
import org.jetbrains.kotlin.idea.j2k.IdeaJavaToKotlinServices
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.util.string.collapseSpaces
|
||||
import org.jetbrains.kotlin.j2k.ConverterSettings
|
||||
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
|
||||
@@ -83,6 +86,9 @@ import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.resolve.AnalyzingUtils
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getCallWithAssert
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import java.io.File
|
||||
import java.lang.annotation.Retention
|
||||
import java.util.*
|
||||
@@ -769,4 +775,27 @@ fun dropOverrideKeywordIfNecessary(element: KtNamedDeclaration) {
|
||||
if (callableDescriptor.overriddenDescriptors.isEmpty()) {
|
||||
element.removeModifier(KtTokens.OVERRIDE_KEYWORD)
|
||||
}
|
||||
}
|
||||
|
||||
fun getQualifiedTypeArgumentList(initializer: KtExpression): KtTypeArgumentList? {
|
||||
val context = initializer.analyze(BodyResolveMode.PARTIAL)
|
||||
val call = initializer.getResolvedCall(context) ?: return null
|
||||
val typeArgumentMap = call.typeArguments
|
||||
val typeArguments = call.candidateDescriptor.typeParameters.mapNotNull { typeArgumentMap[it] }
|
||||
val renderedList = typeArguments.joinToString(prefix = "<", postfix = ">") {
|
||||
IdeDescriptorRenderers.SOURCE_CODE_FOR_TYPE_ARGUMENTS.renderType(it)
|
||||
}
|
||||
return KtPsiFactory(initializer).createTypeArguments(renderedList)
|
||||
}
|
||||
|
||||
fun addTypeArgumentsIfNeeded(expression: KtExpression, typeArgumentList: KtTypeArgumentList) {
|
||||
val context = expression.analyze(BodyResolveMode.PARTIAL)
|
||||
val call = expression.getCallWithAssert(context)
|
||||
val callElement = call.callElement as? KtCallExpression ?: return
|
||||
if (call.typeArgumentList != null) return
|
||||
val callee = call.calleeExpression ?: return
|
||||
if (context.diagnostics.forElement(callee).all { it.factory != Errors.TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER }) return
|
||||
|
||||
callElement.addAfter(typeArgumentList, callElement.calleeExpression)
|
||||
ShortenReferences.DEFAULT.process(callElement.typeArgumentList!!)
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
import java.util.ArrayList
|
||||
|
||||
fun f() {
|
||||
val list: List<List<List<Int>>> = ArrayList(<selection>ArrayList(listOf())</selection>)
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
import java.util.ArrayList
|
||||
|
||||
fun f() {
|
||||
val p0 = ArrayList<List<List<Int>>>(listOf())
|
||||
val list: List<List<List<Int>>> = ArrayList(p0)
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
import java.util.ArrayList
|
||||
|
||||
fun f() {
|
||||
val v : List<List<Int>> = ArrayList(<selection>listOf()</selection>)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
import java.util.ArrayList
|
||||
|
||||
fun f() {
|
||||
val p0 = listOf<List<Int>>()
|
||||
val v : List<List<Int>> = ArrayList(p0)
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// WITH_RUNTIME
|
||||
fun f() {
|
||||
val v : List<Int> = <selection>(listOf())</selection>
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
fun f() {
|
||||
val listOf = listOf<Int>()
|
||||
val v : List<Int> = listOf
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
fun <T> String.ext(): List<T> = listOf()
|
||||
|
||||
fun f() {
|
||||
val v : List<Int> = <selection>"".ext()</selection>
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
fun <T> String.ext(): List<T> = listOf()
|
||||
|
||||
fun f() {
|
||||
val ext = "".ext<Int>()
|
||||
val v : List<Int> = ext
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// WITH_RUNTIME
|
||||
fun f() {
|
||||
val v : List<Int> = <selection>listOf()</selection>
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
fun f() {
|
||||
val listOf = listOf<Int>()
|
||||
val v : List<Int> = listOf
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
fun f() {
|
||||
val v : List<Int> = <selection>listOf()</selection>
|
||||
val u : List<String> = listOf()
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
fun f() {
|
||||
val listOf = listOf<Int>()
|
||||
val v : List<Int> = listOf
|
||||
val u : List<String> = listOf()
|
||||
}
|
||||
+45
@@ -457,6 +457,51 @@ public class ExtractionTestGenerated extends AbstractExtractionTest {
|
||||
doIntroduceVariableTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/refactoring/introduceVariable/explicateTypeArguments")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ExplicateTypeArguments extends AbstractExtractionTest {
|
||||
public void testAllFilesPresentInExplicateTypeArguments() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/introduceVariable/explicateTypeArguments"), Pattern.compile("^(.+)\\.(kt|kts)$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("DeeperNestedCall.kt")
|
||||
public void testDeeperNestedCall() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/explicateTypeArguments/DeeperNestedCall.kt");
|
||||
doIntroduceVariableTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("NestedCall.kt")
|
||||
public void testNestedCall() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/explicateTypeArguments/NestedCall.kt");
|
||||
doIntroduceVariableTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("Parenthesized.kt")
|
||||
public void testParenthesized() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/explicateTypeArguments/Parenthesized.kt");
|
||||
doIntroduceVariableTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("Qualified.kt")
|
||||
public void testQualified() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/explicateTypeArguments/Qualified.kt");
|
||||
doIntroduceVariableTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("Simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/explicateTypeArguments/Simple.kt");
|
||||
doIntroduceVariableTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("UnmatchedOccurrences.kt")
|
||||
public void testUnmatchedOccurrences() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/explicateTypeArguments/UnmatchedOccurrences.kt");
|
||||
doIntroduceVariableTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/refactoring/introduceVariable/extractToScope")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user