Make "Add name(s) to call arguments" available for generic arguments

#KT-15550 Fixed
This commit is contained in:
Mikhail Glukhikh
2018-10-24 16:09:19 +03:00
parent e93a90bc09
commit 6e10235208
22 changed files with 93 additions and 37 deletions
@@ -20,6 +20,8 @@ import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.conversion.copy.end
import org.jetbrains.kotlin.idea.conversion.copy.start
@@ -27,11 +29,14 @@ import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatchStatus
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatchStatus.ARGUMENT_HAS_NO_TYPE
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatchStatus.SUCCESS
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument
class AddNameToArgumentIntention
: SelfTargetingIntention<KtValueArgument>(KtValueArgument::class.java, "Add name to argument"), LowPriorityAction {
class AddNameToArgumentIntention : SelfTargetingIntention<KtValueArgument>(
KtValueArgument::class.java, "Add name to argument"
), LowPriorityAction {
override fun isApplicableTo(element: KtValueArgument, caretOffset: Int): Boolean {
val expression = element.getArgumentExpression() ?: return false
@@ -47,8 +52,8 @@ class AddNameToArgumentIntention
return true
}
override fun allowCaretInsideElement(element: PsiElement)
= element !is KtValueArgumentList && element !is KtContainerNode && super.allowCaretInsideElement(element)
override fun allowCaretInsideElement(element: PsiElement) =
element !is KtValueArgumentList && element !is KtContainerNode && super.allowCaretInsideElement(element)
override fun applyTo(element: KtValueArgument, editor: Editor?) {
val name = detectNameToAdd(element)!!
@@ -65,20 +70,30 @@ class AddNameToArgumentIntention
val callExpr = argumentList.parent as? KtCallElement ?: return null
val resolvedCall = callExpr.resolveToCall() ?: return null
val argumentMatch = resolvedCall.getArgumentMapping(argument) as? ArgumentMatch ?: return null
if (argumentMatch.status != ArgumentMatchStatus.SUCCESS) return null
if (!resolvedCall.resultingDescriptor.hasStableParameterNames()) return null
if (argumentMatch.valueParameter.varargElementType != null) {
val varargArgument = resolvedCall.valueArguments[argumentMatch.valueParameter] as? VarargValueArgument ?: return null
if (varargArgument.arguments.size != 1) return null
val versionSettings = callExpr.languageVersionSettings
if (versionSettings.supportsFeature(LanguageFeature.ProhibitAssigningSingleElementsToVarargsInNamedForm)) {
if (argument.getSpreadElement() == null) return null
}
}
if (!argumentMatchedAndCouldBeNamedInCall(argument, resolvedCall, callExpr.languageVersionSettings)) return null
return argumentMatch.valueParameter.name
return (resolvedCall.getArgumentMapping(argument) as? ArgumentMatch)?.valueParameter?.name
}
companion object {
fun argumentMatchedAndCouldBeNamedInCall(
argument: ValueArgument,
resolvedCall: ResolvedCall<out CallableDescriptor>,
versionSettings: LanguageVersionSettings
): Boolean {
val argumentMatch = resolvedCall.getArgumentMapping(argument) as? ArgumentMatch ?: return false
if (argumentMatch.status != SUCCESS && argumentMatch.status != ARGUMENT_HAS_NO_TYPE) return false
if (argumentMatch.valueParameter.varargElementType != null) {
val varargArgument = resolvedCall.valueArguments[argumentMatch.valueParameter] as? VarargValueArgument ?: return false
if (varargArgument.arguments.size != 1) return false
if (versionSettings.supportsFeature(LanguageFeature.ProhibitAssigningSingleElementsToVarargsInNamedForm)) {
if (argument.getSpreadElement() == null) return false
}
}
return true
}
}
}
@@ -18,17 +18,14 @@ package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatchStatus
import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument
class AddNamesToCallArgumentsIntention : SelfTargetingRangeIntention<KtCallElement>(
KtCallElement::class.java,
"Add names to call arguments"
KtCallElement::class.java,
"Add names to call arguments"
) {
override fun applicabilityRange(element: KtCallElement): TextRange? {
val arguments = element.valueArguments
@@ -37,21 +34,14 @@ class AddNamesToCallArgumentsIntention : SelfTargetingRangeIntention<KtCallEleme
val resolvedCall = element.resolveToCall() ?: return null
if (!resolvedCall.resultingDescriptor.hasStableParameterNames()) return null
for (argument in arguments) {
val argumentMatch = resolvedCall.getArgumentMapping(argument) as? ArgumentMatch ?: return null
if (argumentMatch.status != ArgumentMatchStatus.SUCCESS) return null
if (argumentMatch.valueParameter.varargElementType != null) {
val varargArgument = resolvedCall.valueArguments[argumentMatch.valueParameter] as? VarargValueArgument ?: return null
if (varargArgument.arguments.size != 1) return null
val versionSettings = element.languageVersionSettings
if (versionSettings.supportsFeature(LanguageFeature.ProhibitAssigningSingleElementsToVarargsInNamedForm)) {
if (argument.getSpreadElement() == null) return null
}
if (arguments.all {
AddNameToArgumentIntention.argumentMatchedAndCouldBeNamedInCall(it, resolvedCall, element.languageVersionSettings)
}
) {
return element.calleeExpression?.textRange
}
return element.calleeExpression?.textRange
return null
}
override fun applyTo(element: KtCallElement, editor: Editor?) {
@@ -61,9 +51,11 @@ class AddNamesToCallArgumentsIntention : SelfTargetingRangeIntention<KtCallEleme
if (argument !is KtValueArgument || argument is KtLambdaArgument) continue
val argumentMatch = resolvedCall.getArgumentMapping(argument) as? ArgumentMatch ?: continue
val name = argumentMatch.valueParameter.name
val newArgument = KtPsiFactory(element).createArgument(argument.getArgumentExpression()!!,
name,
argument.getSpreadElement() != null)
val newArgument = KtPsiFactory(element).createArgument(
argument.getArgumentExpression()!!,
name,
argument.getSpreadElement() != null
)
argument.replace(newArgument)
}
}
@@ -0,0 +1,6 @@
fun foo(a: String) {}
inline fun <reified T> generic() = null as T
fun main() {
foo(<caret>generic())
}
@@ -0,0 +1,6 @@
fun foo(a: String) {}
inline fun <reified T> generic() = null as T
fun main() {
foo(a = generic())
}
@@ -0,0 +1,6 @@
fun foo(a: String) {}
inline fun <reified T> generic() = null as T
fun main() {
<caret>foo(generic())
}
@@ -0,0 +1,6 @@
fun foo(a: String) {}
inline fun <reified T> generic() = null as T
fun main() {
foo(a = generic())
}
@@ -2,6 +2,7 @@
// ERROR: Unresolved reference: Some
// ACTION: Create function 'Some'
// ACTION: Rename reference
// ACTION: Add 'ctor =' to argument
package p1
@@ -2,6 +2,7 @@
// ERROR: Unresolved reference: Some
// ACTION: Create function 'Some'
// ACTION: Rename reference
// ACTION: Add 'ctor =' to argument
package p1
@@ -1,6 +1,7 @@
// "Create abstract function 'foo'" "false"
// ACTION: Create function 'foo'
// ACTION: Rename reference
// ACTION: Add 'b =' to argument
// ERROR: Unresolved reference: foo
class A {
fun bar(b: Boolean) {}
@@ -2,6 +2,7 @@
// ACTION: Create extension function 'B.foo'
// ACTION: Create member function 'B.foo'
// ACTION: Rename reference
// ACTION: Add 'b =' to argument
// ERROR: Unresolved reference: foo
abstract class A {
fun bar(b: Boolean) {}
@@ -1,6 +1,7 @@
// "Create extension function 'foo'" "false"
// ACTION: Create function 'foo'
// ACTION: Rename reference
// ACTION: Add 'b =' to argument
// ERROR: Unresolved reference: foo
fun bar(b: Boolean) {
@@ -1,4 +1,5 @@
// "Create member function 'foo'" "false"
// ACTION: Add names to call arguments
// ERROR: Unresolved reference: x
class A<T>(val n: T) {
@@ -1,5 +1,6 @@
// "Create function 'foo'" "false"
// ACTION: Rename reference
// ACTION: Add 'n =' to argument
// ERROR: Unresolved reference: foo
fun bar(n: Int) = "$n"
@@ -1,6 +1,7 @@
// "Create local variable 'foo'" "false"
// ACTION: Rename reference
// ACTION: Create function 'foo'
// ACTION: Add 'f =' to argument
// ERROR: Unresolved reference: foo
fun test(f: (Int) -> Int) {}
@@ -1,6 +1,7 @@
// "Create parameter 'foo'" "false"
// ACTION: Rename reference
// ACTION: Create function 'foo'
// ACTION: Add 'f =' to argument
// ERROR: Unresolved reference: foo
fun test(f: (Int) -> Int) {}
@@ -2,6 +2,7 @@
// ERROR: Unresolved reference: x
// ACTION: Create property 'x'
// ACTION: Rename reference
// ACTION: Add 'n =' to argument
enum class E(n: Int) {
X(<caret>x)
@@ -2,6 +2,7 @@
// ERROR: Unresolved reference: b
// ACTION: Create property 'b'
// ACTION: Rename reference
// ACTION: Add 'a =' to argument
open class A(val a: Int) {
@@ -4,6 +4,7 @@
// ACTION: Create property 'foo'
// ACTION: Create property 'foo' as constructor parameter
// ACTION: Rename reference
// ACTION: Add 'b =' to argument
// ERROR: Unresolved reference: foo
class A {
fun bar(b: Boolean) {}
@@ -3,6 +3,7 @@
// ACTION: Create member property 'B.foo'
// ACTION: Create property 'foo' as constructor parameter
// ACTION: Rename reference
// ACTION: Add 'b =' to argument
// ERROR: Unresolved reference: foo
abstract class A {
fun bar(b: Boolean) {}
@@ -1,6 +1,7 @@
// "Create property 'foo'" "false"
// ACTION: Rename reference
// ACTION: Create function 'foo'
// ACTION: Add 'f =' to argument
// ERROR: Unresolved reference: foo
fun test(f: (Int) -> Int) {}
@@ -1,6 +1,7 @@
// "Create member property 'bar'" "false"
// ACTION: Create extension property 'T.bar'
// ACTION: Rename reference
// ACTION: Add 'n =' to argument
// ERROR: Unresolved reference: bar
fun consume(n: Int) {}
@@ -1151,6 +1151,11 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
runTest("idea/testData/intentions/addNameToArgument/functionLiteralArgument.kt");
}
@TestMetadata("genericCall.kt")
public void testGenericCall() throws Exception {
runTest("idea/testData/intentions/addNameToArgument/genericCall.kt");
}
@TestMetadata("incompleteCall.kt")
public void testIncompleteCall() throws Exception {
runTest("idea/testData/intentions/addNameToArgument/incompleteCall.kt");
@@ -1254,6 +1259,11 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
runTest("idea/testData/intentions/addNamesToCallArguments/ambiguousCall.kt");
}
@TestMetadata("genericCall.kt")
public void testGenericCall() throws Exception {
runTest("idea/testData/intentions/addNamesToCallArguments/genericCall.kt");
}
@TestMetadata("incompleteCall.kt")
public void testIncompleteCall() throws Exception {
runTest("idea/testData/intentions/addNamesToCallArguments/incompleteCall.kt");