Quick-fixes for SMARTCAST_IMPOSSIBLE: refactor

Enhancement for KT-27184
This commit is contained in:
Mikhail Glukhikh
2018-11-29 19:06:44 +03:00
parent cf3215b96e
commit 9049af3bdf
3 changed files with 117 additions and 110 deletions
@@ -32,18 +32,9 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
class IfThenToSafeAccessInspection(
private val stableElementNeeded: Boolean
) : AbstractApplicabilityBasedInspection<KtIfExpression>(KtIfExpression::class.java) {
class IfThenToSafeAccessInspection : AbstractApplicabilityBasedInspection<KtIfExpression>(KtIfExpression::class.java) {
constructor() : this(stableElementNeeded = true)
override fun isApplicable(element: KtIfExpression): Boolean {
val ifThenToSelectData = element.buildSelectTransformationData() ?: return false
if (stableElementNeeded && !ifThenToSelectData.receiverExpression.isStableSimpleExpression(ifThenToSelectData.context)) return false
return ifThenToSelectData.clausesReplaceableBySafeCall()
}
override fun isApplicable(element: KtIfExpression): Boolean = isApplicableTo(element, expressionShouldBeStable = true)
override fun inspectionTarget(element: KtIfExpression) = element.ifKeyword
@@ -54,50 +45,64 @@ class IfThenToSafeAccessInspection(
override val defaultFixText = "Simplify foldable if-then"
override fun fixText(element: KtIfExpression): String {
val ifThenToSelectData = element.buildSelectTransformationData()
return if (ifThenToSelectData?.baseClauseEvaluatesToReceiver() == true) {
if (ifThenToSelectData.condition is KtIsExpression) {
"Replace 'if' expression with safe cast expression"
} else {
"Remove redundant 'if' expression"
}
} else {
"Replace 'if' expression with safe access expression"
}
}
override fun fixText(element: KtIfExpression): String = fixTextFor(element)
override val startFixInWriteAction = false
override fun applyTo(element: PsiElement, project: Project, editor: Editor?) {
val ifExpression = element.getParentOfType<KtIfExpression>(true) ?: return
val ifThenToSelectData = ifExpression.buildSelectTransformationData() ?: return
val factory = KtPsiFactory(ifExpression)
val resultExpr = runWriteAction {
val replacedBaseClause = ifThenToSelectData.replacedBaseClause(factory)
val newExpr = ifExpression.replaced(replacedBaseClause)
KtPsiUtil.deparenthesize(newExpr)
}
if (editor != null && resultExpr is KtSafeQualifiedExpression) {
resultExpr.inlineReceiverIfApplicableWithPrompt(editor)
resultExpr.renameLetParameter(editor)
}
}
private fun IfThenToSelectData.clausesReplaceableBySafeCall(): Boolean = when {
baseClause == null -> false
negatedClause == null && baseClause.isUsedAsExpression(context) -> false
negatedClause != null && !negatedClause.isNullExpression() -> false
baseClause.evaluatesTo(receiverExpression) -> true
baseClause.hasFirstReceiverOf(receiverExpression) -> true
baseClause.anyArgumentEvaluatesTo(receiverExpression) -> true
receiverExpression is KtThisExpression -> getImplicitReceiver()?.let { it.type == receiverExpression.getType(context) } == true
else -> false
convert(ifExpression, editor)
}
companion object {
private fun IfThenToSelectData.clausesReplaceableBySafeCall(): Boolean = when {
baseClause == null -> false
negatedClause == null && baseClause.isUsedAsExpression(context) -> false
negatedClause != null && !negatedClause.isNullExpression() -> false
baseClause.evaluatesTo(receiverExpression) -> true
baseClause.hasFirstReceiverOf(receiverExpression) -> true
baseClause.anyArgumentEvaluatesTo(receiverExpression) -> true
receiverExpression is KtThisExpression -> getImplicitReceiver()?.let { it.type == receiverExpression.getType(context) } == true
else -> false
}
fun fixTextFor(element: KtIfExpression): String {
val ifThenToSelectData = element.buildSelectTransformationData()
return if (ifThenToSelectData?.baseClauseEvaluatesToReceiver() == true) {
if (ifThenToSelectData.condition is KtIsExpression) {
"Replace 'if' expression with safe cast expression"
} else {
"Remove redundant 'if' expression"
}
} else {
"Replace 'if' expression with safe access expression"
}
}
fun convert(ifExpression: KtIfExpression, editor: Editor?) {
val ifThenToSelectData = ifExpression.buildSelectTransformationData() ?: return
val factory = KtPsiFactory(ifExpression)
val resultExpr = runWriteAction {
val replacedBaseClause = ifThenToSelectData.replacedBaseClause(factory)
val newExpr = ifExpression.replaced(replacedBaseClause)
KtPsiUtil.deparenthesize(newExpr)
}
if (editor != null && resultExpr is KtSafeQualifiedExpression) {
resultExpr.inlineReceiverIfApplicableWithPrompt(editor)
resultExpr.renameLetParameter(editor)
}
}
fun isApplicableTo(element: KtIfExpression, expressionShouldBeStable: Boolean): Boolean {
val ifThenToSelectData = element.buildSelectTransformationData() ?: return false
if (expressionShouldBeStable && !ifThenToSelectData.receiverExpression.isStableSimpleExpression(ifThenToSelectData.context)) return false
return ifThenToSelectData.clausesReplaceableBySafeCall()
}
internal fun KtSafeQualifiedExpression.renameLetParameter(editor: Editor) {
val callExpression = selectorExpression as? KtCallExpression ?: return
if (callExpression.calleeExpression?.text != "let") return
@@ -42,67 +42,70 @@ class IfThenToElvisInspection : IntentionBasedInspection<KtIfExpression>(
if (element.shouldBeTransformed()) super.problemHighlightType(element) else ProblemHighlightType.INFORMATION
}
class IfThenToElvisIntention (private val stableElementNeeded: Boolean) : SelfTargetingOffsetIndependentIntention<KtIfExpression>(
KtIfExpression::class.java,
"Replace 'if' expression with elvis expression"
) {
class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpression>(KtIfExpression::class.java, intentionText) {
constructor() : this(stableElementNeeded = true)
private fun IfThenToSelectData.clausesReplaceableByElvis(): Boolean =
when {
baseClause == null || negatedClause == null || negatedClause.isNullOrBlockExpression() ->
false
negatedClause is KtThrowExpression && negatedClause.throwsNullPointerExceptionWithNoArguments() ->
false
baseClause.evaluatesTo(receiverExpression) ->
true
baseClause.anyArgumentEvaluatesTo(receiverExpression) ->
true
hasImplicitReceiverReplaceableBySafeCall() || baseClause.hasFirstReceiverOf(receiverExpression) ->
!baseClause.hasNullableType(context)
else ->
false
}
override fun isApplicableTo(element: KtIfExpression): Boolean {
val ifThenToSelectData = element.buildSelectTransformationData() ?: return false
if (stableElementNeeded && !ifThenToSelectData.receiverExpression.isStableSimpleExpression(ifThenToSelectData.context)) return false
val type = element.getType(ifThenToSelectData.context) ?: return false
if (KotlinBuiltIns.isUnit(type)) return false
return ifThenToSelectData.clausesReplaceableByElvis()
}
private fun KtExpression.isNullOrBlockExpression(): Boolean {
val innerExpression = this.unwrapBlockOrParenthesis()
return innerExpression is KtBlockExpression || innerExpression.node.elementType == KtNodeTypes.NULL
}
override fun isApplicableTo(element: KtIfExpression): Boolean = isApplicableTo(element, expressionShouldBeStable = true)
override fun startInWriteAction() = false
override fun applyTo(element: KtIfExpression, editor: Editor?) {
val ifThenToSelectData = element.buildSelectTransformationData() ?: return
override fun applyTo(element: KtIfExpression, editor: Editor?) = convert(element, editor)
val factory = KtPsiFactory(element)
val elvis = runWriteAction {
val replacedBaseClause = ifThenToSelectData.replacedBaseClause(factory)
val newExpr = element.replaced(
factory.createExpressionByPattern(
"$0 ?: $1",
replacedBaseClause,
ifThenToSelectData.negatedClause!!
)
)
KtPsiUtil.deparenthesize(newExpr) as KtBinaryExpression
companion object {
private fun KtExpression.isNullOrBlockExpression(): Boolean {
val innerExpression = this.unwrapBlockOrParenthesis()
return innerExpression is KtBlockExpression || innerExpression.node.elementType == KtNodeTypes.NULL
}
if (editor != null) {
elvis.inlineLeftSideIfApplicableWithPrompt(editor)
with(IfThenToSafeAccessInspection) {
(elvis.left as? KtSafeQualifiedExpression)?.renameLetParameter(editor)
private fun IfThenToSelectData.clausesReplaceableByElvis(): Boolean =
when {
baseClause == null || negatedClause == null || negatedClause.isNullOrBlockExpression() ->
false
negatedClause is KtThrowExpression && negatedClause.throwsNullPointerExceptionWithNoArguments() ->
false
baseClause.evaluatesTo(receiverExpression) ->
true
baseClause.anyArgumentEvaluatesTo(receiverExpression) ->
true
hasImplicitReceiverReplaceableBySafeCall() || baseClause.hasFirstReceiverOf(receiverExpression) ->
!baseClause.hasNullableType(context)
else ->
false
}
val intentionText = "Replace 'if' expression with elvis expression"
fun convert(element: KtIfExpression, editor: Editor?) {
val ifThenToSelectData = element.buildSelectTransformationData() ?: return
val factory = KtPsiFactory(element)
val elvis = runWriteAction {
val replacedBaseClause = ifThenToSelectData.replacedBaseClause(factory)
val newExpr = element.replaced(
factory.createExpressionByPattern(
"$0 ?: $1",
replacedBaseClause,
ifThenToSelectData.negatedClause!!
)
)
KtPsiUtil.deparenthesize(newExpr) as KtBinaryExpression
}
if (editor != null) {
elvis.inlineLeftSideIfApplicableWithPrompt(editor)
with(IfThenToSafeAccessInspection) {
(elvis.left as? KtSafeQualifiedExpression)?.renameLetParameter(editor)
}
}
}
fun isApplicableTo(element: KtIfExpression, expressionShouldBeStable: Boolean): Boolean {
val ifThenToSelectData = element.buildSelectTransformationData() ?: return false
if (expressionShouldBeStable && !ifThenToSelectData.receiverExpression.isStableSimpleExpression(ifThenToSelectData.context)) return false
val type = element.getType(ifThenToSelectData.context) ?: return false
if (KotlinBuiltIns.isUnit(type)) return false
return ifThenToSelectData.clausesReplaceableByElvis()
}
}
}
@@ -23,21 +23,18 @@ object SmartCastImpossibleInIfThenFactory : KotlinIntentionActionsFactory() {
val ifExpression =
element.getStrictParentOfType<KtContainerNodeForControlStructureBody>()?.parent as? KtIfExpression ?: return emptyList()
val ifThenToSafeAccess = IfThenToSafeAccessInspection(stableElementNeeded = false)
val ifThenToElvis = IfThenToElvisIntention(stableElementNeeded = false)
return listOf(
createQuickFix(
ifExpression,
{ ifThenToSafeAccess.fixText(it) },
{ ifThenToSafeAccess.isApplicable(it) },
{ ifExpr, project, editor -> ifThenToSafeAccess.applyTo(ifExpr.ifKeyword, project, editor) }
{ IfThenToSafeAccessInspection.fixTextFor(it) },
{ IfThenToSafeAccessInspection.isApplicableTo(it, expressionShouldBeStable = false) },
{ ifExpr, _, editor -> IfThenToSafeAccessInspection.convert(ifExpr, editor) }
),
createQuickFix(
ifExpression,
{ ifThenToElvis.text },
{ ifThenToElvis.isApplicableTo(it) },
{ ifExpr, _, editor -> ifThenToElvis.applyTo(ifExpr, editor) }
{ IfThenToElvisIntention.intentionText },
{ IfThenToElvisIntention.isApplicableTo(it, expressionShouldBeStable = false) },
{ ifExpr, _, editor -> IfThenToElvisIntention.convert(ifExpr, editor) }
)
)
}
@@ -49,7 +46,9 @@ object SmartCastImpossibleInIfThenFactory : KotlinIntentionActionsFactory() {
applyTo: (KtIfExpression, project: Project, editor: Editor?) -> Unit
): KotlinQuickFixAction<KtIfExpression> {
return object : KotlinQuickFixAction<KtIfExpression>(ifExpression) {
override fun getText() = fixText(ifExpression)
private val text = fixText(ifExpression)
override fun getText() = text
override fun getFamilyName() = text