FIR/FIR IDE: Use entire FirVariableAssignment when reporting UNSAFE_CALL

(e.g., `nullable.a = b`), and use positioning strategies to locate the
dot in the LHS expression.

Without it, only the callee reference is reported on, which makes the
highlighting of the error and application of quickfixes incorrect in the
IDE.

Also fixed issue with annotated and/or labeled expressions on LHS of
assignment (e.g., `(@Ann label@ i) = 34`).
This commit is contained in:
Mark Punzalan
2021-05-13 09:24:29 +00:00
committed by Ilya Kirillov
parent 1d9247ae0f
commit d2b8204fdc
28 changed files with 342 additions and 92 deletions
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol
import org.jetbrains.kotlin.idea.quickfix.AddExclExclCallFix
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.unwrapParenthesesLabelsAndAnnotations
import org.jetbrains.kotlin.util.OperatorNameConventions
object AddExclExclCallFixFactories {
@@ -29,46 +30,51 @@ object AddExclExclCallFixFactories {
}
private fun KtAnalysisSession.getFixForUnsafeCall(psi: PsiElement): List<IntentionAction> {
val (target, hasImplicitReceiver) = when (psi) {
val (target, hasImplicitReceiver) = when (val unwrapped = psi.unwrapParenthesesLabelsAndAnnotations()) {
// `foo.bar` -> `foo!!.bar`
is KtDotQualifiedExpression -> psi.receiverExpression to false
is KtDotQualifiedExpression -> unwrapped.receiverExpression to false
// `foo[bar]` -> `foo!![bar]`
is KtArrayAccessExpression -> psi.arrayExpression to false
is KtArrayAccessExpression -> unwrapped.arrayExpression to false
is KtCallableReferenceExpression -> psi.lhs.let { lhs ->
is KtCallableReferenceExpression -> unwrapped.lhs.let { lhs ->
if (lhs != null) {
// `foo::bar` -> `foo!!::bar`
lhs to false
} else {
// `::bar -> this!!::bar`
psi to true
unwrapped to true
}
}
// `bar` -> `this!!.bar`
is KtNameReferenceExpression -> psi to true
is KtNameReferenceExpression -> unwrapped to true
// `bar()` -> `this!!.bar()`
is KtCallExpression -> psi to true
is KtCallExpression -> unwrapped to true
// `-foo` -> `-foo!!`
// NOTE: Unsafe unary operator call is reported as UNSAFE_CALL, _not_ UNSAFE_OPERATOR_CALL
is KtUnaryExpression -> psi.baseExpression to false
is KtUnaryExpression -> unwrapped.baseExpression to false
is KtBinaryExpression -> {
val receiver = if (KtPsiUtil.isInOrNotInOperation(psi)) {
// `bar in foo` -> `bar in foo!!`
psi.right
} else {
// `foo + bar` -> `foo!! + bar` OR `foo infixFun bar` -> `foo!! infixFun bar`
psi.left
val receiver = when {
KtPsiUtil.isInOrNotInOperation(unwrapped) ->
// `bar in foo` -> `bar in foo!!`
unwrapped.right
KtPsiUtil.isAssignment(unwrapped) ->
// UNSAFE_CALL for assignments (e.g., `foo.bar = value`) is reported on the entire statement (KtBinaryExpression).
// The unsafe call is on the LHS of the assignment.
return getFixForUnsafeCall(unwrapped.left ?: return emptyList())
else ->
// `foo + bar` -> `foo!! + bar` OR `foo infixFun bar` -> `foo!! infixFun bar`
unwrapped.left
}
receiver to false
}
// UNSAFE_INFIX_CALL/UNSAFE_OPERATOR_CALL on KtBinaryExpression is reported on the child KtOperationReferenceExpression
is KtOperationReferenceExpression -> return getFixForUnsafeCall(psi.parent)
is KtOperationReferenceExpression -> return getFixForUnsafeCall(unwrapped.parent)
else -> return emptyList()
}
@@ -11,9 +11,9 @@ import org.jetbrains.kotlin.idea.frontend.api.types.KtTypeNullability
import org.jetbrains.kotlin.idea.frontend.api.types.KtTypeWithNullability
import org.jetbrains.kotlin.idea.quickfix.ReplaceImplicitReceiverCallFix
import org.jetbrains.kotlin.idea.quickfix.ReplaceWithSafeCallFix
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.unwrapParenthesesLabelsAndAnnotations
object ReplaceCallFixFactories {
val unsafeCallFactory =
@@ -25,14 +25,23 @@ object ReplaceCallFixFactories {
return expectedType?.nullability == KtTypeNullability.NON_NULLABLE
}
when (val psi = diagnostic.psi) {
is KtDotQualifiedExpression -> listOf(ReplaceWithSafeCallFix(psi, psi.shouldHaveNotNullType()))
val psi = diagnostic.psi
val target = if (psi is KtBinaryExpression && psi.operationToken in KtTokens.ALL_ASSIGNMENTS) {
// UNSAFE_CALL for assignments (e.g., `foo.bar = value`) is reported on the entire statement (KtBinaryExpression).
// The unsafe call is on the LHS of the assignment.
psi.left
} else {
psi
}.unwrapParenthesesLabelsAndAnnotations()
when (target) {
is KtDotQualifiedExpression -> listOf(ReplaceWithSafeCallFix(target, target.shouldHaveNotNullType()))
is KtNameReferenceExpression -> {
// TODO: As a safety precaution, resolve the expression to determine if it is a call with an implicit receiver.
// This is a defensive check to ensure that the diagnostic was reported on such a call and not some other name reference.
// This isn't strictly needed because FIR checkers aren't reporting on wrong elements, but ReplaceWithSafeCallFixFactory
// in FE1.0 does so.
listOf(ReplaceImplicitReceiverCallFix(psi, psi.shouldHaveNotNullType()))
listOf(ReplaceImplicitReceiverCallFix(target, target.shouldHaveNotNullType()))
}
else -> emptyList()
}
@@ -229,6 +229,16 @@ public class HighLevelQuickFixTestGenerated extends AbstractHighLevelQuickFixTes
runTest("idea/testData/quickfix/addExclExclCall/array4.kt");
}
@TestMetadata("assignment.kt")
public void testAssignment() throws Exception {
runTest("idea/testData/quickfix/addExclExclCall/assignment.kt");
}
@TestMetadata("assignmentToUnsafeCallExpression.kt")
public void testAssignmentToUnsafeCallExpression() throws Exception {
runTest("idea/testData/quickfix/addExclExclCall/assignmentToUnsafeCallExpression.kt");
}
@TestMetadata("functionReference.kt")
public void testFunctionReference() throws Exception {
runTest("idea/testData/quickfix/addExclExclCall/functionReference.kt");
@@ -1271,6 +1281,11 @@ public class HighLevelQuickFixTestGenerated extends AbstractHighLevelQuickFixTes
runTest("idea/testData/quickfix/replaceWithSafeCall/assignmentToPropertyWithNoExplicitType.kt");
}
@TestMetadata("assignmentToUnsafeCallExpression.kt")
public void testAssignmentToUnsafeCallExpression() throws Exception {
runTest("idea/testData/quickfix/replaceWithSafeCall/assignmentToUnsafeCallExpression.kt");
}
@TestMetadata("comment.kt")
public void testComment() throws Exception {
runTest("idea/testData/quickfix/replaceWithSafeCall/comment.kt");
+7
View File
@@ -0,0 +1,7 @@
// "Add non-null asserted (!!) call" "true"
// WITH_RUNTIME
var i = 0
fun foo(s: String?) {
i = s<caret>.length
}
@@ -0,0 +1,7 @@
// "Add non-null asserted (!!) call" "true"
// WITH_RUNTIME
var i = 0
fun foo(s: String?) {
i = s!!.length
}
@@ -0,0 +1,6 @@
// "Add non-null asserted (!!) call" "true"
class A(var s: String)
fun foo(a: A?) {
a<caret>.s = ""
}
@@ -0,0 +1,6 @@
// "Add non-null asserted (!!) call" "true"
class A(var s: String)
fun foo(a: A?) {
a!!.s = ""
}
@@ -0,0 +1,6 @@
// "Replace with safe (?.) call" "true"
class A(var s: String? = null)
fun foo(a: A?) {
a<caret>.s = ""
}
@@ -0,0 +1,6 @@
// "Replace with safe (?.) call" "true"
class A(var s: String? = null)
fun foo(a: A?) {
a?.s = ""
}
@@ -642,6 +642,16 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
runTest("idea/testData/quickfix/addExclExclCall/array4.kt");
}
@TestMetadata("assignment.kt")
public void testAssignment() throws Exception {
runTest("idea/testData/quickfix/addExclExclCall/assignment.kt");
}
@TestMetadata("assignmentToUnsafeCallExpression.kt")
public void testAssignmentToUnsafeCallExpression() throws Exception {
runTest("idea/testData/quickfix/addExclExclCall/assignmentToUnsafeCallExpression.kt");
}
@TestMetadata("functionReference.kt")
public void testFunctionReference() throws Exception {
runTest("idea/testData/quickfix/addExclExclCall/functionReference.kt");
@@ -11825,6 +11835,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
runTest("idea/testData/quickfix/replaceWithSafeCall/assignmentToPropertyWithNoExplicitType.kt");
}
@TestMetadata("assignmentToUnsafeCallExpression.kt")
public void testAssignmentToUnsafeCallExpression() throws Exception {
runTest("idea/testData/quickfix/replaceWithSafeCall/assignmentToUnsafeCallExpression.kt");
}
@TestMetadata("comment.kt")
public void testComment() throws Exception {
runTest("idea/testData/quickfix/replaceWithSafeCall/comment.kt");