Intentions: Convert function type parameter to receiver

#KT-14246 In Progress
This commit is contained in:
Alexey Sedunov
2016-11-29 17:37:39 +03:00
parent 9cadfd01ae
commit d4ed2d2022
43 changed files with 1066 additions and 16 deletions
+1
View File
@@ -337,6 +337,7 @@ These artifacts include extensions for the types available in the latter JDKs, s
- [`KT-14459`](https://youtrack.jetbrains.com/issue/KT-14459) Initialize with Constructor Parameter: Fix IDE freeze on properties in generic class
- [`KT-14044`](https://youtrack.jetbrains.com/issue/KT-14044) Fix exception on deleting unused declaration in IDEA 2016.3
- [`KT-14019`](https://youtrack.jetbrains.com/issue/KT-14019) Create from Usage: Support generation of abstract members for superclasses
- [`KT-14246`](https://youtrack.jetbrains.com/issue/KT-14246) Intentions: Convert function type parameter to receiver
##### New features
@@ -75,9 +75,14 @@ public class KtFunctionType extends KtElementImplStub<KotlinPlaceHolderStub<KtFu
return list != null ? list.getParameters() : Collections.<KtParameter>emptyList();
}
@Nullable
public KtFunctionTypeReceiver getReceiver() {
return getStubOrPsiChild(KtStubElementTypes.FUNCTION_TYPE_RECEIVER);
}
@Nullable
public KtTypeReference getReceiverTypeReference() {
KtFunctionTypeReceiver receiverDeclaration = getStubOrPsiChild(KtStubElementTypes.FUNCTION_TYPE_RECEIVER);
KtFunctionTypeReceiver receiverDeclaration = getReceiver();
if (receiverDeclaration == null) {
return null;
}
@@ -64,6 +64,10 @@ public class KtParameterList extends KtElementImplStub<KotlinPlaceHolderStub<KtP
EditCommaSeparatedListHelper.INSTANCE.removeItem(parameter);
}
public void removeParameter(int index) {
removeParameter(getParameters().get(index));
}
public KtFunction getOwnerFunction() {
PsiElement parent = getParentByStub();
if (!(parent instanceof KtFunction)) return null;
@@ -108,6 +108,10 @@ class KtPsiFactory(private val project: Project) {
return if (typeReference?.text == type) typeReference else null
}
fun createFunctionTypeReceiver(typeReference: KtTypeReference): KtFunctionTypeReceiver {
return (createType("A.() -> B").typeElement as KtFunctionType).receiver!!.apply { this.typeReference.replace(typeReference) }
}
fun createTypeAlias(name: String, typeParameters: List<String>, typeElement: KtTypeElement): KtTypeAlias {
return createTypeAlias(name, typeParameters, "X").apply { getTypeReference()!!.replace(createType(typeElement)) }
}
@@ -69,4 +69,8 @@ public class KtValueArgumentList extends KtElementImpl {
assert argument.getParent() == this;
EditCommaSeparatedListHelper.INSTANCE.removeItem(argument);
}
public void removeArgument(int index) {
removeArgument(getArguments().get(index));
}
}
@@ -20,10 +20,8 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiErrorElement
import com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtFunctionType
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.psi.psiUtil.siblings
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
@@ -56,18 +54,21 @@ fun setTypeReference(declaration: KtCallableDeclaration, addAfter: PsiElement?,
}
}
fun KtCallableDeclaration.setReceiverTypeReference(typeRef: KtTypeReference?): KtTypeReference? {
private inline fun <T : KtElement> T.doSetReceiverTypeReference(
typeRef: KtTypeReference?,
getReceiverTypeReference: T.() -> KtTypeReference?,
addReceiverTypeReference: T.(typeRef: KtTypeReference) -> KtTypeReference
): KtTypeReference? {
val needParentheses = typeRef != null && typeRef.typeElement is KtFunctionType && !typeRef.hasParentheses()
val oldTypeRef = receiverTypeReference
val oldTypeRef = getReceiverTypeReference()
if (typeRef != null) {
val newTypeRef =
if (oldTypeRef != null) {
oldTypeRef.replace(typeRef) as KtTypeReference
}
else {
val anchor = nameIdentifier ?: valueParameterList
val newTypeRef = addBefore(typeRef, anchor) as KtTypeReference
addAfter(KtPsiFactory(project).createDot(), newTypeRef)
val newTypeRef = addReceiverTypeReference(typeRef)
addAfter(KtPsiFactory(project).createDot(), newTypeRef.parentsWithSelf.first { it.parent == this })
newTypeRef
}
if (needParentheses) {
@@ -79,9 +80,27 @@ fun KtCallableDeclaration.setReceiverTypeReference(typeRef: KtTypeReference?): K
}
else {
if (oldTypeRef != null) {
val dot = oldTypeRef.siblings(forward = true).firstOrNull { it.node.elementType == KtTokens.DOT }
deleteChildRange(oldTypeRef, dot ?: oldTypeRef)
val dotSibling = oldTypeRef.parent as? KtFunctionTypeReceiver ?: oldTypeRef
val dot = dotSibling.siblings(forward = true).firstOrNull { it.node.elementType == KtTokens.DOT }
deleteChildRange(dotSibling, dot ?: dotSibling)
}
return null
}
}
}
fun KtCallableDeclaration.setReceiverTypeReference(typeRef: KtTypeReference?) =
doSetReceiverTypeReference(
typeRef,
{ receiverTypeReference },
{ this.addBefore(it, nameIdentifier ?: valueParameterList) as KtTypeReference }
)
fun KtFunctionType.setReceiverTypeReference(typeRef: KtTypeReference?) =
doSetReceiverTypeReference(
typeRef,
{ receiverTypeReference },
{
(addBefore(KtPsiFactory(project).createFunctionTypeReceiver(it),
parameterList ?: firstChild) as KtFunctionTypeReceiver).typeReference
}
)
@@ -17,9 +17,9 @@
package org.jetbrains.kotlin.resolve.calls.resolvedCallUtil
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.psi.KtPsiUtil
import org.jetbrains.kotlin.psi.KtThisExpression
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.callUtil.isSafeCall
import org.jetbrains.kotlin.resolve.calls.context.CallResolutionContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
@@ -112,3 +112,9 @@ fun ResolvedCall<*>.hasBothReceivers() = dispatchReceiver != null && extensionRe
fun ResolvedCall<*>.getDispatchReceiverWithSmartCast(): ReceiverValue?
= getReceiverValueWithSmartCast(dispatchReceiver, smartCastDispatchReceiverType)
fun KtCallElement.getArgumentByParameterIndex(index: Int, context: BindingContext): List<ValueArgument> {
val resolvedCall = getResolvedCall(context) ?: return emptyList()
val parameterToProcess = resolvedCall.resultingDescriptor.valueParameters.getOrNull(index) ?: return emptyList()
return resolvedCall.valueArguments[parameterToProcess]?.arguments ?: emptyList()
}
@@ -0,0 +1,3 @@
fun foo(f: Int.(s: String) -> Boolean) = 1.f("2")
fun bar = foo { s -> s.length() > this }
@@ -0,0 +1,3 @@
fun foo(f: (n: <spot>Int</spot>, s: String) -> Boolean) = f(1, "2")
fun bar = foo { n, s -> s.length() > n }
@@ -0,0 +1,5 @@
<html>
<body>
This intention converts given parameter of a function type used in a function parameter to receiver
</body>
</html>
+5
View File
@@ -1456,6 +1456,11 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.ConvertFunctionTypeParameterToReceiverIntention</className>
<category>Kotlin</category>
</intentionAction>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
displayName="Object literal can be converted to lambda"
groupName="Kotlin"
@@ -0,0 +1,358 @@
/*
* Copyright 2010-2016 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.intentions
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.MethodReferencesSearch
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.refactoring.util.RefactoringUIUtil
import com.intellij.usageView.UsageInfo
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.core.*
import org.jetbrains.kotlin.idea.refactoring.CallableRefactoring
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.explicateReceiverOf
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.getAffectedCallables
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler
import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.references.KtSimpleReference
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.psi.typeRefHelpers.setReceiverTypeReference
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.getAbbreviatedTypeOrType
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getArgumentByParameterIndex
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
class ConvertFunctionTypeParameterToReceiverIntention : SelfTargetingRangeIntention<KtTypeReference>(
KtTypeReference::class.java,
"Convert function type parameter to receiver"
) {
abstract class AbstractUsageInfo<out T : PsiElement>(element: T) : UsageInfo(element) {
@Suppress("UNCHECKED_CAST")
override fun getElement() = super.getElement() as T?
abstract fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>)
}
class FunctionDefinitionInfo(element: KtFunction) : AbstractUsageInfo<KtFunction>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val function = element ?: return
val functionParameter = function.valueParameters.getOrNull(data.functionParameterIndex) ?: return
val functionType = functionParameter.typeReference?.typeElement as? KtFunctionType ?: return
val functionTypeParameterList = functionType.parameterList ?: return
val parameterToMove = functionTypeParameterList.parameters.getOrNull(data.typeParameterIndex) ?: return
val typeReferenceToMove = parameterToMove.typeReference ?: return
functionType.setReceiverTypeReference(typeReferenceToMove)
functionTypeParameterList.removeParameter(parameterToMove)
}
}
class ParameterCallInfo(element: KtCallExpression) : AbstractUsageInfo<KtCallExpression>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val callExpression = element ?: return
val argumentList = callExpression.valueArgumentList ?: return
val expressionToMove = argumentList.arguments.getOrNull(data.typeParameterIndex)?.getArgumentExpression() ?: return
val callWithReceiver = KtPsiFactory(callExpression).createExpressionByPattern("$0.$1", expressionToMove, callExpression) as KtQualifiedExpression
(callWithReceiver.selectorExpression as KtCallExpression).valueArgumentList!!.removeArgument(data.typeParameterIndex)
callExpression.replace(callWithReceiver)
}
}
class InternalReferencePassInfo(element: KtSimpleNameExpression) : AbstractUsageInfo<KtSimpleNameExpression>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val expression = element ?: return
val lambdaType = data.lambdaType
val validator = CollectingNameValidator()
val parameterNames = lambdaType.arguments
.dropLast(1)
.map { KotlinNameSuggester.suggestNamesByType(it.type, validator, "p").first() }
val receiver = parameterNames.getOrNull(data.typeParameterIndex) ?: return
val arguments = parameterNames.filter { it != receiver }
val adapterLambda = KtPsiFactory(expression).createLambdaExpression(
parameterNames.joinToString(),
"$receiver.${expression.text}(${arguments.joinToString()})"
)
expression.replaced(adapterLambda).let {
MoveLambdaOutsideParenthesesIntention.moveFunctionLiteralOutsideParenthesesIfPossible(it)
}
}
}
class LambdaInfo(element: KtExpression) : AbstractUsageInfo<KtExpression>(element) {
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
val expression = element ?: return
val context = expression.analyze(BodyResolveMode.PARTIAL)
val psiFactory = KtPsiFactory(expression)
if (expression is KtLambdaExpression || (expression !is KtSimpleNameExpression && expression !is KtCallableReferenceExpression)) {
expression.forEachDescendantOfType<KtThisExpression> {
if (it.getLabelName() != null) return@forEachDescendantOfType
val descriptor = context[BindingContext.REFERENCE_TARGET, it.instanceReference] ?: return@forEachDescendantOfType
it.replace(psiFactory.createExpression(explicateReceiverOf(descriptor)))
}
}
if (expression is KtLambdaExpression) {
expression.valueParameters.getOrNull(data.typeParameterIndex)?.let { parameterToConvert ->
val thisRefExpr = psiFactory.createThisExpression()
for (ref in ReferencesSearch.search(parameterToConvert, LocalSearchScope(expression))) {
(ref.element as? KtSimpleNameExpression)?.replace(thisRefExpr)
}
expression.functionLiteral.valueParameterList!!.removeParameter(parameterToConvert)
}
return
}
val originalLambdaTypes = data.lambdaType
val originalParameterTypes = originalLambdaTypes.arguments.dropLast(1).map { it.type }
val calleeText = when (expression) {
is KtSimpleNameExpression -> expression.text
is KtCallableReferenceExpression -> "(${expression.text})"
else -> generateVariable(expression)
}
val parameterNameValidator = CollectingNameValidator(
if (expression !is KtCallableReferenceExpression) listOf(calleeText) else emptyList()
)
val parameterNamesWithReceiver = originalParameterTypes.mapIndexed { i, type ->
if (i != data.typeParameterIndex) KotlinNameSuggester.suggestNamesByType(type, parameterNameValidator, "p").first() else "this"
}
val parameterNames = parameterNamesWithReceiver.filter { it != "this" }
val body = psiFactory.createExpression(parameterNamesWithReceiver.joinToString(prefix = "$calleeText(", postfix = ")"))
val replacingLambda = psiFactory.buildExpression {
appendFixedText("{ ")
appendFixedText(parameterNames.joinToString())
appendFixedText(" -> ")
appendExpression(body)
appendFixedText(" }")
} as KtLambdaExpression
expression.replaced(replacingLambda).let {
MoveLambdaOutsideParenthesesIntention.moveFunctionLiteralOutsideParenthesesIfPossible(it)
}
}
private fun generateVariable(expression: KtExpression): String {
var baseCallee: String = ""
KotlinIntroduceVariableHandler.doRefactoring(project, null, expression, false, emptyList()) {
baseCallee = it.name!!
}
return baseCallee
}
}
private inner class Converter(
private val data: ConversionData
) : CallableRefactoring<CallableDescriptor>(data.function.project, data.functionDescriptor, text) {
override fun performRefactoring(descriptorsForChange: Collection<CallableDescriptor>) {
val callables = getAffectedCallables(project, descriptorsForChange)
val conflicts = MultiMap<PsiElement, String>()
val usages = ArrayList<AbstractUsageInfo<*>>()
project.runSynchronouslyWithProgress("Looking for usages and conflicts...", true) {
runReadAction {
val progressStep = 1.0/callables.size
for ((i, callable) in callables.withIndex()) {
ProgressManager.getInstance().progressIndicator.fraction = (i + 1) * progressStep
if (callable !is PsiNamedElement) continue
if (!checkModifiable(callable)) {
val renderedCallable = RefactoringUIUtil.getDescription(callable, true).capitalize()
conflicts.putValue(callable, "Can't modify $renderedCallable")
}
val references = callable.toLightMethods().flatMapTo(LinkedHashSet()) { MethodReferencesSearch.search(it) }
usageLoop@ for (ref in references) {
val refElement = ref.element ?: continue
when (ref) {
is KtSimpleReference<*> -> processExternalUsage(conflicts, refElement, usages)
is KtReference -> continue@usageLoop
else -> {
if (data.isFirstParameter) continue@usageLoop
conflicts.putValue(
refElement,
"Can't replace non-Kotlin reference with call expression: " + StringUtil.htmlEmphasize(refElement.text)
)
}
}
}
if (callable is KtFunction) {
usages += FunctionDefinitionInfo(callable)
processInternalUsages(callable, usages)
}
}
}
}
project.checkConflictsInteractively(conflicts) {
project.executeWriteCommand(text) {
val elementsToShorten = ArrayList<KtElement>()
usages.forEach { it.process(data, elementsToShorten) }
ShortenReferences.DEFAULT.process(elementsToShorten)
}
}
}
private fun processExternalUsage(conflicts: MultiMap<PsiElement, String>, refElement: PsiElement, usages: java.util.ArrayList<AbstractUsageInfo<*>>) {
val callElement = refElement.getParentOfTypeAndBranch<KtCallElement> { calleeExpression }
if (callElement != null) {
val context = callElement.analyze(BodyResolveMode.PARTIAL)
val expressionToProcess = getArgumentExpressionToProcess(callElement, context) ?: return
if (!data.isFirstParameter
&& callElement is KtConstructorDelegationCall
&& expressionToProcess !is KtLambdaExpression
&& expressionToProcess !is KtSimpleNameExpression
&& expressionToProcess !is KtCallableReferenceExpression) {
conflicts.putValue(
expressionToProcess,
"Following expression won't be processed since refactoring can't preserve its semantics: ${expressionToProcess.text}"
)
return
}
if (!checkThisExpressionsAreExplicatable(conflicts, context, expressionToProcess)) return
if (data.isFirstParameter && expressionToProcess !is KtLambdaExpression) return
usages += LambdaInfo(expressionToProcess)
return
}
if (data.isFirstParameter) return
val callableReference = refElement.getParentOfTypeAndBranch<KtCallableReferenceExpression> { callableReference }
if (callableReference != null) {
conflicts.putValue(
refElement,
"Callable reference transformation is not supported: " + StringUtil.htmlEmphasize(callableReference.text)
)
return
}
}
private fun getArgumentExpressionToProcess(callElement: KtCallElement, context: BindingContext): KtExpression? {
return callElement
.getArgumentByParameterIndex(data.functionParameterIndex, context)
.singleOrNull()
?.getArgumentExpression()
?.let { KtPsiUtil.safeDeparenthesize(it) }
}
private fun checkThisExpressionsAreExplicatable(conflicts: MultiMap<PsiElement, String>, context: BindingContext, expressionToProcess: KtExpression): Boolean {
for (thisExpr in expressionToProcess.collectDescendantsOfType<KtThisExpression>()) {
if (thisExpr.getLabelName() != null) continue
val descriptor = context[BindingContext.REFERENCE_TARGET, thisExpr.instanceReference] ?: continue
if (explicateReceiverOf(descriptor) == "this") {
conflicts.putValue(
thisExpr,
"Following expression won't be processed since refactoring can't preserve its semantics: ${thisExpr.text}"
)
return false
}
}
return true
}
private fun processInternalUsages(callable: KtFunction, usages: ArrayList<AbstractUsageInfo<*>>) {
val body = when (callable) {
is KtConstructor<*> -> callable.containingClassOrObject?.getBody()
else -> callable.bodyExpression
}
if (body != null) {
val functionParameter = callable.valueParameters.getOrNull(data.functionParameterIndex) ?: return
for (ref in ReferencesSearch.search(functionParameter, LocalSearchScope(body))) {
val element = ref.element as? KtSimpleNameExpression ?: continue
val callExpression = element.getParentOfTypeAndBranch<KtCallExpression> { calleeExpression }
if (callExpression != null) {
usages += ParameterCallInfo(callExpression)
}
else if (!data.isFirstParameter) {
usages += InternalReferencePassInfo(element)
}
}
}
}
}
class ConversionData(
val typeParameterIndex: Int,
val functionParameterIndex: Int,
val lambdaType: KotlinType,
val function: KtFunction
) {
val isFirstParameter: Boolean get() = typeParameterIndex == 0
val functionDescriptor by lazy { function.resolveToDescriptor() as FunctionDescriptor }
}
private fun KtTypeReference.getConversionData(): ConversionData? {
val parameter = parent as? KtParameter ?: return null
val functionType = parameter.getParentOfTypeAndBranch<KtFunctionType> { parameterList } ?: return null
if (functionType.receiverTypeReference != null) return null
val lambdaType = functionType.getAbbreviatedTypeOrType(functionType.analyze(BodyResolveMode.PARTIAL)) ?: return null
val containingParameter = (functionType.parent as? KtTypeReference)?.parent as? KtParameter ?: return null
val ownerFunction = containingParameter.ownerFunction ?: return null
val typeParameterIndex = functionType.parameters.indexOf(parameter)
val functionParameterIndex = ownerFunction.valueParameters.indexOf(containingParameter)
return ConversionData(typeParameterIndex, functionParameterIndex, lambdaType, ownerFunction)
}
override fun startInWriteAction(): Boolean = false
override fun applicabilityRange(element: KtTypeReference): TextRange? {
val data = element.getConversionData() ?: return null
val elementBefore = data.function.valueParameters[data.functionParameterIndex].typeReference!!.typeElement as KtFunctionType
val elementAfter = elementBefore.copied().apply {
setReceiverTypeReference(element)
parameterList!!.removeParameter(data.typeParameterIndex)
}
text = "Convert '${elementBefore.text}' to '${elementAfter.text}'"
return element.textRange
}
override fun applyTo(element: KtTypeReference, editor: Editor?) {
element.getConversionData()?.let { Converter(it).run() }
}
}
@@ -56,6 +56,13 @@ class MoveLambdaOutsideParenthesesIntention : SelfTargetingIntention<KtCallExpre
return true
}
fun moveFunctionLiteralOutsideParenthesesIfPossible(expression: KtLambdaExpression) {
val call = ((expression.parent as? KtValueArgument)?.parent as? KtValueArgumentList)?.parent as? KtCallExpression ?: return
if (canMove(call)) {
call.moveFunctionLiteralOutsideParentheses()
}
}
}
override fun isApplicableTo(element: KtCallExpression, caretOffset: Int): Boolean {
@@ -524,7 +524,12 @@ object KotlinIntroduceVariableHandler : RefactoringActionHandler {
|| expression.shouldReplaceOccurrence(bindingContext, container)
|| allReplaces.size > 1
val commonParent = PsiTreeUtil.findCommonParent(allReplaces.map { it.substringContextOrThis }) as KtElement
val commonParent = if (allReplaces.isNotEmpty()) {
PsiTreeUtil.findCommonParent(allReplaces.map { it.substringContextOrThis }) as KtElement
}
else {
expression.parent as KtElement
}
var commonContainer = commonParent.getContainer()!!
if (commonContainer != container && container.isAncestor(commonContainer, true)) {
commonContainer = container
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.ConvertFunctionTypeParameterToReceiverIntention
@@ -0,0 +1,4 @@
// IS_APPLICABLE: false
fun foo(f: Int.(<caret>Boolean) -> String) {
}
@@ -0,0 +1,10 @@
// SHOULD_FAIL_WITH: Following expression won't be processed since refactoring can't preserve its semantics: this
val o = object {
fun bar() {
foo { i, b -> "$this: $i $b" }
}
}
fun foo(f: (<caret>Int, Boolean) -> String) {
}
@@ -0,0 +1,22 @@
fun foo(f: (<caret>Int, Boolean) -> String) {
f(1, false)
bar(f)
}
fun bar(f: (Int, Boolean) -> String) {
}
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
fun baz(f: (Int, Boolean) -> String) {
fun g(i: Int, b: Boolean) = ""
foo(f)
foo(::g)
foo(lambda())
foo { i, b -> "${i + 1} $b" }
}
@@ -0,0 +1,22 @@
fun foo(f: Int.(<caret>Boolean) -> String) {
1.f(false)
bar(f)
}
fun bar(f: (Int, Boolean) -> String) {
}
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
fun baz(f: (Int, Boolean) -> String) {
fun g(i: Int, b: Boolean) = ""
foo(f)
foo(::g)
foo(lambda())
foo { b -> "${this + 1} $b" }
}
@@ -0,0 +1,41 @@
open class Foo(f: (<caret>Int, Boolean) -> String) {
constructor(a: Int, f: (Int, Boolean) -> String) : this(f)
constructor(a: Int) : this(::g)
constructor(a: Int, b: Int) : this(lambda())
constructor(a: Int, b: Int, c: Int) : this({ i, b -> "${i + 1} $b" })
init {
f(1, false)
bar(f)
}
}
fun bar(f: (Int, Boolean) -> String) {
}
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
fun g(i: Int, b: Boolean) = ""
fun baz(f: (Int, Boolean) -> String) {
Foo(f)
Foo(::g)
Foo(lambda())
Foo { i, b -> "${i + 1} $b" }
}
class Baz1(f: (Int, Boolean) -> String) : Foo(f)
class Baz2 : Foo(::g)
class Baz3 : Foo(lambda())
class Baz4 : Foo({ i, b -> "${i + 1} $b" })
class Baz5 : Foo {
constructor(f: (Int, Boolean) -> String) : super(f)
constructor(a: Int) : super(::g)
constructor(a: Int, b: Int) : super(lambda())
constructor(a: Int, b: Int, c: Int) : super({ i, b -> "${i + 1} $b" })
}
@@ -0,0 +1,41 @@
open class Foo(f: Int.(<caret>Boolean) -> String) {
constructor(a: Int, f: (Int, Boolean) -> String) : this(f)
constructor(a: Int) : this(::g)
constructor(a: Int, b: Int) : this(lambda())
constructor(a: Int, b: Int, c: Int) : this({ b -> "${this + 1} $b" })
init {
1.f(false)
bar(f)
}
}
fun bar(f: (Int, Boolean) -> String) {
}
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
fun g(i: Int, b: Boolean) = ""
fun baz(f: (Int, Boolean) -> String) {
Foo(f)
Foo(::g)
Foo(lambda())
Foo { b -> "${this + 1} $b" }
}
class Baz1(f: (Int, Boolean) -> String) : Foo(f)
class Baz2 : Foo(::g)
class Baz3 : Foo(lambda())
class Baz4 : Foo({ b -> "${this + 1} $b" })
class Baz5 : Foo {
constructor(f: (Int, Boolean) -> String) : super(f)
constructor(a: Int) : super(::g)
constructor(a: Int, b: Int) : super(lambda())
constructor(a: Int, b: Int, c: Int) : super({ b -> "${this + 1} $b" })
}
@@ -0,0 +1,41 @@
open class Foo {
constructor(f: (<caret>Int, Boolean) -> String) {
f(1, false)
bar(f)
}
constructor(a: Int, f: (Int, Boolean) -> String) : this(f)
constructor(a: Int) : this(::g)
constructor(a: Int, b: Int) : this(lambda())
constructor(a: Int, b: Int, c: Int) : this({ i, b -> "${i + 1} $b" })
}
fun bar(f: (Int, Boolean) -> String) {
}
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
fun g(i: Int, b: Boolean) = ""
fun baz(f: (Int, Boolean) -> String) {
Foo(f)
Foo(::g)
Foo(lambda())
Foo { i, b -> "${i + 1} $b" }
}
class Baz1(f: (Int, Boolean) -> String) : Foo(f)
class Baz2 : Foo(::g)
class Baz3 : Foo(lambda())
class Baz4 : Foo({ i, b -> "${i + 1} $b" })
class Baz5 : Foo {
constructor(f: (Int, Boolean) -> String) : super(f)
constructor(a: Int) : super(::g)
constructor(a: Int, b: Int) : super(lambda())
constructor(a: Int, b: Int, c: Int) : super({ i, b -> "${i + 1} $b" })
}
@@ -0,0 +1,41 @@
open class Foo {
constructor(f: Int.(<caret>Boolean) -> String) {
1.f(false)
bar(f)
}
constructor(a: Int, f: (Int, Boolean) -> String) : this(f)
constructor(a: Int) : this(::g)
constructor(a: Int, b: Int) : this(lambda())
constructor(a: Int, b: Int, c: Int) : this({ b -> "${this + 1} $b" })
}
fun bar(f: (Int, Boolean) -> String) {
}
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
fun g(i: Int, b: Boolean) = ""
fun baz(f: (Int, Boolean) -> String) {
Foo(f)
Foo(::g)
Foo(lambda())
Foo { b -> "${this + 1} $b" }
}
class Baz1(f: (Int, Boolean) -> String) : Foo(f)
class Baz2 : Foo(::g)
class Baz3 : Foo(lambda())
class Baz4 : Foo({ b -> "${this + 1} $b" })
class Baz5 : Foo {
constructor(f: (Int, Boolean) -> String) : super(f)
constructor(a: Int) : super(::g)
constructor(a: Int, b: Int) : super(lambda())
constructor(a: Int, b: Int, c: Int) : super({ b -> "${this + 1} $b" })
}
@@ -0,0 +1,6 @@
class J extends K {
@Override
public void foo(@NotNull Function2<? super Integer, ? super Boolean, String> f) {
super.foo(f);
}
}
@@ -0,0 +1,6 @@
class J extends K {
@Override
public void foo(@NotNull Function2<? super Integer, ? super Boolean, String> f) {
super.foo(f);
}
}
@@ -0,0 +1,5 @@
open class K {
open fun foo(f: (<caret>Int, Boolean) -> String) {
}
}
@@ -0,0 +1,5 @@
open class K {
open fun foo(f: Int.(<caret>Boolean) -> String) {
}
}
@@ -0,0 +1,22 @@
fun foo(f: (Int, <caret>Boolean) -> String) {
f(1, false)
bar(f)
}
fun bar(f: (Int, Boolean) -> String) {
}
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
fun baz(f: (Int, Boolean) -> String) {
fun g(i: Int, b: Boolean) = ""
foo(f)
foo(::g)
foo(lambda())
foo { i, b -> "${i + 1} $b" }
}
@@ -0,0 +1,23 @@
fun foo(f: Boolean.(Int<caret>) -> String) {
false.f(1)
bar { i, b -> b.f(i) }
}
fun bar(f: (Int, Boolean) -> String) {
}
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
fun baz(f: (Int, Boolean) -> String) {
fun g(i: Int, b: Boolean) = ""
foo { i -> f(i, this) }
foo { i -> (::g)(i, this) }
val f1 = lambda()
foo { i -> f1(i, this) }
foo { i -> "${i + 1} ${this}" }
}
@@ -0,0 +1,8 @@
// SHOULD_FAIL_WITH: Callable reference transformation is not supported: ::foo
fun foo(f: (Int, <caret>Boolean) -> String) {
}
fun baz(f: (Int, Boolean) -> String) {
val x = ::foo
}
@@ -0,0 +1,39 @@
open class Foo(f: (Int, <caret>Boolean) -> String) {
constructor(a: Int, f: (Int, Boolean) -> String) : this(f)
constructor(a: Int) : this(::g)
constructor(a: Int, b: Int, c: Int) : this({ i, b -> "${i + 1} $b" })
init {
f(1, false)
bar(f)
}
}
fun bar(f: (Int, Boolean) -> String) {
}
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
fun g(i: Int, b: Boolean) = ""
fun baz(f: (Int, Boolean) -> String) {
Foo(f)
Foo(::g)
Foo(lambda())
Foo { i, b -> "${i + 1} $b" }
}
class Baz1(f: (Int, Boolean) -> String) : Foo(f)
class Baz2 : Foo(::g)
class Baz3 : Foo(lambda())
class Baz4 : Foo({ i, b -> "${i + 1} $b" })
class Baz5 : Foo {
constructor(f: (Int, Boolean) -> String) : super(f)
constructor(a: Int) : super(::g)
constructor(a: Int, b: Int, c: Int) : super({ i, b -> "${i + 1} $b" })
}
@@ -0,0 +1,43 @@
open class Foo(f: Boolean.(Int<caret>) -> String) {
constructor(a: Int, f: (Int, Boolean) -> String) : this({ i -> f(i, this) })
constructor(a: Int) : this({ i -> (::g)(i, this) })
constructor(a: Int, b: Int, c: Int) : this({ i -> "${i + 1} ${this}" })
init {
false.f(1)
bar { i, b -> b.f(i) }
}
}
fun bar(f: (Int, Boolean) -> String) {
}
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
fun g(i: Int, b: Boolean) = ""
fun baz(f: (Int, Boolean) -> String) {
Foo { i -> f(i, this) }
Foo { i -> (::g)(i, this) }
val f1 = lambda()
Foo { i -> f1(i, this) }
Foo { i -> "${i + 1} ${this}" }
}
class Baz1(f: (Int, Boolean) -> String) : Foo({ i -> f(i, this) })
class Baz2 : Foo({ i -> (::g)(i, this) })
val f = lambda()
class Baz3 : Foo({ i -> f(i, this) })
class Baz4 : Foo({ i -> "${i + 1} ${this}" })
class Baz5 : Foo {
constructor(f: (Int, Boolean) -> String) : super({ i -> f(i, this) })
constructor(a: Int) : super({ i -> (::g)(i, this) })
constructor(a: Int, b: Int, c: Int) : super({ i -> "${i + 1} ${this}" })
}
@@ -0,0 +1,39 @@
open class Foo {
constructor(f: (Int, <caret>Boolean) -> String){
f(1, false)
bar(f)
}
constructor(a: Int, f: (Int, Boolean) -> String) : this(f)
constructor(a: Int) : this(::g)
constructor(a: Int, b: Int, c: Int) : this({ i, b -> "${i + 1} $b" })
}
fun bar(f: (Int, Boolean) -> String) {
}
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
fun g(i: Int, b: Boolean) = ""
fun baz(f: (Int, Boolean) -> String) {
Foo(f)
Foo(::g)
Foo(lambda())
Foo { i, b -> "${i + 1} $b" }
}
class Baz1(f: (Int, Boolean) -> String) : Foo(f)
class Baz2 : Foo(::g)
class Baz3 : Foo(lambda())
class Baz4 : Foo({ i, b -> "${i + 1} $b" })
class Baz5 : Foo {
constructor(f: (Int, Boolean) -> String) : super(f)
constructor(a: Int) : super(::g)
constructor(a: Int, b: Int, c: Int) : super({ i, b -> "${i + 1} $b" })
}
@@ -0,0 +1,43 @@
open class Foo {
constructor(f: Boolean.(Int<caret>) -> String){
false.f(1)
bar { i, b -> b.f(i) }
}
constructor(a: Int, f: (Int, Boolean) -> String) : this({ i -> f(i, this) })
constructor(a: Int) : this({ i -> (::g)(i, this) })
constructor(a: Int, b: Int, c: Int) : this({ i -> "${i + 1} ${this}" })
}
fun bar(f: (Int, Boolean) -> String) {
}
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
fun g(i: Int, b: Boolean) = ""
fun baz(f: (Int, Boolean) -> String) {
Foo { i -> f(i, this) }
Foo { i -> (::g)(i, this) }
val f1 = lambda()
Foo { i -> f1(i, this) }
Foo { i -> "${i + 1} ${this}" }
}
class Baz1(f: (Int, Boolean) -> String) : Foo({ i -> f(i, this) })
class Baz2 : Foo({ i -> (::g)(i, this) })
val f = lambda()
class Baz3 : Foo({ i -> f(i, this) })
class Baz4 : Foo({ i -> "${i + 1} ${this}" })
class Baz5 : Foo {
constructor(f: (Int, Boolean) -> String) : super({ i -> f(i, this) })
constructor(a: Int) : super({ i -> (::g)(i, this) })
constructor(a: Int, b: Int, c: Int) : super({ i -> "${i + 1} ${this}" })
}
@@ -0,0 +1,8 @@
// SHOULD_FAIL_WITH: Following expression won't be processed since refactoring can't preserve its semantics: lambda()
open class Foo(f: (Int, <caret>Boolean) -> String)
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
class Baz5 : Foo {
constructor() : super(lambda())
}
@@ -0,0 +1,6 @@
// SHOULD_FAIL_WITH: Following expression won't be processed since refactoring can't preserve its semantics: lambda()
open class Foo(f: (Int, <caret>Boolean) -> String) {
constructor() : this(lambda())
}
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
@@ -0,0 +1,6 @@
class J extends K {
@Override
public void foo(@NotNull Function2<? super Integer, ? super Boolean, String> f) {
super.foo(f);
}
}
@@ -0,0 +1,6 @@
// SHOULD_FAIL_WITH: Can't replace non-Kotlin reference with call expression: super.foo
open class K {
open fun foo(f: (Int, <caret>Boolean) -> String) {
}
}
@@ -0,0 +1,2 @@
// IS_APPLICABLE: false
fun foo(f: (Int, Boolean) -> String): (<caret>Int, Boolean) -> String = f
@@ -0,0 +1,4 @@
// IS_APPLICABLE: false
fun foo(f: (Int, Boolean) -> <caret>String) {
}
@@ -0,0 +1,11 @@
open class A {
open fun foo(f: (Int, <caret>Boolean) -> String) {
}
}
class B : A() {
override fun foo(f: (Int, Boolean) -> String) {
}
}
@@ -0,0 +1,11 @@
open class A {
open fun foo(f: Boolean.(Int<caret>) -> String) {
}
}
class B : A() {
override fun foo(f: Boolean.(Int) -> String) {
}
}
@@ -3996,6 +3996,111 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
@TestMetadata("idea/testData/intentions/convertFunctionTypeParameterToReceiver")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ConvertFunctionTypeParameterToReceiver extends AbstractIntentionTest {
public void testAllFilesPresentInConvertFunctionTypeParameterToReceiver() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertFunctionTypeParameterToReceiver"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("alreadyHasReceiver.kt")
public void testAlreadyHasReceiver() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeParameterToReceiver/alreadyHasReceiver.kt");
doTest(fileName);
}
@TestMetadata("cantReplaceWithThis.kt")
public void testCantReplaceWithThis() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeParameterToReceiver/cantReplaceWithThis.kt");
doTest(fileName);
}
@TestMetadata("firstParameter.kt")
public void testFirstParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeParameterToReceiver/firstParameter.kt");
doTest(fileName);
}
@TestMetadata("firstParameterPrimaryConstructor.kt")
public void testFirstParameterPrimaryConstructor() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeParameterToReceiver/firstParameterPrimaryConstructor.kt");
doTest(fileName);
}
@TestMetadata("firstParameterSecondaryConstructor.kt")
public void testFirstParameterSecondaryConstructor() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeParameterToReceiver/firstParameterSecondaryConstructor.kt");
doTest(fileName);
}
@TestMetadata("firstParameterWithJavaUsages.kt")
public void testFirstParameterWithJavaUsages() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeParameterToReceiver/firstParameterWithJavaUsages.kt");
doTest(fileName);
}
@TestMetadata("nonFirstParameter.kt")
public void testNonFirstParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeParameterToReceiver/nonFirstParameter.kt");
doTest(fileName);
}
@TestMetadata("nonFirstParameterCallableReferenceUsage.kt")
public void testNonFirstParameterCallableReferenceUsage() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeParameterToReceiver/nonFirstParameterCallableReferenceUsage.kt");
doTest(fileName);
}
@TestMetadata("nonFirstParameterPrimaryConstructor.kt")
public void testNonFirstParameterPrimaryConstructor() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeParameterToReceiver/nonFirstParameterPrimaryConstructor.kt");
doTest(fileName);
}
@TestMetadata("nonFirstParameterSecondaryConstructor.kt")
public void testNonFirstParameterSecondaryConstructor() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeParameterToReceiver/nonFirstParameterSecondaryConstructor.kt");
doTest(fileName);
}
@TestMetadata("nonFirstParameterSuperDelegationCall.kt")
public void testNonFirstParameterSuperDelegationCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeParameterToReceiver/nonFirstParameterSuperDelegationCall.kt");
doTest(fileName);
}
@TestMetadata("nonFirstParameterThisDelegationCall.kt")
public void testNonFirstParameterThisDelegationCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeParameterToReceiver/nonFirstParameterThisDelegationCall.kt");
doTest(fileName);
}
@TestMetadata("nonFirstParameterWithJavaUsages.kt")
public void testNonFirstParameterWithJavaUsages() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeParameterToReceiver/nonFirstParameterWithJavaUsages.kt");
doTest(fileName);
}
@TestMetadata("notInFunctionParameter.kt")
public void testNotInFunctionParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeParameterToReceiver/notInFunctionParameter.kt");
doTest(fileName);
}
@TestMetadata("notOnFunctionTypeParameter.kt")
public void testNotOnFunctionTypeParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeParameterToReceiver/notOnFunctionTypeParameter.kt");
doTest(fileName);
}
@TestMetadata("overrides.kt")
public void testOverrides() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeParameterToReceiver/overrides.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/intentions/convertIfWithThrowToAssert")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)