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.bindingContextUtil.isUsedAsExpression
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
|
||||||
|
|
||||||
class IfThenToSafeAccessInspection(
|
class IfThenToSafeAccessInspection : AbstractApplicabilityBasedInspection<KtIfExpression>(KtIfExpression::class.java) {
|
||||||
private val stableElementNeeded: Boolean
|
|
||||||
) : AbstractApplicabilityBasedInspection<KtIfExpression>(KtIfExpression::class.java) {
|
|
||||||
|
|
||||||
constructor() : this(stableElementNeeded = true)
|
override fun isApplicable(element: KtIfExpression): Boolean = isApplicableTo(element, expressionShouldBeStable = 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 inspectionTarget(element: KtIfExpression) = element.ifKeyword
|
override fun inspectionTarget(element: KtIfExpression) = element.ifKeyword
|
||||||
|
|
||||||
@@ -54,50 +45,64 @@ class IfThenToSafeAccessInspection(
|
|||||||
|
|
||||||
override val defaultFixText = "Simplify foldable if-then"
|
override val defaultFixText = "Simplify foldable if-then"
|
||||||
|
|
||||||
override fun fixText(element: KtIfExpression): String {
|
override fun fixText(element: KtIfExpression): String = fixTextFor(element)
|
||||||
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 val startFixInWriteAction = false
|
override val startFixInWriteAction = false
|
||||||
|
|
||||||
override fun applyTo(element: PsiElement, project: Project, editor: Editor?) {
|
override fun applyTo(element: PsiElement, project: Project, editor: Editor?) {
|
||||||
val ifExpression = element.getParentOfType<KtIfExpression>(true) ?: return
|
val ifExpression = element.getParentOfType<KtIfExpression>(true) ?: return
|
||||||
val ifThenToSelectData = ifExpression.buildSelectTransformationData() ?: return
|
convert(ifExpression, editor)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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) {
|
internal fun KtSafeQualifiedExpression.renameLetParameter(editor: Editor) {
|
||||||
val callExpression = selectorExpression as? KtCallExpression ?: return
|
val callExpression = selectorExpression as? KtCallExpression ?: return
|
||||||
if (callExpression.calleeExpression?.text != "let") 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
|
if (element.shouldBeTransformed()) super.problemHighlightType(element) else ProblemHighlightType.INFORMATION
|
||||||
}
|
}
|
||||||
|
|
||||||
class IfThenToElvisIntention (private val stableElementNeeded: Boolean) : SelfTargetingOffsetIndependentIntention<KtIfExpression>(
|
class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpression>(KtIfExpression::class.java, intentionText) {
|
||||||
KtIfExpression::class.java,
|
|
||||||
"Replace 'if' expression with elvis expression"
|
|
||||||
) {
|
|
||||||
|
|
||||||
constructor() : this(stableElementNeeded = true)
|
override fun isApplicableTo(element: KtIfExpression): Boolean = isApplicableTo(element, expressionShouldBeStable = 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 startInWriteAction() = false
|
override fun startInWriteAction() = false
|
||||||
|
|
||||||
override fun applyTo(element: KtIfExpression, editor: Editor?) {
|
override fun applyTo(element: KtIfExpression, editor: Editor?) = convert(element, editor)
|
||||||
val ifThenToSelectData = element.buildSelectTransformationData() ?: return
|
|
||||||
|
|
||||||
val factory = KtPsiFactory(element)
|
companion object {
|
||||||
val elvis = runWriteAction {
|
private fun KtExpression.isNullOrBlockExpression(): Boolean {
|
||||||
val replacedBaseClause = ifThenToSelectData.replacedBaseClause(factory)
|
val innerExpression = this.unwrapBlockOrParenthesis()
|
||||||
val newExpr = element.replaced(
|
return innerExpression is KtBlockExpression || innerExpression.node.elementType == KtNodeTypes.NULL
|
||||||
factory.createExpressionByPattern(
|
|
||||||
"$0 ?: $1",
|
|
||||||
replacedBaseClause,
|
|
||||||
ifThenToSelectData.negatedClause!!
|
|
||||||
)
|
|
||||||
)
|
|
||||||
KtPsiUtil.deparenthesize(newExpr) as KtBinaryExpression
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editor != null) {
|
private fun IfThenToSelectData.clausesReplaceableByElvis(): Boolean =
|
||||||
elvis.inlineLeftSideIfApplicableWithPrompt(editor)
|
when {
|
||||||
with(IfThenToSafeAccessInspection) {
|
baseClause == null || negatedClause == null || negatedClause.isNullOrBlockExpression() ->
|
||||||
(elvis.left as? KtSafeQualifiedExpression)?.renameLetParameter(editor)
|
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 =
|
val ifExpression =
|
||||||
element.getStrictParentOfType<KtContainerNodeForControlStructureBody>()?.parent as? KtIfExpression ?: return emptyList()
|
element.getStrictParentOfType<KtContainerNodeForControlStructureBody>()?.parent as? KtIfExpression ?: return emptyList()
|
||||||
|
|
||||||
val ifThenToSafeAccess = IfThenToSafeAccessInspection(stableElementNeeded = false)
|
|
||||||
val ifThenToElvis = IfThenToElvisIntention(stableElementNeeded = false)
|
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
createQuickFix(
|
createQuickFix(
|
||||||
ifExpression,
|
ifExpression,
|
||||||
{ ifThenToSafeAccess.fixText(it) },
|
{ IfThenToSafeAccessInspection.fixTextFor(it) },
|
||||||
{ ifThenToSafeAccess.isApplicable(it) },
|
{ IfThenToSafeAccessInspection.isApplicableTo(it, expressionShouldBeStable = false) },
|
||||||
{ ifExpr, project, editor -> ifThenToSafeAccess.applyTo(ifExpr.ifKeyword, project, editor) }
|
{ ifExpr, _, editor -> IfThenToSafeAccessInspection.convert(ifExpr, editor) }
|
||||||
),
|
),
|
||||||
createQuickFix(
|
createQuickFix(
|
||||||
ifExpression,
|
ifExpression,
|
||||||
{ ifThenToElvis.text },
|
{ IfThenToElvisIntention.intentionText },
|
||||||
{ ifThenToElvis.isApplicableTo(it) },
|
{ IfThenToElvisIntention.isApplicableTo(it, expressionShouldBeStable = false) },
|
||||||
{ ifExpr, _, editor -> ifThenToElvis.applyTo(ifExpr, editor) }
|
{ ifExpr, _, editor -> IfThenToElvisIntention.convert(ifExpr, editor) }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -49,7 +46,9 @@ object SmartCastImpossibleInIfThenFactory : KotlinIntentionActionsFactory() {
|
|||||||
applyTo: (KtIfExpression, project: Project, editor: Editor?) -> Unit
|
applyTo: (KtIfExpression, project: Project, editor: Editor?) -> Unit
|
||||||
): KotlinQuickFixAction<KtIfExpression> {
|
): KotlinQuickFixAction<KtIfExpression> {
|
||||||
return object : KotlinQuickFixAction<KtIfExpression>(ifExpression) {
|
return object : KotlinQuickFixAction<KtIfExpression>(ifExpression) {
|
||||||
override fun getText() = fixText(ifExpression)
|
private val text = fixText(ifExpression)
|
||||||
|
|
||||||
|
override fun getText() = text
|
||||||
|
|
||||||
override fun getFamilyName() = text
|
override fun getFamilyName() = text
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user