Lift assignment: suggest for assignment of null

#KT-30191 Fixed
 #KT-28595 Fixed
This commit is contained in:
Toshiaki Kameyama
2019-06-04 11:56:58 +09:00
committed by Dmitry Gridin
parent 4b4be07942
commit 05a132357b
12 changed files with 166 additions and 10 deletions
@@ -28,8 +28,11 @@ import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.lastBlockStatementOrThis
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeConstructor
import org.jetbrains.kotlin.types.typeUtil.isNothing
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
object BranchedFoldingUtils {
private fun getFoldableBranchedAssignment(branch: KtExpression?): KtBinaryExpression? {
@@ -56,19 +59,25 @@ object BranchedFoldingUtils {
it.getTargetLabel() == null
}
private fun KtBinaryExpression.checkAssignmentsMatch(other: KtBinaryExpression, rightTypeConstructor: TypeConstructor): Boolean {
return left?.text == other.left?.text
&& operationToken == other.operationToken
&& rightTypeConstructor == other.rightTypeConstructor()
private fun KtBinaryExpression.checkAssignmentsMatch(
other: KtBinaryExpression,
leftType: KotlinType,
rightTypeConstructor: TypeConstructor
): Boolean {
val left = this.left ?: return false
val otherLeft = other.left ?: return false
if (left.text != otherLeft.text || operationToken != other.operationToken) return false
val rightType = other.rightType() ?: return false
return rightType.constructor == rightTypeConstructor || (operationToken == KtTokens.EQ && rightType.isSubtypeOf(leftType))
}
private fun KtBinaryExpression.rightTypeConstructor(): TypeConstructor? {
private fun KtBinaryExpression.rightType(): KotlinType? {
val right = this.right ?: return null
val context = this.analyze()
val diagnostics = context.diagnostics
fun hasTypeMismatchError(e: KtExpression) = diagnostics.forElement(e).any { it.factory == Errors.TYPE_MISMATCH }
if (hasTypeMismatchError(this) || hasTypeMismatchError(right)) return null
return right.getType(context)?.constructor
return right.getType(context)
}
internal fun getFoldableAssignmentNumber(expression: KtExpression?): Int {
@@ -104,16 +113,17 @@ object BranchedFoldingUtils {
else -> false
}
if (!collectAssignmentsAndCheck(expression)) return -1
val firstAssignment = assignments.firstOrNull() ?: return 0
val rightTypeConstructor = firstAssignment.rightTypeConstructor() ?: return -1
if (assignments.any { !firstAssignment.checkAssignmentsMatch(it, rightTypeConstructor) }) {
val firstAssignment = assignments.firstOrNull { !it.right.isNullExpression() } ?: assignments.firstOrNull() ?: return 0
val leftType = firstAssignment.left?.let { it.getType(it.analyze(BodyResolveMode.PARTIAL)) } ?: return 0
val rightTypeConstructor = firstAssignment.rightType()?.constructor ?: return -1
if (assignments.any { !firstAssignment.checkAssignmentsMatch(it, leftType, rightTypeConstructor) }) {
return -1
}
if (expression.anyDescendantOfType<KtBinaryExpression>(
predicate = {
if (it.operationToken in KtTokens.ALL_ASSIGNMENTS)
if (it.getNonStrictParentOfType<KtFinallySection>() != null)
firstAssignment.checkAssignmentsMatch(it, rightTypeConstructor)
firstAssignment.checkAssignmentsMatch(it, leftType, rightTypeConstructor)
else
it !in assignments
else
@@ -0,0 +1,11 @@
fun bar(i: Int) {
var str: String? = null
<caret>if (i == 1) {
str = null
} else if (i == 2) {
str = "2"
} else {
str = "3"
}
}
@@ -0,0 +1,11 @@
fun bar(i: Int) {
var str: String? = null
str = if (i == 1) {
null
} else if (i == 2) {
"2"
} else {
"3"
}
}
@@ -0,0 +1,11 @@
fun bar(i: Int) {
var str: String? = null
<caret>if (i == 1) {
str = "1"
} else if (i == 2) {
str = "2"
} else {
str = null
}
}
@@ -0,0 +1,11 @@
fun bar(i: Int) {
var str: String? = null
str = if (i == 1) {
"1"
} else if (i == 2) {
"2"
} else {
null
}
}
@@ -0,0 +1,11 @@
fun bar(i: Int) {
var str: String? = null
<caret>if (i == 1) {
str = null
} else if (i == 2) {
str = null
} else {
str = null
}
}
@@ -0,0 +1,11 @@
fun bar(i: Int) {
var str: String? = null
str = if (i == 1) {
null
} else if (i == 2) {
null
} else {
null
}
}
@@ -0,0 +1,13 @@
// PROBLEM: none
// DISABLE-ERRORS
fun bar(i: Int) {
var str: String? = null
<caret>if (i == 1) {
str = null
} else if (i == 2) {
str = "2"
} else {
str = 3
}
}
@@ -0,0 +1,13 @@
// PROBLEM: none
// DISABLE-ERRORS
fun bar(i: Int) {
var str: String = ""
<caret>if (i == 1) {
str = null
} else if (i == 2) {
str = "2"
} else {
str = "3"
}
}
@@ -0,0 +1,12 @@
open class A
class B : A()
class C : A()
fun liftClass(boolean: Boolean) {
val a1: A
<caret>if (boolean) {
a1 = B()
} else {
a1 = C()
}
}
@@ -0,0 +1,12 @@
open class A
class B : A()
class C : A()
fun liftClass(boolean: Boolean) {
val a1: A
a1 = if (boolean) {
B()
} else {
C()
}
}
@@ -4575,6 +4575,31 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/liftOut/ifToAssignment/elseIfOnly.kt");
}
@TestMetadata("hasNull.kt")
public void testHasNull() throws Exception {
runTest("idea/testData/inspectionsLocal/liftOut/ifToAssignment/hasNull.kt");
}
@TestMetadata("hasNull2.kt")
public void testHasNull2() throws Exception {
runTest("idea/testData/inspectionsLocal/liftOut/ifToAssignment/hasNull2.kt");
}
@TestMetadata("hasNull3.kt")
public void testHasNull3() throws Exception {
runTest("idea/testData/inspectionsLocal/liftOut/ifToAssignment/hasNull3.kt");
}
@TestMetadata("hasNull4.kt")
public void testHasNull4() throws Exception {
runTest("idea/testData/inspectionsLocal/liftOut/ifToAssignment/hasNull4.kt");
}
@TestMetadata("hasNull5.kt")
public void testHasNull5() throws Exception {
runTest("idea/testData/inspectionsLocal/liftOut/ifToAssignment/hasNull5.kt");
}
@TestMetadata("ifElseIf.kt")
public void testIfElseIf() throws Exception {
runTest("idea/testData/inspectionsLocal/liftOut/ifToAssignment/ifElseIf.kt");
@@ -4655,6 +4680,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/liftOut/ifToAssignment/simpleIfWithoutTerminatingAssignment.kt");
}
@TestMetadata("subTypes.kt")
public void testSubTypes() throws Exception {
runTest("idea/testData/inspectionsLocal/liftOut/ifToAssignment/subTypes.kt");
}
@TestMetadata("typeMismatch.kt")
public void testTypeMismatch() throws Exception {
runTest("idea/testData/inspectionsLocal/liftOut/ifToAssignment/typeMismatch.kt");