Quick-fixes for SMARTCAST_IMPOSSIBLE: refactor
Enhancement for KT-27184
This commit is contained in:
+52
-47
@@ -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
|
||||
|
||||
+56
-53
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user