Add quick fixes for SMARTCAST_IMPOSSIBLE in 'if' #KT-27184 Fixed

This commit is contained in:
Toshiaki Kameyama
2018-11-28 06:13:30 +03:00
committed by Mikhail Glukhikh
parent a0162adbf9
commit cf3215b96e
28 changed files with 392 additions and 4 deletions
@@ -32,11 +32,15 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
class IfThenToSafeAccessInspection : AbstractApplicabilityBasedInspection<KtIfExpression>(KtIfExpression::class.java) {
class IfThenToSafeAccessInspection(
private val stableElementNeeded: Boolean
) : AbstractApplicabilityBasedInspection<KtIfExpression>(KtIfExpression::class.java) {
constructor() : this(stableElementNeeded = true)
override fun isApplicable(element: KtIfExpression): Boolean {
val ifThenToSelectData = element.buildSelectTransformationData() ?: return false
if (!ifThenToSelectData.receiverExpression.isStableSimpleExpression(ifThenToSelectData.context)) return false
if (stableElementNeeded && !ifThenToSelectData.receiverExpression.isStableSimpleExpression(ifThenToSelectData.context)) return false
return ifThenToSelectData.clausesReplaceableBySafeCall()
}
@@ -42,11 +42,13 @@ class IfThenToElvisInspection : IntentionBasedInspection<KtIfExpression>(
if (element.shouldBeTransformed()) super.problemHighlightType(element) else ProblemHighlightType.INFORMATION
}
class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpression>(
class IfThenToElvisIntention (private val stableElementNeeded: Boolean) : SelfTargetingOffsetIndependentIntention<KtIfExpression>(
KtIfExpression::class.java,
"Replace 'if' expression with elvis expression"
) {
constructor() : this(stableElementNeeded = true)
private fun IfThenToSelectData.clausesReplaceableByElvis(): Boolean =
when {
baseClause == null || negatedClause == null || negatedClause.isNullOrBlockExpression() ->
@@ -65,7 +67,7 @@ class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention<KtIfExpre
override fun isApplicableTo(element: KtIfExpression): Boolean {
val ifThenToSelectData = element.buildSelectTransformationData() ?: return false
if (!ifThenToSelectData.receiverExpression.isStableSimpleExpression(ifThenToSelectData.context)) 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
@@ -370,6 +370,7 @@ class QuickFixRegistrar : QuickFixContributor {
SMARTCAST_IMPOSSIBLE.registerFactory(SmartCastImpossibleExclExclFixFactory)
SMARTCAST_IMPOSSIBLE.registerFactory(CastExpressionFix.SmartCastImpossibleFactory)
SMARTCAST_IMPOSSIBLE.registerFactory(SmartCastImpossibleInIfThenFactory)
PLATFORM_CLASS_MAPPED_TO_KOTLIN.registerFactory(MapPlatformClassToKotlinFix)
@@ -0,0 +1,63 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.inspections.branchedTransformations.IfThenToSafeAccessInspection
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.IfThenToElvisIntention
import org.jetbrains.kotlin.psi.KtContainerNodeForControlStructureBody
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
object SmartCastImpossibleInIfThenFactory : KotlinIntentionActionsFactory() {
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
val element = diagnostic.psiElement as? KtNameReferenceExpression ?: return emptyList()
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) }
),
createQuickFix(
ifExpression,
{ ifThenToElvis.text },
{ ifThenToElvis.isApplicableTo(it) },
{ ifExpr, _, editor -> ifThenToElvis.applyTo(ifExpr, editor) }
)
)
}
private fun createQuickFix(
ifExpression: KtIfExpression,
fixText: (KtIfExpression) -> String,
isApplicable: (KtIfExpression) -> Boolean,
applyTo: (KtIfExpression, project: Project, editor: Editor?) -> Unit
): KotlinQuickFixAction<KtIfExpression> {
return object : KotlinQuickFixAction<KtIfExpression>(ifExpression) {
override fun getText() = fixText(ifExpression)
override fun getFamilyName() = text
override fun isAvailable(project: Project, editor: Editor?, file: KtFile) = element?.let { isApplicable(it) } ?: false
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
element?.also { applyTo(it, project, editor) }
}
}
}
}
@@ -0,0 +1,8 @@
// "Replace 'if' expression with safe access expression" "true"
class Test {
var x: String? = ""
fun test() {
if (x != null) <caret>x.length
}
}
@@ -0,0 +1,8 @@
// "Replace 'if' expression with safe access expression" "true"
class Test {
var x: String? = ""
fun test() {
x?.length
}
}
@@ -0,0 +1,10 @@
// "Replace 'if' expression with safe access expression" "true"
class Test {
var x: String? = ""
fun test() {
if (x != null) {
<caret>x.length
}
}
}
@@ -0,0 +1,8 @@
// "Replace 'if' expression with safe access expression" "true"
class Test {
var x: String? = ""
fun test() {
x?.length
}
}
@@ -0,0 +1,11 @@
// "Replace 'if' expression with safe access expression" "true"
// WITH_RUNTIME
class Test {
var x: String? = ""
fun test() {
if (x != null) foo(<caret>x)
}
fun foo(s: String) = 1
}
@@ -0,0 +1,11 @@
// "Replace 'if' expression with safe access expression" "true"
// WITH_RUNTIME
class Test {
var x: String? = ""
fun test() {
x?.let { foo(it) }
}
fun foo(s: String) = 1
}
@@ -0,0 +1,8 @@
// "Replace 'if' expression with safe access expression" "true"
class Test {
var x: Any? = null
fun test() {
if (x is String) <caret>x.length
}
}
@@ -0,0 +1,8 @@
// "Replace 'if' expression with safe access expression" "true"
class Test {
var x: Any? = null
fun test() {
(x as? String)?.length
}
}
@@ -0,0 +1,11 @@
// "Replace 'if' expression with safe access expression" "true"
// WITH_RUNTIME
class Test {
var x: Any? = null
fun test() {
if (x is String) foo(x<caret>)
}
fun foo(s: String) = 1
}
@@ -0,0 +1,11 @@
// "Replace 'if' expression with safe access expression" "true"
// WITH_RUNTIME
class Test {
var x: Any? = null
fun test() {
(x as? String)?.let { foo(it) }
}
fun foo(s: String) = 1
}
@@ -0,0 +1,8 @@
// "Replace 'if' expression with elvis expression" "true"
class Test {
var x: String? = ""
fun test() {
val i = if (x != null) <caret>x.length else 0
}
}
@@ -0,0 +1,8 @@
// "Replace 'if' expression with elvis expression" "true"
class Test {
var x: String? = ""
fun test() {
val i = x?.length ?: 0
}
}
@@ -0,0 +1,12 @@
// "Replace 'if' expression with elvis expression" "true"
class Test {
var x: String? = ""
fun test() {
val i = if (x != null) {
<caret>x.length
} else {
0
}
}
}
@@ -0,0 +1,8 @@
// "Replace 'if' expression with elvis expression" "true"
class Test {
var x: String? = ""
fun test() {
val i = x?.length ?: 0
}
}
@@ -0,0 +1,13 @@
// "Replace 'if' expression with elvis expression" "true"
// WITH_RUNTIME
class Test {
var x: String? = ""
fun test() {
val i = if (x != null) foo(<caret>x) else bar()
}
fun foo(s: String) = 1
fun bar() = 0
}
@@ -0,0 +1,13 @@
// "Replace 'if' expression with elvis expression" "true"
// WITH_RUNTIME
class Test {
var x: String? = ""
fun test() {
val i = x?.let { foo(it) } ?: bar()
}
fun foo(s: String) = 1
fun bar() = 0
}
@@ -0,0 +1,8 @@
// "Replace 'if' expression with elvis expression" "true"
class Test {
var x: Any? = null
fun test() {
val i = if (x is String) <caret>x.length else 0
}
}
@@ -0,0 +1,8 @@
// "Replace 'if' expression with elvis expression" "true"
class Test {
var x: Any? = null
fun test() {
val i = (x as? String)?.length ?: 0
}
}
@@ -0,0 +1,13 @@
// "Replace 'if' expression with elvis expression" "true"
// WITH_RUNTIME
class Test {
var x: Any? = null
fun test() {
val i = if (x is String) foo(<caret>x) else bar()
}
fun foo(s: String) = 1
fun bar() = 0
}
@@ -0,0 +1,13 @@
// "Replace 'if' expression with elvis expression" "true"
// WITH_RUNTIME
class Test {
var x: Any? = null
fun test() {
val i = (x as? String)?.let { foo(it) } ?: bar()
}
fun foo(s: String) = 1
fun bar() = 0
}
@@ -0,0 +1,18 @@
// "Replace 'if' expression with elvis expression" "false"
// ACTION: Add non-null asserted (!!) call
// ACTION: Introduce local variable
// DISABLE-ERRORS
class Test {
var x: String? = ""
fun test() {
val i = if (x != null) {
bar()
<caret>x.length
} else {
0
}
}
fun bar() {}
}
@@ -0,0 +1,16 @@
// "Replace 'if' expression with safe access expression" "false"
// ACTION: Add non-null asserted (!!) call
// ACTION: Introduce local variable
// DISABLE-ERRORS
class Test {
var x: String? = ""
fun test() {
if (x != null) {
bar()
<caret>x.length
}
}
fun bar() {}
}
@@ -3751,6 +3751,19 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
}
}
@TestMetadata("idea/testData/quickfix/smartCastImpossibleInIfThen")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class SmartCastImpossibleInIfThen extends AbstractQuickFixMultiFileTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTestWithExtraFile, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInSmartCastImpossibleInIfThen() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/smartCastImpossibleInIfThen"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
}
}
@TestMetadata("idea/testData/quickfix/specifyOverrideExplicitly")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -10311,6 +10311,79 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
}
}
@TestMetadata("idea/testData/quickfix/smartCastImpossibleInIfThen")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class SmartCastImpossibleInIfThen extends AbstractQuickFixTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInSmartCastImpossibleInIfThen() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/smartCastImpossibleInIfThen"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("ifThen.kt")
public void testIfThen() throws Exception {
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThen.kt");
}
@TestMetadata("ifThen2.kt")
public void testIfThen2() throws Exception {
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThen2.kt");
}
@TestMetadata("ifThen3.kt")
public void testIfThen3() throws Exception {
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThen3.kt");
}
@TestMetadata("ifThen4.kt")
public void testIfThen4() throws Exception {
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThen4.kt");
}
@TestMetadata("ifThen5.kt")
public void testIfThen5() throws Exception {
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThen5.kt");
}
@TestMetadata("ifThenElse.kt")
public void testIfThenElse() throws Exception {
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThenElse.kt");
}
@TestMetadata("ifThenElse2.kt")
public void testIfThenElse2() throws Exception {
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThenElse2.kt");
}
@TestMetadata("ifThenElse3.kt")
public void testIfThenElse3() throws Exception {
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThenElse3.kt");
}
@TestMetadata("ifThenElse4.kt")
public void testIfThenElse4() throws Exception {
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThenElse4.kt");
}
@TestMetadata("ifThenElse5.kt")
public void testIfThenElse5() throws Exception {
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThenElse5.kt");
}
@TestMetadata("ifThenElseMultiStatement.kt")
public void testIfThenElseMultiStatement() throws Exception {
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThenElseMultiStatement.kt");
}
@TestMetadata("ifThenMultiStatement.kt")
public void testIfThenMultiStatement() throws Exception {
runTest("idea/testData/quickfix/smartCastImpossibleInIfThen/ifThenMultiStatement.kt");
}
}
@TestMetadata("idea/testData/quickfix/specifyOverrideExplicitly")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)