IfNullToElvisIntention - better behavior for var's + slightly refactored utility functions

This commit is contained in:
Valentin Kipyatkov
2015-04-29 16:45:29 +03:00
parent 186dd5454d
commit 38f735dcac
7 changed files with 49 additions and 45 deletions
@@ -16,14 +16,13 @@
package org.jetbrains.kotlin.idea.intentions package org.jetbrains.kotlin.idea.intentions
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.JetBundle
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.types.JetType
public class ConvertToBlockBodyIntention : JetSelfTargetingIntention<JetDeclarationWithBody>( public class ConvertToBlockBodyIntention : JetSelfTargetingIntention<JetDeclarationWithBody>(
javaClass(), "Convert to block body", firstElementOfTypeOnly = true javaClass(), "Convert to block body", firstElementOfTypeOnly = true
@@ -33,7 +32,7 @@ public class ConvertToBlockBodyIntention : JetSelfTargetingIntention<JetDeclarat
when (element) { when (element) {
is JetNamedFunction -> { is JetNamedFunction -> {
val returnType = functionReturnType(element) ?: return false val returnType = element.returnType() ?: return false
if (!element.hasDeclaredReturnType() && returnType.isError()) return false// do not convert when type is implicit and unknown if (!element.hasDeclaredReturnType() && returnType.isError()) return false// do not convert when type is implicit and unknown
return true return true
} }
@@ -53,7 +52,7 @@ public class ConvertToBlockBodyIntention : JetSelfTargetingIntention<JetDeclarat
val body = declaration.getBodyExpression()!! val body = declaration.getBodyExpression()!!
fun generateBody(returnsValue: Boolean): JetExpression { fun generateBody(returnsValue: Boolean): JetExpression {
val bodyType = expressionType(body) val bodyType = body.analyze().getType(body)
val needReturn = returnsValue && val needReturn = returnsValue &&
(bodyType == null || (!KotlinBuiltIns.isUnit(bodyType) && !KotlinBuiltIns.isNothing(bodyType))) (bodyType == null || (!KotlinBuiltIns.isUnit(bodyType) && !KotlinBuiltIns.isNothing(bodyType)))
@@ -64,9 +63,9 @@ public class ConvertToBlockBodyIntention : JetSelfTargetingIntention<JetDeclarat
val newBody = when (declaration) { val newBody = when (declaration) {
is JetNamedFunction -> { is JetNamedFunction -> {
val returnType = functionReturnType(declaration)!! val returnType = declaration.returnType()!!
if (!declaration.hasDeclaredReturnType() && !KotlinBuiltIns.isUnit(returnType)) { if (!declaration.hasDeclaredReturnType() && !KotlinBuiltIns.isUnit(returnType)) {
specifyTypeExplicitly(declaration, returnType) declaration.setType(returnType)
} }
generateBody(!KotlinBuiltIns.isUnit(returnType) && !KotlinBuiltIns.isNothing(returnType)) generateBody(!KotlinBuiltIns.isUnit(returnType) && !KotlinBuiltIns.isNothing(returnType))
} }
@@ -80,5 +79,10 @@ public class ConvertToBlockBodyIntention : JetSelfTargetingIntention<JetDeclarat
body.replace(newBody) body.replace(newBody)
return declaration return declaration
} }
private fun JetNamedFunction.returnType(): JetType? {
val descriptor = analyze()[BindingContext.DECLARATION_TO_DESCRIPTOR, this] ?: return null
return (descriptor as FunctionDescriptor).getReturnType()
}
} }
} }
@@ -52,9 +52,9 @@ public class ConvertToExpressionBodyIntention : JetSelfTargetingOffsetIndependen
val value = calcValue(declaration)!! val value = calcValue(declaration)!!
if (!declaration.hasDeclaredReturnType() && declaration is JetNamedFunction) { if (!declaration.hasDeclaredReturnType() && declaration is JetNamedFunction) {
val valueType = expressionType(value) val valueType = value.analyze().getType(value)
if (valueType == null || !KotlinBuiltIns.isUnit(valueType)) { if (valueType == null || !KotlinBuiltIns.isUnit(valueType)) {
specifyTypeExplicitly(declaration, "Unit") declaration.setType(KotlinBuiltIns.getInstance().getUnitType())
} }
} }
@@ -114,7 +114,7 @@ public class ConvertToExpressionBodyIntention : JetSelfTargetingOffsetIndependen
is JetExpression -> { is JetExpression -> {
if (statement is JetBinaryExpression && statement.getOperationToken() == JetTokens.EQ) return null // assignment does not have value if (statement is JetBinaryExpression && statement.getOperationToken() == JetTokens.EQ) return null // assignment does not have value
val expressionType = expressionType(statement) ?: return null val expressionType = statement.analyze().getType(statement) ?: return null
if (!KotlinBuiltIns.isUnit(expressionType) && !KotlinBuiltIns.isNothing(expressionType)) return null if (!KotlinBuiltIns.isUnit(expressionType) && !KotlinBuiltIns.isNothing(expressionType)) return null
return statement return statement
} }
@@ -37,21 +37,24 @@ public class IfNullToElvisIntention : JetSelfTargetingOffsetIndependentIntention
val type = data.ifNullExpression.analyze().getType(data.ifNullExpression) ?: return false val type = data.ifNullExpression.analyze().getType(data.ifNullExpression) ?: return false
if (!type.isNothing()) return false if (!type.isNothing()) return false
//TODO: what if implicit var's type will change?
return true return true
} }
override fun applyTo(element: JetIfExpression, editor: Editor) { override fun applyTo(element: JetIfExpression, editor: Editor) {
val (initializer, statement, ifNullExpr) = calcData(element)!! val (initializer, declaration, ifNullExpr) = calcData(element)!!
val factory = JetPsiFactory(element) val factory = JetPsiFactory(element)
val explicitTypeToAdd = if (declaration.isVar() && declaration.getTypeReference() == null)
initializer.analyze().getType(initializer)
else
null
// do not loose any comments! // do not loose any comments!
val comments = element.extractComments(ifNullExpr) val comments = element.extractComments(ifNullExpr)
for (comment in comments) { for (comment in comments) {
statement.add(factory.createWhiteSpace()) declaration.add(factory.createWhiteSpace())
statement.add(comment) declaration.add(comment)
} }
val elvis = factory.createExpression("a ?: b") as JetBinaryExpression val elvis = factory.createExpression("a ?: b") as JetBinaryExpression
@@ -60,12 +63,16 @@ public class IfNullToElvisIntention : JetSelfTargetingOffsetIndependentIntention
val newElvis = initializer.replaced(elvis) val newElvis = initializer.replaced(elvis)
element.delete() element.delete()
if (explicitTypeToAdd != null && !explicitTypeToAdd.isError()) {
declaration.setType(explicitTypeToAdd)
}
editor.getCaretModel().moveToOffset(newElvis.getRight()!!.getTextOffset()) editor.getCaretModel().moveToOffset(newElvis.getRight()!!.getTextOffset())
} }
private data class Data( private data class Data(
val initializer: JetExpression, val initializer: JetExpression,
val statement: JetExpression, val declaration: JetVariableDeclaration,
val ifNullExpression: JetExpression val ifNullExpression: JetExpression
) )
@@ -28,33 +28,11 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.types.JetType import org.jetbrains.kotlin.types.JetType
fun specifyTypeExplicitly(declaration: JetNamedFunction, typeText: String) { fun JetCallableDeclaration.setType(type: JetType) {
specifyTypeExplicitly(declaration, JetPsiFactory(declaration).createType(typeText))
}
fun specifyTypeExplicitly(declaration: JetNamedFunction, type: JetType) {
if (type.isError()) return if (type.isError()) return
val typeReference = JetPsiFactory(declaration).createType(IdeDescriptorRenderers.SOURCE_CODE.renderType(type)) val typeReference = JetPsiFactory(getProject()).createType(IdeDescriptorRenderers.SOURCE_CODE.renderType(type))
specifyTypeExplicitly(declaration, typeReference) setTypeReference(typeReference)
ShortenReferences.DEFAULT.process(declaration.getTypeReference()!!) ShortenReferences.DEFAULT.process(getTypeReference()!!)
}
fun specifyTypeExplicitly(declaration: JetNamedFunction, typeReference: JetTypeReference) {
val anchor = declaration.getValueParameterList() ?: return/*incomplete declaration*/
declaration.addAfter(typeReference, anchor)
declaration.addAfter(JetPsiFactory(declaration).createColon(), anchor)
}
fun expressionType(expression: JetExpression): JetType? {
val bindingContext = expression.analyze()
return bindingContext.getType(expression)
}
fun functionReturnType(function: JetNamedFunction): JetType? {
val bindingContext = function.analyze()
val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, function)
if (descriptor == null) return null
return (descriptor as FunctionDescriptor).getReturnType()
} }
fun JetContainerNode.description(): String? { fun JetContainerNode.description(): String? {
@@ -0,0 +1,5 @@
fun foo(p: List<String?>, b: Boolean) {
var v = p[0]
<caret>if (v == null) return
if (b) v = null
}
@@ -0,0 +1,4 @@
fun foo(p: List<String?>, b: Boolean) {
var v: String? = p[0] ?: <caret>return
if (b) v = null
}
@@ -4173,6 +4173,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/ifNullToElvis/ThrowInBlock.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/ifNullToElvis/ThrowInBlock.kt");
doTest(fileName); doTest(fileName);
} }
@TestMetadata("Var.kt")
public void testVar() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/ifNullToElvis/Var.kt");
doTest(fileName);
}
} }
@TestMetadata("idea/testData/intentions/insertCurlyBracesToTemplate") @TestMetadata("idea/testData/intentions/insertCurlyBracesToTemplate")