DeprecatedSymbolUsageFix: support for optional parameters (not complete)

This commit is contained in:
Valentin Kipyatkov
2015-05-21 08:44:31 +03:00
parent 766ca2b905
commit f58ce760ca
26 changed files with 627 additions and 99 deletions
@@ -16,13 +16,11 @@
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.intention.HighPriorityAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiRecursiveElementVisitor
import org.jetbrains.kotlin.analyzer.analyzeInContext
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
@@ -30,6 +28,7 @@ import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.core.OptionalParametersHelper
import org.jetbrains.kotlin.idea.core.asExpression
import org.jetbrains.kotlin.idea.core.refactoring.JetNameSuggester
import org.jetbrains.kotlin.idea.core.refactoring.JetNameValidator
@@ -39,17 +38,18 @@ import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.renderName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.collectElementsOfType
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.psi.psiUtil.replaced
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
@@ -122,7 +122,6 @@ public abstract class DeprecatedSymbolUsageFixBase(
resolvedCall: ResolvedCall<out CallableDescriptor>,
replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression
): JetExpression {
var (expression, imports, parameterUsages) = replacement
val project = element.getProject()
val psiFactory = JetPsiFactory(project)
val descriptor = resolvedCall.getResultingDescriptor()
@@ -131,87 +130,100 @@ public abstract class DeprecatedSymbolUsageFixBase(
val qualifiedExpression = callExpression.getParent() as? JetQualifiedExpression
val expressionToReplace = qualifiedExpression ?: callExpression
var receiver = element.getReceiverExpression()
receiver?.putCopyableUserData(USER_CODE_KEY, Unit)
var receiver = element.getReceiverExpression()?.marked(USER_CODE_KEY)
var receiverType = if (receiver != null) bindingContext.getType(receiver) else null
if (receiver == null) {
val receiverValue = if (descriptor.isExtension) resolvedCall.getExtensionReceiver() else resolvedCall.getDispatchReceiver()
val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, expressionToReplace]
if (receiverValue is ThisReceiver && resolutionScope != null) {
receiver = receiverValue.asExpression(resolutionScope, psiFactory)
receiverType = receiverValue.getType()
}
}
receiver?.putCopyableUserData(FROM_THIS_KEY, Unit)
receiver?.mark(RECEIVER_VALUE_KEY)
for ((parameter, usages) in replacement.parameterUsages.entrySet()) {
usages.forEach { it.put(PARAMETER_USAGE_KEY, parameter) }
}
var expression = replacement.expression
//TODO: this@
for (thisExpression in expression.collectElementsOfType<JetThisExpression>()) {
for (thisExpression in expression.collectDescendantsOfType<JetThisExpression>()) {
if (receiver != null) {
thisExpression.replace(receiver)
}
else {
thisExpression.putCopyableUserData(FROM_THIS_KEY, Unit)
thisExpression.mark(RECEIVER_VALUE_KEY)
}
}
fun argumentForParameter(parameter: ValueParameterDescriptor): JetExpression? {
//TODO: optional parameters
val arguments = resolvedCall.getValueArguments()[parameter] ?: return null //TODO: what if not? vararg?
return arguments.getArguments().firstOrNull()?.getArgumentExpression() //TODO: what if multiple?
}
@data class IntroduceValueForParameter(
val parameter: ValueParameterDescriptor,
val value: JetExpression,
val valueType: JetType?)
val introduceValuesForParameters = ArrayList<Pair<ValueParameterDescriptor, JetExpression>>()
val introduceValuesForParameters = ArrayList<IntroduceValueForParameter>()
for (parameter in descriptor.getValueParameters()) {
val argument = argumentForParameter(parameter) ?: continue
argument.putCopyableUserData(FROM_PARAMETER_KEY, parameter)
argument.putCopyableUserData(USER_CODE_KEY, Unit)
// process parameters in reverse order because default values can use previous parameters
for (parameter in descriptor.getValueParameters().reverse()) {
val argument = argumentForParameter(parameter, resolvedCall, bindingContext, project) ?: continue
val usages = parameterUsages[parameter.getOriginal()]!!
usages.forEach { it.replace(argument) }
argument.expression.put(PARAMETER_VALUE_KEY, parameter)
if (argument.shouldKeepValue(usages.size())) {
introduceValuesForParameters.add(parameter to argument)
val originalParameter = parameter.getOriginal()
val usages = expression.collectDescendantsOfType<JetExpression> { it[PARAMETER_USAGE_KEY] == originalParameter }
usages.forEach { it.replace(argument.wrapped) }
if (argument.expression.shouldKeepValue(usages.size())) {
introduceValuesForParameters.add(IntroduceValueForParameter(parameter, argument.expression, argument.expressionType))
}
}
unwrapDefaultValues(expression)
if (qualifiedExpression is JetSafeQualifiedExpression) {
expression = expression.wrapExpressionForSafeCall(expressionToReplace, receiver!!, bindingContext)
expression = expression.wrapExpressionForSafeCall(expressionToReplace, receiver!!, receiverType, bindingContext)
}
else if (callExpression is JetBinaryExpression && callExpression.getOperationToken() == JetTokens.IDENTIFIER) {
expression = expression.keepInfixFormIfPossible()
}
if (receiver != null) {
val thisReplaced = expression.collectElementsOfType<JetExpression> { it.getCopyableUserData(FROM_THIS_KEY) != null }
val thisReplaced = expression.collectDescendantsOfType<JetExpression> { it[RECEIVER_VALUE_KEY] }
if (receiver.shouldKeepValue(thisReplaced.size())) {
expression = expression.introduceValue(receiver, expressionToReplace, bindingContext, thisReplaced)
expression = expression.introduceValue(receiver, receiverType, expressionToReplace, bindingContext, thisReplaced)
}
}
for ((parameter, value) in introduceValuesForParameters) {
val usagesReplaced = expression.collectElementsOfType<JetExpression> { it.getCopyableUserData(FROM_PARAMETER_KEY) == parameter }
expression = expression.introduceValue(value, expressionToReplace, bindingContext, usagesReplaced, nameSuggestion = parameter.getName().asString())
for ((parameter, value, valueType) in introduceValuesForParameters) {
val usagesReplaced = expression.collectDescendantsOfType<JetExpression> { it[PARAMETER_VALUE_KEY] == parameter }
expression = expression.introduceValue(value, valueType, expressionToReplace, bindingContext, usagesReplaced, nameSuggestion = parameter.getName().asString())
}
var result = expressionToReplace.replaced(expression)
var result = expressionToReplace.replace(expression) as JetExpression
//TODO: drop import of old function (if not needed anymore)?
val file = result.getContainingJetFile()
for (importFqName in imports) {
for (importFqName in replacement.imports) {
val descriptors = file.getResolutionFacade().resolveImportReference(file, importFqName)
val descriptorToImport = descriptors.firstOrNull() ?: continue
ImportInsertHelper.getInstance(project).importDescriptor(file, descriptorToImport)
}
//TODO: do this earlier
dropArgumentsForDefaultValues(result)
val shortenFilter = { element: PsiElement ->
if (element.getCopyableUserData(USER_CODE_KEY) != null) {
if (element[USER_CODE_KEY]) {
ShortenReferences.FilterResult.SKIP
}
else {
val thisReceiver = (element as? JetQualifiedExpression)?.getReceiverExpression() as? JetThisExpression
if (thisReceiver != null && thisReceiver.getCopyableUserData(USER_CODE_KEY) != null) // don't remove explicit 'this' coming from user's code
if (thisReceiver != null && thisReceiver[USER_CODE_KEY]) // don't remove explicit 'this' coming from user's code
ShortenReferences.FilterResult.GO_INSIDE
else
ShortenReferences.FilterResult.PROCESS
@@ -220,13 +232,13 @@ public abstract class DeprecatedSymbolUsageFixBase(
result = ShortenReferences({ ShortenReferences.Options(removeThis = true) }).process(result, shortenFilter) as JetExpression
// clean up user data
result.accept(object : PsiRecursiveElementVisitor() {
override fun visitElement(element: PsiElement) {
element.putCopyableUserData(USER_CODE_KEY, null)
element.putCopyableUserData(FROM_PARAMETER_KEY, null)
element.putCopyableUserData(FROM_THIS_KEY, null)
}
})
result.forEachDescendantOfType<JetExpression> {
it.clear(USER_CODE_KEY)
it.clear(PARAMETER_USAGE_KEY)
it.clear(PARAMETER_VALUE_KEY)
it.clear(RECEIVER_VALUE_KEY)
it.clear(DEFAULT_PARAMETER_VALUE_KEY)
}
return result
}
@@ -234,12 +246,13 @@ public abstract class DeprecatedSymbolUsageFixBase(
private fun JetExpression.wrapExpressionForSafeCall(
expressionToReplace: JetExpression,
receiver: JetExpression,
receiverType: JetType?,
bindingContext: BindingContext
): JetExpression {
val psiFactory = JetPsiFactory(this)
val qualified = this as? JetQualifiedExpression
if (qualified != null) {
if (qualified.getReceiverExpression().getCopyableUserData(FROM_THIS_KEY) != null) {
if (qualified.getReceiverExpression()[RECEIVER_VALUE_KEY]) {
if (qualified is JetSafeQualifiedExpression) return this // already safe
val selector = qualified.getSelectorExpression()
if (selector != null) {
@@ -249,8 +262,8 @@ public abstract class DeprecatedSymbolUsageFixBase(
}
if (expressionToReplace.isUsedAsExpression(bindingContext)) {
val thisReplaced = this.collectElementsOfType<JetExpression> { it.getCopyableUserData(FROM_THIS_KEY) != null }
return this.introduceValue(receiver, expressionToReplace, bindingContext, thisReplaced, safeCall = true)
val thisReplaced = this.collectDescendantsOfType<JetExpression> { it[RECEIVER_VALUE_KEY] }
return this.introduceValue(receiver, receiverType, expressionToReplace, bindingContext, thisReplaced, safeCall = true)
}
else {
return psiFactory.createExpressionByPattern("if ($0 != null) { $1 }", receiver, this)
@@ -260,7 +273,7 @@ public abstract class DeprecatedSymbolUsageFixBase(
private fun JetExpression.keepInfixFormIfPossible(): JetExpression {
if (this !is JetDotQualifiedExpression) return this
val receiver = getReceiverExpression()
if (receiver.getCopyableUserData(FROM_THIS_KEY) == null) return this
if (!receiver[RECEIVER_VALUE_KEY]) return this
val call = getSelectorExpression() as? JetCallExpression ?: return this
val nameExpression = call.getCalleeExpression() as? JetSimpleNameExpression ?: return this
val argument = call.getValueArguments().singleOrNull() ?: return this
@@ -271,6 +284,7 @@ public abstract class DeprecatedSymbolUsageFixBase(
private fun JetExpression.introduceValue(
value: JetExpression,
valueType: JetType?,
insertDeclarationsBefore: JetExpression,
bindingContext: BindingContext,
usages: Collection<JetExpression>,
@@ -281,7 +295,7 @@ public abstract class DeprecatedSymbolUsageFixBase(
val psiFactory = JetPsiFactory(this)
fun nameInCode(name: String) = IdeDescriptorRenderers.SOURCE_CODE.renderName(Name.identifier(name))
fun nameInCode(name: String) = Name.identifier(name).renderName()
fun replaceUsages(name: String) {
val nameInCode = psiFactory.createExpression(nameInCode(name))
@@ -306,7 +320,6 @@ public abstract class DeprecatedSymbolUsageFixBase(
val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, insertDeclarationsBefore]
if (usages.isNotEmpty()) {
val valueType = bindingContext.getType(value)
var explicitType: JetType? = null
if (valueType != null && !ErrorUtils.containsErrorType(valueType)) {
val valueTypeWithoutExpectedType = value.analyzeInContext(
@@ -358,7 +371,7 @@ public abstract class DeprecatedSymbolUsageFixBase(
}
private fun collectNameUsages(scope: JetExpression, name: String)
= scope.collectElementsOfType<JetSimpleNameExpression> { it.getReceiverExpression() == null && it.getReferencedName() == name }
= scope.collectDescendantsOfType<JetSimpleNameExpression> { it.getReceiverExpression() == null && it.getReferencedName() == name }
private fun JetExpression?.shouldKeepValue(usageCount: Int): Boolean {
if (usageCount == 1) return false
@@ -381,8 +394,108 @@ public abstract class DeprecatedSymbolUsageFixBase(
}
}
private class Argument(
val expression: JetExpression,
val wrapped: JetExpression,
val expressionType: JetType?,
val isDefaultValue: Boolean)
private fun argumentForParameter(
parameter: ValueParameterDescriptor,
resolvedCall: ResolvedCall<out CallableDescriptor>,
bindingContext: BindingContext,
project: Project): Argument? {
//TODO: named parameters - keep named form if makes sense
//TODO: keep functional literal argument form
val resolvedArgument = resolvedCall.getValueArguments()[parameter]!!
when (resolvedArgument) {
is ExpressionValueArgument -> {
val expression = resolvedArgument.getValueArgument()!!.getArgumentExpression()!!
expression.mark(USER_CODE_KEY)
return Argument(expression, expression, bindingContext.getType(expression), isDefaultValue = false)
}
is DefaultValueArgument -> {
val defaultValue = OptionalParametersHelper.defaultParameterValue(parameter, project) ?: return null
val (expression, parameterUsages) = defaultValue
for ((param, usages) in parameterUsages) {
usages.forEach { it.put(PARAMETER_USAGE_KEY, param) }
}
// we temporary wrap default values into parenthesis so that we can safely mark them with DEFAULT_PARAMETER_VALUE_KEY
val wrapped = JetPsiFactory(project).createExpressionByPattern("($0)", expression) as JetParenthesizedExpression
wrapped.mark(DEFAULT_PARAMETER_VALUE_KEY)
// clean up user data in original
expression.forEachDescendantOfType<JetExpression> { it.clear(PARAMETER_USAGE_KEY) }
return Argument(wrapped.getExpression()!!, wrapped, null/*TODO*/, isDefaultValue = true)
}
is VarargValueArgument -> /*TODO*/ return null
else -> error("Unknown argument type: $resolvedArgument")
}
}
private fun dropArgumentsForDefaultValues(result: JetExpression) {
val project = result.getProject()
val newBindingContext = result.analyze()
val argumentsToDrop = ArrayList<ValueArgument>()
// we drop only those arguments that added to the code from some parameter's default
fun canDropArgument(argument: ValueArgument) = argument.getArgumentExpression()!![DEFAULT_PARAMETER_VALUE_KEY]
//TODO: other types of calls
result.forEachDescendantOfType<JetCallExpression> { callExpression ->
val resolvedCall = callExpression.getResolvedCall(newBindingContext) ?: return@forEachDescendantOfType
argumentsToDrop.addAll(OptionalParametersHelper.detectArgumentsToDropForDefaults(resolvedCall, project, ::canDropArgument))
}
for (argument in argumentsToDrop) {
argument as JetValueArgument
val argumentList = argument.getParent() as JetValueArgumentList
argumentList.removeArgument(argument)
}
}
private fun unwrapDefaultValues(expression: JetExpression) {
val values = expression.collectDescendantsOfType<JetParenthesizedExpression> {
it[DEFAULT_PARAMETER_VALUE_KEY] && !it.getParent()[DEFAULT_PARAMETER_VALUE_KEY]
}
fun JetParenthesizedExpression.unwrap(): JetExpression {
if (!this[DEFAULT_PARAMETER_VALUE_KEY]) return this
var inner = getExpression()!!
if (inner is JetParenthesizedExpression) {
inner = inner.unwrap()
}
val result = replace(inner) as JetExpression
result.mark(DEFAULT_PARAMETER_VALUE_KEY)
return result
}
values.forEach { it.unwrap() }
}
//TODO: making functions below private causes VerifyError
fun <T: Any> PsiElement.get(key: Key<T>): T? = getCopyableUserData(key)
fun PsiElement.get(key: Key<Unit>): Boolean = getCopyableUserData(key) != null
fun <T: Any> JetExpression.clear(key: Key<T>) = putCopyableUserData(key, null)
fun <T: Any> JetExpression.put(key: Key<T>, value: T) = putCopyableUserData(key, value)
fun JetExpression.mark(key: Key<Unit>) = putCopyableUserData(key, Unit)
fun <T: JetExpression> T.marked(key: Key<Unit>): T {
putCopyableUserData(key, Unit)
return this
}
private val USER_CODE_KEY = Key<Unit>("USER_CODE")
private val FROM_PARAMETER_KEY = Key<ValueParameterDescriptor>("FROM_PARAMETER")
private val FROM_THIS_KEY = Key<Unit>("FROM_THIS")
private val PARAMETER_USAGE_KEY = Key<ValueParameterDescriptor>("PARAMETER_USAGE")
private val PARAMETER_VALUE_KEY = Key<ValueParameterDescriptor>("PARAMETER_VALUE")
private val RECEIVER_VALUE_KEY = Key<Unit>("RECEIVER_VALUE")
private val DEFAULT_PARAMETER_VALUE_KEY = Key<Unit>("DEFAULT_PARAMETER_VALUE")
}
}
}
@@ -26,7 +26,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.quickfix.quickfixUtil.createIntentionFactory
import org.jetbrains.kotlin.idea.quickfix.quickfixUtil.createIntentionForFirstParentOfType
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments
import org.jetbrains.kotlin.resolve.BindingContext
@@ -63,7 +63,7 @@ public class RemoveNameFromFunctionExpressionFix(element: JetNamedFunction) : Je
var wereAutoLabelUsages = false
val name = function.getName() ?: return
function.forEachDescendantsOfType<JetReturnExpression> {
function.forEachDescendantOfType<JetReturnExpression> {
if (!wereAutoLabelUsages && it.getLabelName() == name) {
wereAutoLabelUsages = it.analyze().get(BindingContext.LABEL_TARGET, it.getTargetLabel()) == function
}
@@ -32,7 +32,7 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.collectElementsOfType
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
import org.jetbrains.kotlin.psi.psiUtil.replaced
import org.jetbrains.kotlin.resolve.BindingContext
@@ -144,7 +144,7 @@ object ReplaceWithAnnotationAnalyzer {
}
val parameterUsages = symbolDescriptor.getValueParameters()
.map { parameter -> parameter to expression.collectElementsOfType<JetExpression> { it.getCopyableUserData(parameterUsageKey) == parameter } }
.map { parameter -> parameter to expression.collectDescendantsOfType<JetExpression> { it.getCopyableUserData(parameterUsageKey) == parameter } }
.toMap()
expression.accept(object : PsiRecursiveElementVisitor() {
@@ -28,7 +28,7 @@ import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
@@ -66,7 +66,7 @@ private fun renderClassNameForKClassLiteral(type: JetType): String? {
fun createReplacementTasks(element: JetElement, anyJavaClass: Boolean = false): List<ReplacementTask> {
val replacementTasks = arrayListOf<ReplacementTask>()
element.forEachDescendantsOfType(fun(expression: JetCallExpression) {
element.forEachDescendantOfType(fun(expression: JetCallExpression) {
val context = expression.analyze()
val resolvedCall = expression.getResolvedCall(context) ?: return
@@ -94,7 +94,7 @@ fun createReplacementTasksForAnnotationClass(element: JetClass): List<Replacemen
replacementTasks.add(JavaClassParameterReplacementTask(typeReference, classTypeArgText))
}
element.forEachDescendantsOfType(fun(parameter: JetParameter) {
element.forEachDescendantOfType(fun(parameter: JetParameter) {
val valueParameterDescriptor = parameter.descriptor as? ValueParameterDescriptor ?: return
val type = valueParameterDescriptor.getType()