If necessary, add parameter types while inlining lambda

This commit is contained in:
Mikhail Glukhikh
2017-04-11 16:03:31 +03:00
parent 666133beee
commit 6453e10d44
5 changed files with 92 additions and 17 deletions
@@ -19,21 +19,29 @@ package org.jetbrains.kotlin.idea.codeInliner
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.core.asExpression
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.intentions.InsertExplicitTypeArgumentsIntention
import org.jetbrains.kotlin.idea.intentions.SpecifyExplicitLambdaSignatureIntention
import org.jetbrains.kotlin.idea.references.canBeResolvedViaImport
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.isReallySuccess
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.utils.sure
import java.util.*
class CodeToInlineBuilder(
@@ -57,9 +65,58 @@ class CodeToInlineBuilder(
insertExplicitReceivers(codeToInline, bindingContext)
if (mainExpression != null) {
val functionLiteralExpression = mainExpression.unpackFunctionLiteral(true)
if (functionLiteralExpression != null) {
val functionLiteralParameterTypes = getParametersForFunctionLiteral(functionLiteralExpression, bindingContext)
if (functionLiteralParameterTypes != null) {
codeToInline.addPostInsertionAction(mainExpression) { inlinedExpression ->
addFunctionLiteralParameterTypes(functionLiteralParameterTypes, inlinedExpression)
}
}
}
}
return codeToInline.toNonMutable()
}
private fun getParametersForFunctionLiteral(functionLiteralExpression: KtLambdaExpression, context: BindingContext): String? {
val lambdaDescriptor = context.get(BindingContext.FUNCTION, functionLiteralExpression.functionLiteral)
if (lambdaDescriptor == null || ErrorUtils.containsErrorType(lambdaDescriptor)) return null
return lambdaDescriptor.valueParameters.joinToString {
it.name.asString() + ": " + IdeDescriptorRenderers.SOURCE_CODE.renderType(it.type)
}
}
private fun addFunctionLiteralParameterTypes(parameters: String, inlinedExpression: KtExpression) {
val containingFile = inlinedExpression.containingKtFile
val resolutionFacade = containingFile.getResolutionFacade()
val lambdaExpr = inlinedExpression.unpackFunctionLiteral(true).sure {
"can't find function literal expression for " + inlinedExpression.text
}
if (!needToAddParameterTypes(lambdaExpr, resolutionFacade)) return
SpecifyExplicitLambdaSignatureIntention.applyWithParameters(lambdaExpr, parameters)
}
private fun needToAddParameterTypes(
lambdaExpression: KtLambdaExpression,
resolutionFacade: ResolutionFacade
): Boolean {
val functionLiteral = lambdaExpression.functionLiteral
val context = resolutionFacade.analyze(lambdaExpression, BodyResolveMode.PARTIAL_WITH_DIAGNOSTICS)
return context.diagnostics.any { diagnostic ->
val factory = diagnostic.factory
val element = diagnostic.psiElement
val hasCantInferParameter = factory == Errors.CANNOT_INFER_PARAMETER_TYPE &&
element.parent.parent == functionLiteral
val hasUnresolvedItOrThis = factory == Errors.UNRESOLVED_REFERENCE &&
element.text == "it" &&
element.getStrictParentOfType<KtFunctionLiteral>() == functionLiteral
hasCantInferParameter || hasUnresolvedItOrThis
}
}
private fun insertExplicitTypeArguments(codeToInline: MutableCodeToInline, bindingContext: BindingContext, analyze: () -> BindingContext): BindingContext {
val typeArgsToAdd = ArrayList<Pair<KtCallExpression, KtTypeArgumentList>>()
codeToInline.forEachDescendantOfType<KtCallExpression> {
@@ -19,7 +19,6 @@ package org.jetbrains.kotlin.idea.intentions
import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
@@ -46,30 +45,36 @@ class SpecifyExplicitLambdaSignatureIntention : SelfTargetingIntention<KtLambdaE
}
override fun applyTo(element: KtLambdaExpression, editor: Editor?) {
val psiFactory = KtPsiFactory(element)
val functionLiteral = element.functionLiteral
val functionDescriptor = element.analyze(BodyResolveMode.PARTIAL)[BindingContext.FUNCTION, functionLiteral]!!
val parameterString = functionDescriptor.valueParameters
.map { "${it.name}: ${IdeDescriptorRenderers.SOURCE_CODE.renderType(it.type)}" }
.joinToString(", ")
applyWithParameters(element, parameterString)
}
val newParameterList = psiFactory.createLambdaParameterListIfAny(parameterString)
val oldParameterList = functionLiteral.valueParameterList
if (oldParameterList != null && newParameterList != null) {
oldParameterList.replace(newParameterList)
}
else {
val openBraceElement = functionLiteral.lBrace
val nextSibling = openBraceElement.nextSibling
val addNewline = nextSibling is PsiWhiteSpace && nextSibling.text?.contains("\n") ?: false
val (whitespace, arrow) = psiFactory.createWhitespaceAndArrow()
functionLiteral.addRangeAfter(whitespace, arrow, openBraceElement)
newParameterList?.let { functionLiteral.addAfter(it, openBraceElement) }
if (addNewline) {
functionLiteral.addAfter(psiFactory.createNewLine(), openBraceElement)
companion object {
fun applyWithParameters(element: KtLambdaExpression, parameterString: String) {
val psiFactory = KtPsiFactory(element)
val functionLiteral = element.functionLiteral
val newParameterList = psiFactory.createLambdaParameterListIfAny(parameterString)
val oldParameterList = functionLiteral.valueParameterList
if (oldParameterList != null && newParameterList != null) {
oldParameterList.replace(newParameterList)
}
else {
val openBraceElement = functionLiteral.lBrace
val nextSibling = openBraceElement.nextSibling
val addNewline = nextSibling is PsiWhiteSpace && nextSibling.text?.contains("\n") ?: false
val (whitespace, arrow) = psiFactory.createWhitespaceAndArrow()
functionLiteral.addRangeAfter(whitespace, arrow, openBraceElement)
newParameterList?.let { functionLiteral.addAfter(it, openBraceElement) }
if (addNewline) {
functionLiteral.addAfter(psiFactory.createNewLine(), openBraceElement)
}
}
ShortenReferences.DEFAULT.process(element.valueParameters)
}
ShortenReferences.DEFAULT.process(element.valueParameters)
}
}
@@ -0,0 +1,4 @@
fun foo() {
fun bar(): (Int) -> Int = { it }
val b = <caret>bar()
}
@@ -0,0 +1,3 @@
fun foo() {
val b = { it: Int -> it }
}
@@ -136,6 +136,12 @@ public class InlineTestGenerated extends AbstractInlineTest {
doTest(fileName);
}
@TestMetadata("FunctionalType.kt")
public void testFunctionalType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/expressionBody/FunctionalType.kt");
doTest(fileName);
}
@TestMetadata("MultipleInComposition.kt")
public void testMultipleInComposition() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/expressionBody/MultipleInComposition.kt");