Skip to content

Commit 46aaee5

Browse files
ndmglukhikh
authored andcommitted
Fix nullability quick-fixes with implicit receiver #KT-17726 Fixed
1 parent 9d2ae54 commit 46aaee5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+479
-23
lines changed

idea/src/org/jetbrains/kotlin/idea/quickfix/ExclExclCallFixes.kt

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ import org.jetbrains.kotlin.types.TypeUtils
3838
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
3939
import org.jetbrains.kotlin.util.OperatorNameConventions
4040
import org.jetbrains.kotlin.util.isValidOperator
41+
import org.jetbrains.kotlin.psi.KtPsiFactory
42+
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
43+
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getImplicitReceiverValue
44+
4145

4246
abstract class ExclExclCallFix(psiElement: PsiElement) : KotlinQuickFixAction<PsiElement>(psiElement) {
4347
override fun getFamilyName(): String = text
@@ -75,7 +79,9 @@ class RemoveExclExclCallFix(psiElement: PsiElement) : ExclExclCallFix(psiElement
7579
}
7680
}
7781

78-
class AddExclExclCallFix(psiElement: PsiElement) : ExclExclCallFix(psiElement) {
82+
class AddExclExclCallFix(psiElement: PsiElement, val checkImplicitReceivers: Boolean) : ExclExclCallFix(psiElement) {
83+
constructor(psiElement: PsiElement) : this(psiElement, true)
84+
7985
override fun getText() = KotlinBundle.message("introduce.non.null.assertion")
8086

8187
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean
@@ -85,40 +91,50 @@ class AddExclExclCallFix(psiElement: PsiElement) : ExclExclCallFix(psiElement) {
8591
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
8692
if (!FileModificationService.getInstance().prepareFileForWrite(file)) return
8793

88-
val modifiedExpression = getExpressionForIntroduceCall() ?: return
89-
val exclExclExpression = KtPsiFactory(project).createExpressionByPattern("$0!!", modifiedExpression)
94+
val expr = getExpressionForIntroduceCall() ?: return
95+
val modifiedExpression = expr.expression
96+
val exclExclExpression = if (expr.implicitReceiver) {
97+
KtPsiFactory(project).createExpressionByPattern("this!!.$0", modifiedExpression)
98+
} else {
99+
KtPsiFactory(project).createExpressionByPattern("$0!!", modifiedExpression)
100+
}
90101
modifiedExpression.replace(exclExclExpression)
91102
}
92103

93-
private fun getExpressionForIntroduceCall(): KtExpression? {
104+
private class ExpressionForCall(val expression: KtExpression, val implicitReceiver: Boolean)
105+
106+
private fun KtExpression?.expressionForCall(implicitReceiver: Boolean = false) = this?.let { ExpressionForCall(it, implicitReceiver) }
107+
108+
private fun getExpressionForIntroduceCall(): ExpressionForCall? {
94109
val psiElement = element ?: return null
95-
if (psiElement is LeafPsiElement && psiElement.elementType == KtTokens.DOT) {
96-
val sibling = psiElement.prevSibling
97-
if (sibling is KtExpression) {
98-
return sibling
99-
}
110+
return if (psiElement is LeafPsiElement && psiElement.elementType == KtTokens.DOT) {
111+
(psiElement.prevSibling as? KtExpression).expressionForCall()
100112
}
101113
else if (psiElement is KtArrayAccessExpression) {
102-
return psiElement.arrayExpression
114+
psiElement.arrayExpression.expressionForCall()
103115
}
104116
else if (psiElement is KtOperationReferenceExpression) {
105117
val parent = psiElement.parent
106-
return when (parent) {
107-
is KtUnaryExpression -> parent.baseExpression
108-
is KtBinaryExpression -> parent.left
118+
when (parent) {
119+
is KtUnaryExpression -> parent.baseExpression.expressionForCall()
120+
is KtBinaryExpression -> parent.left.expressionForCall()
109121
else -> null
110122
}
111123
}
112124
else if (psiElement is KtExpression) {
113-
return psiElement
125+
if (checkImplicitReceivers && psiElement.getResolvedCall(psiElement.analyze())?.getImplicitReceiverValue() != null) {
126+
val expressionToReplace = psiElement.parent as? KtCallExpression ?: psiElement
127+
expressionToReplace.expressionForCall(implicitReceiver = true)
128+
}
129+
else psiElement.expressionForCall()
130+
}
131+
else {
132+
null
114133
}
115-
116-
return null
117134
}
118135

119136
companion object : KotlinSingleIntentionActionFactory() {
120-
override fun createAction(diagnostic: Diagnostic): IntentionAction
121-
= AddExclExclCallFix(diagnostic.psiElement)
137+
override fun createAction(diagnostic: Diagnostic): IntentionAction = AddExclExclCallFix(diagnostic.psiElement)
122138
}
123139
}
124140

@@ -137,7 +153,7 @@ object SmartCastImpossibleExclExclFixFactory: KotlinSingleIntentionActionFactory
137153
val nullableExpectedType = TypeUtils.makeNullable(expectedType)
138154
if (!type.isSubtypeOf(nullableExpectedType)) return null
139155

140-
return AddExclExclCallFix(element)
156+
return AddExclExclCallFix(element, checkImplicitReceivers = false)
141157
}
142158
}
143159

idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceCallFix.kt

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ import com.intellij.openapi.editor.Editor
2121
import com.intellij.openapi.project.Project
2222
import com.intellij.psi.PsiFile
2323
import org.jetbrains.kotlin.diagnostics.Diagnostic
24+
import org.jetbrains.kotlin.idea.caches.resolve.analyze
2425
import org.jetbrains.kotlin.psi.*
2526
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
27+
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
28+
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getImplicitReceiverValue
2629

2730
abstract class ReplaceCallFix(
2831
expression: KtQualifiedExpression,
@@ -44,14 +47,36 @@ abstract class ReplaceCallFix(
4447
}
4548
}
4649

50+
class ReplaceImplicitReceiverCallFix(expression: KtExpression) : KotlinQuickFixAction<KtExpression>(expression) {
51+
override fun getFamilyName() = text
52+
53+
override fun getText() = "Replace with safe (this?.) call"
54+
55+
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
56+
val element = element ?: return
57+
val newExpression = KtPsiFactory(element).createExpressionByPattern("this?.$0", element)
58+
element.replace(newExpression)
59+
}
60+
}
61+
4762
class ReplaceWithSafeCallFix(expression: KtDotQualifiedExpression): ReplaceCallFix(expression, "?.") {
4863

4964
override fun getText() = "Replace with safe (?.) call"
5065

5166
companion object : KotlinSingleIntentionActionFactory() {
5267
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
53-
val qualifiedExpression = diagnostic.psiElement.getParentOfType<KtDotQualifiedExpression>(strict = false) ?: return null
54-
return ReplaceWithSafeCallFix(qualifiedExpression)
68+
val psiElement = diagnostic.psiElement
69+
val qualifiedExpression = psiElement.parent as? KtDotQualifiedExpression
70+
if (qualifiedExpression != null) {
71+
return ReplaceWithSafeCallFix(qualifiedExpression)
72+
} else {
73+
psiElement as? KtNameReferenceExpression ?: return null
74+
if (psiElement.getResolvedCall(psiElement.analyze())?.getImplicitReceiverValue() != null) {
75+
val expressionToReplace: KtExpression = psiElement.parent as? KtCallExpression ?: psiElement
76+
return ReplaceImplicitReceiverCallFix(expressionToReplace)
77+
}
78+
return null
79+
}
5580
}
5681
}
5782
}

idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceInfixOrOperatorCallFix.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention
2525
import org.jetbrains.kotlin.lexer.KtTokens
2626
import org.jetbrains.kotlin.psi.*
2727
import org.jetbrains.kotlin.psi.psiUtil.getAssignmentByLHS
28-
import org.jetbrains.kotlin.resolve.BindingContext
28+
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
29+
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getImplicitReceiverValue
2930
import org.jetbrains.kotlin.types.expressions.OperatorConventions
30-
import org.jetbrains.kotlin.types.typeUtil.isBoolean
3131

3232
class ReplaceInfixOrOperatorCallFix(element: KtExpression) : KotlinQuickFixAction<KtExpression>(element) {
3333

@@ -99,6 +99,7 @@ class ReplaceInfixOrOperatorCallFix(element: KtExpression) : KotlinQuickFixActio
9999
is KtCallExpression -> {
100100
if (parent.calleeExpression == null) null
101101
else if (parent.parent is KtQualifiedExpression) null
102+
else if (parent.getResolvedCall(parent.analyze())?.getImplicitReceiverValue() != null) null
102103
else ReplaceInfixOrOperatorCallFix(parent)
103104
}
104105
else -> null
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// "Add non-null asserted (!!) call" "true"
2+
3+
fun String?.foo() {
4+
<caret>length
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// "Add non-null asserted (!!) call" "true"
2+
3+
fun String?.foo() {
4+
this!!.length
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// "Add non-null asserted (!!) call" "true"
2+
// WITH_RUNTIME
3+
fun String?.foo() {
4+
<caret>toLowerCase()
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// "Add non-null asserted (!!) call" "true"
2+
// WITH_RUNTIME
3+
fun String?.foo() {
4+
this!!.toLowerCase()
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// "Add non-null asserted (!!) call" "true"
2+
3+
fun foo(a: String?) {
4+
a<caret>.length
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// "Add non-null asserted (!!) call" "true"
2+
3+
fun foo(a: String?) {
4+
a!!.length
5+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// "Add non-null asserted (!!) call" "true"
2+
// WITH_RUNTIME
3+
4+
fun foo(a: String?) {
5+
a<caret>.toLowerCase()
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// "Add non-null asserted (!!) call" "true"
2+
// WITH_RUNTIME
3+
4+
fun foo(a: String?) {
5+
a!!.toLowerCase()
6+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// "Replace with safe (?.) call" "false"
2+
// ACTION: Add non-null asserted (!!) call
3+
// ACTION: Convert to block body
4+
// ACTION: Replace overloaded operator with function call
5+
// ACTION: Replace with safe (this?.) call
6+
// ACTION: Wrap with '?.let { ... }' call
7+
// ERROR: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
8+
9+
fun String?.foo(exec: (String.() -> Unit)) = exec<caret>()
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// "Replace with safe (?.) call" "true"
2+
// WITH_RUNTIME
3+
4+
fun foo(array: Array<String>?) {
5+
array<caret>[0]
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// "Replace with safe (?.) call" "true"
2+
// WITH_RUNTIME
3+
4+
fun foo(array: Array<String>?) {
5+
array?.get(0)
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// "Replace with safe (?.) call" "true"
2+
// WITH_RUNTIME
3+
4+
fun foo(array: Array<String>?) {
5+
array<caret>[0] = ""
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// "Replace with safe (?.) call" "true"
2+
// WITH_RUNTIME
3+
4+
fun foo(array: Array<String>?) {
5+
array?.set(0, "")
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// "Replace with safe (?.) call" "true"
2+
// WITH_RUNTIME
3+
4+
fun foo(bar: Int?) {
5+
bar +<caret> 1
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// "Replace with safe (?.) call" "true"
2+
// WITH_RUNTIME
3+
4+
fun foo(bar: Int?) {
5+
bar?.plus(1)
6+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// "Replace with safe (?.) call" "true"
2+
// WITH_RUNTIME
3+
4+
fun foo() {}
5+
6+
fun bar() {
7+
val fff: (() -> Unit)? = ::foo
8+
<caret>fff()
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// "Replace with safe (?.) call" "true"
2+
// WITH_RUNTIME
3+
4+
fun foo() {}
5+
6+
fun bar() {
7+
val fff: (() -> Unit)? = ::foo
8+
fff?.invoke()
9+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// "Replace with safe (?.) call" "true"
2+
// WITH_RUNTIME
3+
4+
fun foo(list: List<String>?) {
5+
list<caret>[0]
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// "Replace with safe (?.) call" "true"
2+
// WITH_RUNTIME
3+
4+
fun foo(list: List<String>?) {
5+
list?.get(0)
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// "Replace with dot call" "true"
2+
// WITH_RUNTIME
3+
fun foo(a: String) {
4+
a<caret>?.toLowerCase()
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// "Replace with dot call" "true"
2+
// WITH_RUNTIME
3+
fun foo(a: String) {
4+
a.toLowerCase()
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// "Replace with dot call" "true"
2+
// WITH_RUNTIME
3+
fun foo(a: String) {
4+
a<caret>?.length
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// "Replace with dot call" "true"
2+
// WITH_RUNTIME
3+
fun foo(a: String) {
4+
a.length
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// "Replace with safe (?.) call" "true"
2+
// WITH_RUNTIME
3+
fun foo(a: String?) {
4+
a.apply {
5+
this<caret>.length
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// "Replace with safe (?.) call" "true"
2+
// WITH_RUNTIME
3+
fun foo(a: String?) {
4+
a.apply {
5+
this?.length
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// "Replace with safe (this?.) call" "true"
2+
// WITH_RUNTIME
3+
fun foo(a: String?) {
4+
a.apply {
5+
<caret>length
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// "Replace with safe (this?.) call" "true"
2+
// WITH_RUNTIME
3+
fun foo(a: String?) {
4+
a.apply {
5+
this?.length
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// "Replace with safe (this?.) call" "true"
2+
// WITH_RUNTIME
3+
fun foo(a: String?) {
4+
a.apply {
5+
<caret>toLowerCase()
6+
}
7+
}

0 commit comments

Comments
 (0)