Introduce Variable: Add type argument to initializer if they can't be inferred from the context

#KT-5466 Fixed
This commit is contained in:
Alexey Sedunov
2015-12-16 16:28:14 +03:00
parent ea8da18338
commit cfe1c44260
17 changed files with 181 additions and 46 deletions
@@ -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 }
}
}
@@ -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()}")
@@ -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!!)
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
import java.util.ArrayList
fun f() {
val list: List<List<List<Int>>> = ArrayList(<selection>ArrayList(listOf())</selection>)
}
@@ -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)
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
import java.util.ArrayList
fun f() {
val v : List<List<Int>> = ArrayList(<selection>listOf()</selection>)
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
import java.util.ArrayList
fun f() {
val p0 = listOf<List<Int>>()
val v : List<List<Int>> = ArrayList(p0)
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun f() {
val v : List<Int> = <selection>(listOf())</selection>
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun f() {
val listOf = listOf<Int>()
val v : List<Int> = listOf
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun <T> String.ext(): List<T> = listOf()
fun f() {
val v : List<Int> = <selection>"".ext()</selection>
}
@@ -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>
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun f() {
val listOf = listOf<Int>()
val v : List<Int> = listOf
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun f() {
val v : List<Int> = <selection>listOf()</selection>
val u : List<String> = listOf()
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun f() {
val listOf = listOf<Int>()
val v : List<Int> = listOf
val u : List<String> = listOf()
}
@@ -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)