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
@@ -66,6 +66,7 @@ public class JetValueArgumentList extends JetElementImpl {
}
public void removeArgument(@NotNull JetValueArgument argument) {
assert argument.getParent() == this;
EditCommaSeparatedListHelper.INSTANCE$.removeItem(argument);
}
}
@@ -546,33 +546,54 @@ public fun PsiElement.getElementTextWithContext(): String {
// Calls `block` on each descendant of T type
// Note, that calls happen in order of DFS-exit, so deeper nodes are applied earlier
inline fun <reified T : JetElement> forEachDescendantOfTypeVisitor(
noinline block: (T) -> Unit
): JetVisitorVoid =
object : JetTreeVisitorVoid() {
override fun visitJetElement(element: JetElement) {
super.visitJetElement(element)
if (element is T) {
block(element)
}
public inline fun <reified T : JetElement> forEachDescendantOfTypeVisitor(noinline block: (T) -> Unit): JetVisitorVoid {
return object : JetTreeVisitorVoid() {
override fun visitJetElement(element: JetElement) {
super.visitJetElement(element)
if (element is T) {
block(element)
}
}
}
}
inline fun <reified T : JetElement, R> flatMapDescendantsOfTypeVisitor(
accumulator: MutableCollection<R>,
noinline map: (T) -> Collection<R>
): JetVisitorVoid = forEachDescendantOfTypeVisitor<T> { accumulator.addAll(map(it)) }
public inline fun <reified T : JetElement, R> flatMapDescendantsOfTypeVisitor(accumulator: MutableCollection<R>, noinline map: (T) -> Collection<R>): JetVisitorVoid {
return forEachDescendantOfTypeVisitor<T> { accumulator.addAll(map(it)) }
}
inline fun <reified T : JetElement> PsiElement.forEachDescendantsOfType(noinline block: (T) -> Unit) =
accept(forEachDescendantOfTypeVisitor(block))
inline fun <reified T : JetElement> PsiElement.anyDescendantOfType(noinline predicate: (T) -> Boolean): Boolean {
var result = false
accept(forEachDescendantOfTypeVisitor<T> {
if (!result && predicate(it)) {
result = true
public inline fun <reified T : PsiElement> PsiElement.forEachDescendantOfType(noinline action: (T) -> Unit) {
this.accept(object : PsiRecursiveElementVisitor(){
override fun visitElement(element: PsiElement) {
super.visitElement(element)
if (element is T) {
action(element)
}
}
})
}
public inline fun <reified T : PsiElement> PsiElement.anyDescendantOfType(noinline predicate: (T) -> Boolean): Boolean {
var result = false
this.accept(object : PsiRecursiveElementVisitor(){
override fun visitElement(element: PsiElement) {
if (result) return
if (element is T && predicate(element)) {
result = true
return
}
super.visitElement(element)
}
})
return result
}
public inline fun <reified T : PsiElement> PsiElement.collectDescendantsOfType(noinline predicate: (T) -> Boolean = { true }): Collection<T> {
val result = ArrayList<T>()
forEachDescendantOfType<T> {
if (predicate(it)) {
result.add(it)
}
}
return result
}
@@ -636,21 +657,3 @@ public fun JetExpression.getAnnotationEntries(): List<JetAnnotationEntry> {
else -> emptyList<JetAnnotationEntry>()
}
}
// TODO: it can be default value for parameter but it's not supported yet by the compiler
public inline fun <reified TElement> PsiElement.collectElementsOfType(): Collection<TElement> {
return collectElementsOfType { true }
}
public inline fun <reified TElement> PsiElement.collectElementsOfType(@inlineOptions(InlineOption.ONLY_LOCAL_RETURN) predicate: (TElement) -> Boolean): Collection<TElement> {
val result = ArrayList<TElement>()
this.accept(object : PsiRecursiveElementVisitor(){
override fun visitElement(element: PsiElement) {
if (element is TElement && predicate(element)) {
result.add(element)
}
super.visitElement(element)
}
})
return result
}
@@ -0,0 +1,135 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.core
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.psi.JetExpression
import org.jetbrains.kotlin.psi.JetParameter
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
import org.jetbrains.kotlin.psi.ValueArgument
import org.jetbrains.kotlin.psi.psiUtil.copied
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getParameterForArgument
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import java.util.ArrayList
import java.util.HashMap
public object OptionalParametersHelper {
public fun detectArgumentsToDropForDefaults(
resolvedCall: ResolvedCall<out CallableDescriptor>,
project: Project,
canDrop: (ValueArgument) -> Boolean = { true }
): Collection<ValueArgument> {
if (!resolvedCall.getStatus().isSuccess()) return emptyList()
val descriptor = resolvedCall.getResultingDescriptor()
val parameterToDefaultValue = descriptor.getValueParameters()
.map { parameter -> defaultParameterValue(parameter, project)?.let { parameter to it } }
.filterNotNull()
.toMap()
if (parameterToDefaultValue.isEmpty()) return emptyList()
val arguments = resolvedCall.getCall().getValueArguments()
val argumentsToDrop = ArrayList<ValueArgument>()
for (argument in arguments.reverse()) {
if (!canDrop(argument)) break // TODO: not correct because of named arguments
if (!argument.matchesDefault(resolvedCall, parameterToDefaultValue)) break // TODO: not correct because of named arguments
argumentsToDrop.add(argument)
}
return argumentsToDrop
}
private fun ValueArgument.matchesDefault(resolvedCall: ResolvedCall<out CallableDescriptor>, parameterToDefaultValue: Map<ValueParameterDescriptor, DefaultValue>): Boolean {
val parameter = resolvedCall.getParameterForArgument(this) ?: return false
val defaultValue = parameterToDefaultValue[parameter] ?: return false
val expression = defaultValue.substituteArguments(resolvedCall)
val argumentExpression = getArgumentExpression()!!
return argumentExpression.getText() == expression.getText() //TODO
}
private fun DefaultValue.substituteArguments(resolvedCall: ResolvedCall<out CallableDescriptor>): JetExpression {
if (parameterUsages.isEmpty()) return expression
val key = Key<JetExpression>("SUBSTITUTION")
for ((parameter, usages) in parameterUsages) {
val resolvedArgument = resolvedCall.getValueArguments()[parameter]!!
if (resolvedArgument is ExpressionValueArgument) {
val argument = resolvedArgument.getValueArgument()!!.getArgumentExpression()!!
usages.forEach { it.putCopyableUserData(key, argument) }
}
//TODO: vararg
}
var expressionCopy = expression.copied()
expression.forEachDescendantOfType<JetExpression> { it.putCopyableUserData(key, null) }
val replacements = ArrayList<Pair<JetExpression, JetExpression>>()
expressionCopy.forEachDescendantOfType<JetExpression> {
val replacement = it.getCopyableUserData(key)
if (replacement != null) {
replacements.add(it to replacement)
}
}
for ((expression, replacement) in replacements) {
val replaced = expression.replace(replacement) as JetExpression
if (expression == expressionCopy) {
expressionCopy = replaced
}
}
return expressionCopy
}
public data class DefaultValue(
public val expression: JetExpression,
public val parameterUsages: Map<ValueParameterDescriptor, Collection<JetExpression>>
)
//TODO: handle imports
//TODO: handle implicit receivers
public fun defaultParameterValue(parameter: ValueParameterDescriptor, project: Project): DefaultValue? {
if (!parameter.hasDefaultValue()) return null
//TODO: parameter in overriding method!
val sourceParameter = DescriptorToSourceUtilsIde.getAnyDeclaration(project, parameter) as? JetParameter ?: return null
//TODO: use JetSourceNavigationHelper
val expression = sourceParameter.getDefaultValue() ?: return null
val allParameters = (parameter.getContainingDeclaration() as CallableDescriptor).getValueParameters().toSet()
val parameterUsages = HashMap<ValueParameterDescriptor, MutableCollection<JetExpression>>()
val bindingContext = expression.analyze()
expression.forEachDescendantOfType<JetSimpleNameExpression> {
val target = bindingContext[BindingContext.REFERENCE_TARGET, it]
if (target is ValueParameterDescriptor && target in allParameters) {
parameterUsages.getOrPut(target) { ArrayList() }.add(it)
}
}
return DefaultValue(expression, parameterUsages)
}
}
@@ -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()
@@ -0,0 +1,12 @@
// "Replace with 'newFun(p1, p2.toString(), p3, p4, p5)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p2.toString(), p3, p4, p5)"))
fun oldFun(p1: String, p2: Int = 0, p3: String? = null, p4: String = p1, p5: Int = p2)
fun newFun(p1: String, p2: String, p3: String?, p4: String, p5: Int)
}
fun foo(i: I) {
i.<caret>oldFun("a")
}
@@ -0,0 +1,12 @@
// "Replace with 'newFun(p1, p2.toString(), p3, p4, p5)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p2.toString(), p3, p4, p5)"))
fun oldFun(p1: String, p2: Int = 0, p3: String? = null, p4: String = p1, p5: Int = p2)
fun newFun(p1: String, p2: String, p3: String?, p4: String, p5: Int)
}
fun foo(i: I) {
i.<caret>newFun("a", 0.toString(), null, "a", 0)
}
@@ -0,0 +1,12 @@
// "Replace with 'newFun(p1, p2.toString(), p3)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p2.toString(), p3)"))
fun oldFun(p1: String, p2: Int = 0, p3: String? = null)
fun newFun(p1: String, p2: String, p3: String? = null)
}
fun foo(i: I) {
i.<caret>oldFun("a")
}
@@ -0,0 +1,12 @@
// "Replace with 'newFun(p1, p2.toString(), p3)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p2.toString(), p3)"))
fun oldFun(p1: String, p2: Int = 0, p3: String? = null)
fun newFun(p1: String, p2: String, p3: String? = null)
}
fun foo(i: I) {
i.<caret>newFun("a", 0.toString())
}
@@ -0,0 +1,12 @@
// "Replace with 'newFun(p1, p2.toString(), p3)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p2.toString(), p3)"))
fun oldFun(p1: String, p2: Int, p3: String?)
fun newFun(p1: String, p2: String = "", p3: String? = null)
}
fun foo(i: I) {
i.<caret>oldFun("a", 0, null)
}
@@ -0,0 +1,12 @@
// "Replace with 'newFun(p1, p2.toString(), p3)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p2.toString(), p3)"))
fun oldFun(p1: String, p2: Int, p3: String?)
fun newFun(p1: String, p2: String = "", p3: String? = null)
}
fun foo(i: I) {
i.<caret>newFun("a", 0.toString(), null)
}
@@ -0,0 +1,12 @@
// "Replace with 'newFun(p1, p2, p3)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p2, p3)"))
fun oldFun(p1: String, p2: Int = p1.length(), p3: String? = p1)
fun newFun(x: String, y: Int = x.length(), z: String? = "a")
}
fun foo(i: I) {
i.<caret>oldFun("a")
}
@@ -0,0 +1,12 @@
// "Replace with 'newFun(p1, p2, p3)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p2, p3)"))
fun oldFun(p1: String, p2: Int = p1.length(), p3: String? = p1)
fun newFun(x: String, y: Int = x.length(), z: String? = "a")
}
fun foo(i: I) {
i.<caret>newFun("a")
}
@@ -0,0 +1,14 @@
// "Replace with 'newFun(p1, p2)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p2)"))
fun oldFun(p1: String, p2: String = p1)
fun newFun(p1: String, p2: String)
}
fun foo(i: I) {
i.<caret>oldFun(bar())
}
fun bar(): String = ""
@@ -0,0 +1,15 @@
// "Replace with 'newFun(p1, p2)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p2)"))
fun oldFun(p1: String, p2: String = p1)
fun newFun(p1: String, p2: String)
}
fun foo(i: I) {
val p1 = bar()
i.<caret>newFun(p1, p1)
}
fun bar(): String = ""
@@ -0,0 +1,14 @@
// "Replace with 'newFun(p1, p2)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p2)"))
fun oldFun(p1: String, p2: String = p1)
fun newFun(p1: String, p2: String = p2)
}
fun foo(i: I) {
i.<caret>newFun(bar())
}
fun bar(): String = ""
@@ -0,0 +1,14 @@
// "Replace with 'newFun(p1, p2)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p2)"))
fun oldFun(p1: String, p2: String = p1)
fun newFun(p1: String, p2: String = p2)
}
fun foo(i: I) {
i.<caret>oldFun(bar())
}
fun bar(): String = ""
@@ -0,0 +1,14 @@
// "Replace with 'newFun(p1, p2, p3)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p2, p3)"))
fun oldFun(p1: String, p2: String = bar(), p3: String = p2)
fun newFun(p1: String, p2: String, p3: String)
}
fun foo(i: I) {
i.<caret>oldFun("")
}
fun bar(): String = ""
@@ -0,0 +1,15 @@
// "Replace with 'newFun(p1, p2, p3)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p2, p3)"))
fun oldFun(p1: String, p2: String = bar(), p3: String = p2)
fun newFun(p1: String, p2: String, p3: String)
}
fun foo(i: I) {
val p2 = bar()
i.<caret>newFun("", p2, p2)
}
fun bar(): String = ""
@@ -0,0 +1,14 @@
// "Replace with 'newFun(p1, p2, p3)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p2, p3)"))
fun oldFun(p1: String, p2: String = bar(), p3: String = p2)
fun newFun(p1: String, p2: String, p3: String = p2)
}
fun foo(i: I) {
i.<caret>newFun("", bar())
}
fun bar(): String = ""
@@ -0,0 +1,14 @@
// "Replace with 'newFun(p1, p2, p3)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p2, p3)"))
fun oldFun(p1: String, p2: String = bar(), p3: String = p2)
fun newFun(p1: String, p2: String, p3: String = p2)
}
fun foo(i: I) {
i.<caret>oldFun("")
}
fun bar(): String = ""
@@ -0,0 +1,12 @@
// "Replace with 'newFun(p1, p3, p2)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p3, p2)"))
fun oldFun(p1: String, p2: String = "", p3: Int = -1)
fun newFun(p1: String, p2: Int = -1, p3: String = "")
}
fun foo(i: I) {
i.<caret>oldFun("")
}
@@ -0,0 +1,12 @@
// "Replace with 'newFun(p1, p3, p2)'" "true"
interface I {
@deprecated("", ReplaceWith("newFun(p1, p3, p2)"))
fun oldFun(p1: String, p2: String = "", p3: Int = -1)
fun newFun(p1: String, p2: Int = -1, p3: String = "")
}
fun foo(i: I) {
i.<caret>newFun("")
}
@@ -3124,6 +3124,48 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
doTest(fileName);
}
@TestMetadata("optionalParameters1.kt")
public void testOptionalParameters1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters1.kt");
doTest(fileName);
}
@TestMetadata("optionalParameters2.kt")
public void testOptionalParameters2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters2.kt");
doTest(fileName);
}
@TestMetadata("optionalParameters3.kt")
public void testOptionalParameters3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters3.kt");
doTest(fileName);
}
@TestMetadata("optionalParameters4.kt")
public void testOptionalParameters4() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters4.kt");
doTest(fileName);
}
@TestMetadata("optionalParameters5.kt")
public void testOptionalParameters5() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters5.kt");
doTest(fileName);
}
@TestMetadata("optionalParameters7.kt")
public void testOptionalParameters7() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters7.kt");
doTest(fileName);
}
@TestMetadata("optionalParameters9.kt")
public void testOptionalParameters9() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/optionalParameters9.kt");
doTest(fileName);
}
@TestMetadata("parameters.kt")
public void testParameters() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/parameters.kt");