From 46aaee5d05758a941ad6cc0918adb13ffa16465e Mon Sep 17 00:00:00 2001 From: Dmitry Neverov Date: Sun, 21 May 2017 07:07:43 +0200 Subject: [PATCH] Fix nullability quick-fixes with implicit receiver #KT-17726 Fixed --- .../kotlin/idea/quickfix/ExclExclCallFixes.kt | 54 ++++-- .../kotlin/idea/quickfix/ReplaceCallFix.kt | 29 +++- .../quickfix/ReplaceInfixOrOperatorCallFix.kt | 5 +- .../quickfix/addExclExclCall/implicit.kt | 5 + .../addExclExclCall/implicit.kt.after | 5 + .../addExclExclCall/implicitFunctionCall.kt | 5 + .../implicitFunctionCall.kt.after | 5 + .../quickfix/addExclExclCall/normal.kt | 5 + .../quickfix/addExclExclCall/normal.kt.after | 5 + .../quickfix/addExclExclCall/normal2.kt | 6 + .../quickfix/addExclExclCall/normal2.kt.after | 6 + .../unsafeInvokeWithImplicitReceiver.kt | 9 + .../replaceInfixOrOperatorCall/array.kt | 6 + .../replaceInfixOrOperatorCall/array.kt.after | 6 + .../replaceInfixOrOperatorCall/arraySet.kt | 6 + .../arraySet.kt.after | 6 + .../binaryOperator.kt | 6 + .../binaryOperator.kt.after | 6 + .../callExpression.kt | 9 + .../callExpression.kt.after | 9 + .../replaceInfixOrOperatorCall/list.kt | 6 + .../replaceInfixOrOperatorCall/list.kt.after | 6 + .../replaceWithDotCall/functionCall.kt | 5 + .../replaceWithDotCall/functionCall.kt.after | 5 + .../quickfix/replaceWithDotCall/normal.kt | 5 + .../replaceWithDotCall/normal.kt.after | 5 + .../quickfix/replaceWithSafeCall/apply.kt | 7 + .../replaceWithSafeCall/apply.kt.after | 7 + .../applyWithImplicitParameter.kt | 7 + .../applyWithImplicitParameter.kt.after | 7 + .../applyWithImplicitParameterFunctionCall.kt | 7 + ...WithImplicitParameterFunctionCall.kt.after | 7 + .../replaceWithSafeCall/extFunction.kt | 5 + .../replaceWithSafeCall/extFunction.kt.after | 5 + .../replaceWithSafeCall/functionCall.kt | 5 + .../replaceWithSafeCall/functionCall.kt.after | 5 + .../quickfix/replaceWithSafeCall/let.kt | 7 + .../quickfix/replaceWithSafeCall/let.kt.after | 7 + .../replaceWithSafeCall/letWithParameter.kt | 7 + .../letWithParameter.kt.after | 7 + ...oReplaceWithSafeCallForImplicitReceiver.kt | 15 ++ .../quickfix/replaceWithSafeCall/normal.kt | 5 + .../replaceWithSafeCall/normal.kt.after | 5 + .../idea/quickfix/QuickFixTestGenerated.java | 162 ++++++++++++++++++ 44 files changed, 479 insertions(+), 23 deletions(-) create mode 100644 idea/testData/quickfix/addExclExclCall/implicit.kt create mode 100644 idea/testData/quickfix/addExclExclCall/implicit.kt.after create mode 100644 idea/testData/quickfix/addExclExclCall/implicitFunctionCall.kt create mode 100644 idea/testData/quickfix/addExclExclCall/implicitFunctionCall.kt.after create mode 100644 idea/testData/quickfix/addExclExclCall/normal.kt create mode 100644 idea/testData/quickfix/addExclExclCall/normal.kt.after create mode 100644 idea/testData/quickfix/addExclExclCall/normal2.kt create mode 100644 idea/testData/quickfix/addExclExclCall/normal2.kt.after create mode 100644 idea/testData/quickfix/nullables/unsafeInfixCall/unsafeInvokeWithImplicitReceiver.kt create mode 100644 idea/testData/quickfix/replaceInfixOrOperatorCall/array.kt create mode 100644 idea/testData/quickfix/replaceInfixOrOperatorCall/array.kt.after create mode 100644 idea/testData/quickfix/replaceInfixOrOperatorCall/arraySet.kt create mode 100644 idea/testData/quickfix/replaceInfixOrOperatorCall/arraySet.kt.after create mode 100644 idea/testData/quickfix/replaceInfixOrOperatorCall/binaryOperator.kt create mode 100644 idea/testData/quickfix/replaceInfixOrOperatorCall/binaryOperator.kt.after create mode 100644 idea/testData/quickfix/replaceInfixOrOperatorCall/callExpression.kt create mode 100644 idea/testData/quickfix/replaceInfixOrOperatorCall/callExpression.kt.after create mode 100644 idea/testData/quickfix/replaceInfixOrOperatorCall/list.kt create mode 100644 idea/testData/quickfix/replaceInfixOrOperatorCall/list.kt.after create mode 100644 idea/testData/quickfix/replaceWithDotCall/functionCall.kt create mode 100644 idea/testData/quickfix/replaceWithDotCall/functionCall.kt.after create mode 100644 idea/testData/quickfix/replaceWithDotCall/normal.kt create mode 100644 idea/testData/quickfix/replaceWithDotCall/normal.kt.after create mode 100644 idea/testData/quickfix/replaceWithSafeCall/apply.kt create mode 100644 idea/testData/quickfix/replaceWithSafeCall/apply.kt.after create mode 100644 idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameter.kt create mode 100644 idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameter.kt.after create mode 100644 idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameterFunctionCall.kt create mode 100644 idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameterFunctionCall.kt.after create mode 100644 idea/testData/quickfix/replaceWithSafeCall/extFunction.kt create mode 100644 idea/testData/quickfix/replaceWithSafeCall/extFunction.kt.after create mode 100644 idea/testData/quickfix/replaceWithSafeCall/functionCall.kt create mode 100644 idea/testData/quickfix/replaceWithSafeCall/functionCall.kt.after create mode 100644 idea/testData/quickfix/replaceWithSafeCall/let.kt create mode 100644 idea/testData/quickfix/replaceWithSafeCall/let.kt.after create mode 100644 idea/testData/quickfix/replaceWithSafeCall/letWithParameter.kt create mode 100644 idea/testData/quickfix/replaceWithSafeCall/letWithParameter.kt.after create mode 100644 idea/testData/quickfix/replaceWithSafeCall/noReplaceWithSafeCallForImplicitReceiver.kt create mode 100644 idea/testData/quickfix/replaceWithSafeCall/normal.kt create mode 100644 idea/testData/quickfix/replaceWithSafeCall/normal.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ExclExclCallFixes.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ExclExclCallFixes.kt index 62695c43fbc..c14edb393ea 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ExclExclCallFixes.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ExclExclCallFixes.kt @@ -38,6 +38,10 @@ import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.util.isValidOperator +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getImplicitReceiverValue + abstract class ExclExclCallFix(psiElement: PsiElement) : KotlinQuickFixAction(psiElement) { override fun getFamilyName(): String = text @@ -75,7 +79,9 @@ class RemoveExclExclCallFix(psiElement: PsiElement) : ExclExclCallFix(psiElement } } -class AddExclExclCallFix(psiElement: PsiElement) : ExclExclCallFix(psiElement) { +class AddExclExclCallFix(psiElement: PsiElement, val checkImplicitReceivers: Boolean) : ExclExclCallFix(psiElement) { + constructor(psiElement: PsiElement) : this(psiElement, true) + override fun getText() = KotlinBundle.message("introduce.non.null.assertion") override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean @@ -85,40 +91,50 @@ class AddExclExclCallFix(psiElement: PsiElement) : ExclExclCallFix(psiElement) { override fun invoke(project: Project, editor: Editor?, file: KtFile) { if (!FileModificationService.getInstance().prepareFileForWrite(file)) return - val modifiedExpression = getExpressionForIntroduceCall() ?: return - val exclExclExpression = KtPsiFactory(project).createExpressionByPattern("$0!!", modifiedExpression) + val expr = getExpressionForIntroduceCall() ?: return + val modifiedExpression = expr.expression + val exclExclExpression = if (expr.implicitReceiver) { + KtPsiFactory(project).createExpressionByPattern("this!!.$0", modifiedExpression) + } else { + KtPsiFactory(project).createExpressionByPattern("$0!!", modifiedExpression) + } modifiedExpression.replace(exclExclExpression) } - private fun getExpressionForIntroduceCall(): KtExpression? { + private class ExpressionForCall(val expression: KtExpression, val implicitReceiver: Boolean) + + private fun KtExpression?.expressionForCall(implicitReceiver: Boolean = false) = this?.let { ExpressionForCall(it, implicitReceiver) } + + private fun getExpressionForIntroduceCall(): ExpressionForCall? { val psiElement = element ?: return null - if (psiElement is LeafPsiElement && psiElement.elementType == KtTokens.DOT) { - val sibling = psiElement.prevSibling - if (sibling is KtExpression) { - return sibling - } + return if (psiElement is LeafPsiElement && psiElement.elementType == KtTokens.DOT) { + (psiElement.prevSibling as? KtExpression).expressionForCall() } else if (psiElement is KtArrayAccessExpression) { - return psiElement.arrayExpression + psiElement.arrayExpression.expressionForCall() } else if (psiElement is KtOperationReferenceExpression) { val parent = psiElement.parent - return when (parent) { - is KtUnaryExpression -> parent.baseExpression - is KtBinaryExpression -> parent.left + when (parent) { + is KtUnaryExpression -> parent.baseExpression.expressionForCall() + is KtBinaryExpression -> parent.left.expressionForCall() else -> null } } else if (psiElement is KtExpression) { - return psiElement + if (checkImplicitReceivers && psiElement.getResolvedCall(psiElement.analyze())?.getImplicitReceiverValue() != null) { + val expressionToReplace = psiElement.parent as? KtCallExpression ?: psiElement + expressionToReplace.expressionForCall(implicitReceiver = true) + } + else psiElement.expressionForCall() + } + else { + null } - - return null } companion object : KotlinSingleIntentionActionFactory() { - override fun createAction(diagnostic: Diagnostic): IntentionAction - = AddExclExclCallFix(diagnostic.psiElement) + override fun createAction(diagnostic: Diagnostic): IntentionAction = AddExclExclCallFix(diagnostic.psiElement) } } @@ -137,7 +153,7 @@ object SmartCastImpossibleExclExclFixFactory: KotlinSingleIntentionActionFactory val nullableExpectedType = TypeUtils.makeNullable(expectedType) if (!type.isSubtypeOf(nullableExpectedType)) return null - return AddExclExclCallFix(element) + return AddExclExclCallFix(element, checkImplicitReceivers = false) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceCallFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceCallFix.kt index 03eddbb72f5..486895883d0 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceCallFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceCallFix.kt @@ -21,8 +21,11 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getImplicitReceiverValue abstract class ReplaceCallFix( expression: KtQualifiedExpression, @@ -44,14 +47,36 @@ abstract class ReplaceCallFix( } } +class ReplaceImplicitReceiverCallFix(expression: KtExpression) : KotlinQuickFixAction(expression) { + override fun getFamilyName() = text + + override fun getText() = "Replace with safe (this?.) call" + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val element = element ?: return + val newExpression = KtPsiFactory(element).createExpressionByPattern("this?.$0", element) + element.replace(newExpression) + } +} + class ReplaceWithSafeCallFix(expression: KtDotQualifiedExpression): ReplaceCallFix(expression, "?.") { override fun getText() = "Replace with safe (?.) call" companion object : KotlinSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { - val qualifiedExpression = diagnostic.psiElement.getParentOfType(strict = false) ?: return null - return ReplaceWithSafeCallFix(qualifiedExpression) + val psiElement = diagnostic.psiElement + val qualifiedExpression = psiElement.parent as? KtDotQualifiedExpression + if (qualifiedExpression != null) { + return ReplaceWithSafeCallFix(qualifiedExpression) + } else { + psiElement as? KtNameReferenceExpression ?: return null + if (psiElement.getResolvedCall(psiElement.analyze())?.getImplicitReceiverValue() != null) { + val expressionToReplace: KtExpression = psiElement.parent as? KtCallExpression ?: psiElement + return ReplaceImplicitReceiverCallFix(expressionToReplace) + } + return null + } } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceInfixOrOperatorCallFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceInfixOrOperatorCallFix.kt index f0dcb9275a6..5d1c54c01d6 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceInfixOrOperatorCallFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ReplaceInfixOrOperatorCallFix.kt @@ -25,9 +25,9 @@ import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getAssignmentByLHS -import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getImplicitReceiverValue import org.jetbrains.kotlin.types.expressions.OperatorConventions -import org.jetbrains.kotlin.types.typeUtil.isBoolean class ReplaceInfixOrOperatorCallFix(element: KtExpression) : KotlinQuickFixAction(element) { @@ -99,6 +99,7 @@ class ReplaceInfixOrOperatorCallFix(element: KtExpression) : KotlinQuickFixActio is KtCallExpression -> { if (parent.calleeExpression == null) null else if (parent.parent is KtQualifiedExpression) null + else if (parent.getResolvedCall(parent.analyze())?.getImplicitReceiverValue() != null) null else ReplaceInfixOrOperatorCallFix(parent) } else -> null diff --git a/idea/testData/quickfix/addExclExclCall/implicit.kt b/idea/testData/quickfix/addExclExclCall/implicit.kt new file mode 100644 index 00000000000..ed029ac2894 --- /dev/null +++ b/idea/testData/quickfix/addExclExclCall/implicit.kt @@ -0,0 +1,5 @@ +// "Add non-null asserted (!!) call" "true" + +fun String?.foo() { + length +} \ No newline at end of file diff --git a/idea/testData/quickfix/addExclExclCall/implicit.kt.after b/idea/testData/quickfix/addExclExclCall/implicit.kt.after new file mode 100644 index 00000000000..9b8283c14f3 --- /dev/null +++ b/idea/testData/quickfix/addExclExclCall/implicit.kt.after @@ -0,0 +1,5 @@ +// "Add non-null asserted (!!) call" "true" + +fun String?.foo() { + this!!.length +} \ No newline at end of file diff --git a/idea/testData/quickfix/addExclExclCall/implicitFunctionCall.kt b/idea/testData/quickfix/addExclExclCall/implicitFunctionCall.kt new file mode 100644 index 00000000000..72d90b5e1c6 --- /dev/null +++ b/idea/testData/quickfix/addExclExclCall/implicitFunctionCall.kt @@ -0,0 +1,5 @@ +// "Add non-null asserted (!!) call" "true" +// WITH_RUNTIME +fun String?.foo() { + toLowerCase() +} \ No newline at end of file diff --git a/idea/testData/quickfix/addExclExclCall/implicitFunctionCall.kt.after b/idea/testData/quickfix/addExclExclCall/implicitFunctionCall.kt.after new file mode 100644 index 00000000000..174f04015c2 --- /dev/null +++ b/idea/testData/quickfix/addExclExclCall/implicitFunctionCall.kt.after @@ -0,0 +1,5 @@ +// "Add non-null asserted (!!) call" "true" +// WITH_RUNTIME +fun String?.foo() { + this!!.toLowerCase() +} \ No newline at end of file diff --git a/idea/testData/quickfix/addExclExclCall/normal.kt b/idea/testData/quickfix/addExclExclCall/normal.kt new file mode 100644 index 00000000000..244e1995be9 --- /dev/null +++ b/idea/testData/quickfix/addExclExclCall/normal.kt @@ -0,0 +1,5 @@ +// "Add non-null asserted (!!) call" "true" + +fun foo(a: String?) { + a.length +} \ No newline at end of file diff --git a/idea/testData/quickfix/addExclExclCall/normal.kt.after b/idea/testData/quickfix/addExclExclCall/normal.kt.after new file mode 100644 index 00000000000..aa284cc474a --- /dev/null +++ b/idea/testData/quickfix/addExclExclCall/normal.kt.after @@ -0,0 +1,5 @@ +// "Add non-null asserted (!!) call" "true" + +fun foo(a: String?) { + a!!.length +} \ No newline at end of file diff --git a/idea/testData/quickfix/addExclExclCall/normal2.kt b/idea/testData/quickfix/addExclExclCall/normal2.kt new file mode 100644 index 00000000000..b79b224547b --- /dev/null +++ b/idea/testData/quickfix/addExclExclCall/normal2.kt @@ -0,0 +1,6 @@ +// "Add non-null asserted (!!) call" "true" +// WITH_RUNTIME + +fun foo(a: String?) { + a.toLowerCase() +} \ No newline at end of file diff --git a/idea/testData/quickfix/addExclExclCall/normal2.kt.after b/idea/testData/quickfix/addExclExclCall/normal2.kt.after new file mode 100644 index 00000000000..6ec9f75cb27 --- /dev/null +++ b/idea/testData/quickfix/addExclExclCall/normal2.kt.after @@ -0,0 +1,6 @@ +// "Add non-null asserted (!!) call" "true" +// WITH_RUNTIME + +fun foo(a: String?) { + a!!.toLowerCase() +} \ No newline at end of file diff --git a/idea/testData/quickfix/nullables/unsafeInfixCall/unsafeInvokeWithImplicitReceiver.kt b/idea/testData/quickfix/nullables/unsafeInfixCall/unsafeInvokeWithImplicitReceiver.kt new file mode 100644 index 00000000000..5b887b1942b --- /dev/null +++ b/idea/testData/quickfix/nullables/unsafeInfixCall/unsafeInvokeWithImplicitReceiver.kt @@ -0,0 +1,9 @@ +// "Replace with safe (?.) call" "false" +// ACTION: Add non-null asserted (!!) call +// ACTION: Convert to block body +// ACTION: Replace overloaded operator with function call +// ACTION: Replace with safe (this?.) call +// ACTION: Wrap with '?.let { ... }' call +// ERROR: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String? + +fun String?.foo(exec: (String.() -> Unit)) = exec() \ No newline at end of file diff --git a/idea/testData/quickfix/replaceInfixOrOperatorCall/array.kt b/idea/testData/quickfix/replaceInfixOrOperatorCall/array.kt new file mode 100644 index 00000000000..0025d4e9dcd --- /dev/null +++ b/idea/testData/quickfix/replaceInfixOrOperatorCall/array.kt @@ -0,0 +1,6 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME + +fun foo(array: Array?) { + array[0] +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceInfixOrOperatorCall/array.kt.after b/idea/testData/quickfix/replaceInfixOrOperatorCall/array.kt.after new file mode 100644 index 00000000000..396d3c95181 --- /dev/null +++ b/idea/testData/quickfix/replaceInfixOrOperatorCall/array.kt.after @@ -0,0 +1,6 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME + +fun foo(array: Array?) { + array?.get(0) +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceInfixOrOperatorCall/arraySet.kt b/idea/testData/quickfix/replaceInfixOrOperatorCall/arraySet.kt new file mode 100644 index 00000000000..7392346d0cd --- /dev/null +++ b/idea/testData/quickfix/replaceInfixOrOperatorCall/arraySet.kt @@ -0,0 +1,6 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME + +fun foo(array: Array?) { + array[0] = "" +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceInfixOrOperatorCall/arraySet.kt.after b/idea/testData/quickfix/replaceInfixOrOperatorCall/arraySet.kt.after new file mode 100644 index 00000000000..2e26bfb9b46 --- /dev/null +++ b/idea/testData/quickfix/replaceInfixOrOperatorCall/arraySet.kt.after @@ -0,0 +1,6 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME + +fun foo(array: Array?) { + array?.set(0, "") +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceInfixOrOperatorCall/binaryOperator.kt b/idea/testData/quickfix/replaceInfixOrOperatorCall/binaryOperator.kt new file mode 100644 index 00000000000..908b407ad11 --- /dev/null +++ b/idea/testData/quickfix/replaceInfixOrOperatorCall/binaryOperator.kt @@ -0,0 +1,6 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME + +fun foo(bar: Int?) { + bar + 1 +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceInfixOrOperatorCall/binaryOperator.kt.after b/idea/testData/quickfix/replaceInfixOrOperatorCall/binaryOperator.kt.after new file mode 100644 index 00000000000..9863dbfce16 --- /dev/null +++ b/idea/testData/quickfix/replaceInfixOrOperatorCall/binaryOperator.kt.after @@ -0,0 +1,6 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME + +fun foo(bar: Int?) { + bar?.plus(1) +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceInfixOrOperatorCall/callExpression.kt b/idea/testData/quickfix/replaceInfixOrOperatorCall/callExpression.kt new file mode 100644 index 00000000000..3b062c34e48 --- /dev/null +++ b/idea/testData/quickfix/replaceInfixOrOperatorCall/callExpression.kt @@ -0,0 +1,9 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME + +fun foo() {} + +fun bar() { + val fff: (() -> Unit)? = ::foo + fff() +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceInfixOrOperatorCall/callExpression.kt.after b/idea/testData/quickfix/replaceInfixOrOperatorCall/callExpression.kt.after new file mode 100644 index 00000000000..918037f70b1 --- /dev/null +++ b/idea/testData/quickfix/replaceInfixOrOperatorCall/callExpression.kt.after @@ -0,0 +1,9 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME + +fun foo() {} + +fun bar() { + val fff: (() -> Unit)? = ::foo + fff?.invoke() +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceInfixOrOperatorCall/list.kt b/idea/testData/quickfix/replaceInfixOrOperatorCall/list.kt new file mode 100644 index 00000000000..6d9f7595d07 --- /dev/null +++ b/idea/testData/quickfix/replaceInfixOrOperatorCall/list.kt @@ -0,0 +1,6 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME + +fun foo(list: List?) { + list[0] +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceInfixOrOperatorCall/list.kt.after b/idea/testData/quickfix/replaceInfixOrOperatorCall/list.kt.after new file mode 100644 index 00000000000..e89f33e5cd4 --- /dev/null +++ b/idea/testData/quickfix/replaceInfixOrOperatorCall/list.kt.after @@ -0,0 +1,6 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME + +fun foo(list: List?) { + list?.get(0) +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithDotCall/functionCall.kt b/idea/testData/quickfix/replaceWithDotCall/functionCall.kt new file mode 100644 index 00000000000..9a1069b8cc7 --- /dev/null +++ b/idea/testData/quickfix/replaceWithDotCall/functionCall.kt @@ -0,0 +1,5 @@ +// "Replace with dot call" "true" +// WITH_RUNTIME +fun foo(a: String) { + a?.toLowerCase() +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithDotCall/functionCall.kt.after b/idea/testData/quickfix/replaceWithDotCall/functionCall.kt.after new file mode 100644 index 00000000000..c8fa9874140 --- /dev/null +++ b/idea/testData/quickfix/replaceWithDotCall/functionCall.kt.after @@ -0,0 +1,5 @@ +// "Replace with dot call" "true" +// WITH_RUNTIME +fun foo(a: String) { + a.toLowerCase() +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithDotCall/normal.kt b/idea/testData/quickfix/replaceWithDotCall/normal.kt new file mode 100644 index 00000000000..f807755ce93 --- /dev/null +++ b/idea/testData/quickfix/replaceWithDotCall/normal.kt @@ -0,0 +1,5 @@ +// "Replace with dot call" "true" +// WITH_RUNTIME +fun foo(a: String) { + a?.length +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithDotCall/normal.kt.after b/idea/testData/quickfix/replaceWithDotCall/normal.kt.after new file mode 100644 index 00000000000..f79947610a9 --- /dev/null +++ b/idea/testData/quickfix/replaceWithDotCall/normal.kt.after @@ -0,0 +1,5 @@ +// "Replace with dot call" "true" +// WITH_RUNTIME +fun foo(a: String) { + a.length +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/apply.kt b/idea/testData/quickfix/replaceWithSafeCall/apply.kt new file mode 100644 index 00000000000..a541e684f8c --- /dev/null +++ b/idea/testData/quickfix/replaceWithSafeCall/apply.kt @@ -0,0 +1,7 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME +fun foo(a: String?) { + a.apply { + this.length + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/apply.kt.after b/idea/testData/quickfix/replaceWithSafeCall/apply.kt.after new file mode 100644 index 00000000000..553377f2257 --- /dev/null +++ b/idea/testData/quickfix/replaceWithSafeCall/apply.kt.after @@ -0,0 +1,7 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME +fun foo(a: String?) { + a.apply { + this?.length + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameter.kt b/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameter.kt new file mode 100644 index 00000000000..1c278c5f944 --- /dev/null +++ b/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameter.kt @@ -0,0 +1,7 @@ +// "Replace with safe (this?.) call" "true" +// WITH_RUNTIME +fun foo(a: String?) { + a.apply { + length + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameter.kt.after b/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameter.kt.after new file mode 100644 index 00000000000..2dc2952a0ad --- /dev/null +++ b/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameter.kt.after @@ -0,0 +1,7 @@ +// "Replace with safe (this?.) call" "true" +// WITH_RUNTIME +fun foo(a: String?) { + a.apply { + this?.length + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameterFunctionCall.kt b/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameterFunctionCall.kt new file mode 100644 index 00000000000..9b8aba9f9e1 --- /dev/null +++ b/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameterFunctionCall.kt @@ -0,0 +1,7 @@ +// "Replace with safe (this?.) call" "true" +// WITH_RUNTIME +fun foo(a: String?) { + a.apply { + toLowerCase() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameterFunctionCall.kt.after b/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameterFunctionCall.kt.after new file mode 100644 index 00000000000..bbda28a0a3c --- /dev/null +++ b/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameterFunctionCall.kt.after @@ -0,0 +1,7 @@ +// "Replace with safe (this?.) call" "true" +// WITH_RUNTIME +fun foo(a: String?) { + a.apply { + this?.toLowerCase() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/extFunction.kt b/idea/testData/quickfix/replaceWithSafeCall/extFunction.kt new file mode 100644 index 00000000000..61e65674de0 --- /dev/null +++ b/idea/testData/quickfix/replaceWithSafeCall/extFunction.kt @@ -0,0 +1,5 @@ +// "Replace with safe (this?.) call" "true" +// WITH_RUNTIME +fun String?.foo() { + toLowerCase() +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/extFunction.kt.after b/idea/testData/quickfix/replaceWithSafeCall/extFunction.kt.after new file mode 100644 index 00000000000..7d18b9a606f --- /dev/null +++ b/idea/testData/quickfix/replaceWithSafeCall/extFunction.kt.after @@ -0,0 +1,5 @@ +// "Replace with safe (this?.) call" "true" +// WITH_RUNTIME +fun String?.foo() { + this?.toLowerCase() +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/functionCall.kt b/idea/testData/quickfix/replaceWithSafeCall/functionCall.kt new file mode 100644 index 00000000000..e3143b81dcc --- /dev/null +++ b/idea/testData/quickfix/replaceWithSafeCall/functionCall.kt @@ -0,0 +1,5 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME +fun foo(a: String?) { + a.toLowerCase() +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/functionCall.kt.after b/idea/testData/quickfix/replaceWithSafeCall/functionCall.kt.after new file mode 100644 index 00000000000..be4477ffd6e --- /dev/null +++ b/idea/testData/quickfix/replaceWithSafeCall/functionCall.kt.after @@ -0,0 +1,5 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME +fun foo(a: String?) { + a?.toLowerCase() +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/let.kt b/idea/testData/quickfix/replaceWithSafeCall/let.kt new file mode 100644 index 00000000000..e99a3bb8e34 --- /dev/null +++ b/idea/testData/quickfix/replaceWithSafeCall/let.kt @@ -0,0 +1,7 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME +fun foo(a: String?) { + a.let { + it.length + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/let.kt.after b/idea/testData/quickfix/replaceWithSafeCall/let.kt.after new file mode 100644 index 00000000000..70f26cddc0d --- /dev/null +++ b/idea/testData/quickfix/replaceWithSafeCall/let.kt.after @@ -0,0 +1,7 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME +fun foo(a: String?) { + a.let { + it?.length + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/letWithParameter.kt b/idea/testData/quickfix/replaceWithSafeCall/letWithParameter.kt new file mode 100644 index 00000000000..82bcc2f89a7 --- /dev/null +++ b/idea/testData/quickfix/replaceWithSafeCall/letWithParameter.kt @@ -0,0 +1,7 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME +fun foo(a: String?) { + a.let { b -> + b.length + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/letWithParameter.kt.after b/idea/testData/quickfix/replaceWithSafeCall/letWithParameter.kt.after new file mode 100644 index 00000000000..6adc9ef1c8e --- /dev/null +++ b/idea/testData/quickfix/replaceWithSafeCall/letWithParameter.kt.after @@ -0,0 +1,7 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME +fun foo(a: String?) { + a.let { b -> + b?.length + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/noReplaceWithSafeCallForImplicitReceiver.kt b/idea/testData/quickfix/replaceWithSafeCall/noReplaceWithSafeCallForImplicitReceiver.kt new file mode 100644 index 00000000000..5b19167dc1e --- /dev/null +++ b/idea/testData/quickfix/replaceWithSafeCall/noReplaceWithSafeCallForImplicitReceiver.kt @@ -0,0 +1,15 @@ +// "Replace with safe (?.) call" "false" +// ACTION: Add non-null asserted (!!) call +// ACTION: Convert to expression body +// ACTION: Replace with safe (this?.) call +// ACTION: Wrap with '?.let { ... }' call +// ERROR: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type A? + +class A { + fun foo() { + } +} + +fun A?.bar() { + foo() +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/normal.kt b/idea/testData/quickfix/replaceWithSafeCall/normal.kt new file mode 100644 index 00000000000..0876a73d81c --- /dev/null +++ b/idea/testData/quickfix/replaceWithSafeCall/normal.kt @@ -0,0 +1,5 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME +fun foo(a: String?) { + a.length +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/normal.kt.after b/idea/testData/quickfix/replaceWithSafeCall/normal.kt.after new file mode 100644 index 00000000000..e718a236be4 --- /dev/null +++ b/idea/testData/quickfix/replaceWithSafeCall/normal.kt.after @@ -0,0 +1,5 @@ +// "Replace with safe (?.) call" "true" +// WITH_RUNTIME +fun foo(a: String?) { + a?.length +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index bf71a68ad90..2df5381d861 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -234,6 +234,39 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } } + @TestMetadata("idea/testData/quickfix/addExclExclCall") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class AddExclExclCall extends AbstractQuickFixTest { + public void testAllFilesPresentInAddExclExclCall() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/addExclExclCall"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("implicit.kt") + public void testImplicit() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addExclExclCall/implicit.kt"); + doTest(fileName); + } + + @TestMetadata("implicitFunctionCall.kt") + public void testImplicitFunctionCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addExclExclCall/implicitFunctionCall.kt"); + doTest(fileName); + } + + @TestMetadata("normal.kt") + public void testNormal() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addExclExclCall/normal.kt"); + doTest(fileName); + } + + @TestMetadata("normal2.kt") + public void testNormal2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/addExclExclCall/normal2.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/quickfix/addGenericUpperBound") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) @@ -7243,6 +7276,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { doTest(fileName); } + @TestMetadata("unsafeInvokeWithImplicitReceiver.kt") + public void testUnsafeInvokeWithImplicitReceiver() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/nullables/unsafeInfixCall/unsafeInvokeWithImplicitReceiver.kt"); + doTest(fileName); + } + @TestMetadata("unsafeInvokeWithReceiver.kt") public void testUnsafeInvokeWithReceiver() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/nullables/unsafeInfixCall/unsafeInvokeWithReceiver.kt"); @@ -8427,6 +8466,45 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } } + @TestMetadata("idea/testData/quickfix/replaceInfixOrOperatorCall") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReplaceInfixOrOperatorCall extends AbstractQuickFixTest { + public void testAllFilesPresentInReplaceInfixOrOperatorCall() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/replaceInfixOrOperatorCall"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("array.kt") + public void testArray() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceInfixOrOperatorCall/array.kt"); + doTest(fileName); + } + + @TestMetadata("arraySet.kt") + public void testArraySet() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceInfixOrOperatorCall/arraySet.kt"); + doTest(fileName); + } + + @TestMetadata("binaryOperator.kt") + public void testBinaryOperator() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceInfixOrOperatorCall/binaryOperator.kt"); + doTest(fileName); + } + + @TestMetadata("callExpression.kt") + public void testCallExpression() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceInfixOrOperatorCall/callExpression.kt"); + doTest(fileName); + } + + @TestMetadata("list.kt") + public void testList() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceInfixOrOperatorCall/list.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/quickfix/replaceJvmFieldWithConst") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) @@ -8490,6 +8568,90 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } } + @TestMetadata("idea/testData/quickfix/replaceWithDotCall") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReplaceWithDotCall extends AbstractQuickFixTest { + public void testAllFilesPresentInReplaceWithDotCall() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/replaceWithDotCall"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("functionCall.kt") + public void testFunctionCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceWithDotCall/functionCall.kt"); + doTest(fileName); + } + + @TestMetadata("normal.kt") + public void testNormal() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceWithDotCall/normal.kt"); + doTest(fileName); + } + } + + @TestMetadata("idea/testData/quickfix/replaceWithSafeCall") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReplaceWithSafeCall extends AbstractQuickFixTest { + public void testAllFilesPresentInReplaceWithSafeCall() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/replaceWithSafeCall"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("apply.kt") + public void testApply() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceWithSafeCall/apply.kt"); + doTest(fileName); + } + + @TestMetadata("applyWithImplicitParameter.kt") + public void testApplyWithImplicitParameter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameter.kt"); + doTest(fileName); + } + + @TestMetadata("applyWithImplicitParameterFunctionCall.kt") + public void testApplyWithImplicitParameterFunctionCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameterFunctionCall.kt"); + doTest(fileName); + } + + @TestMetadata("extFunction.kt") + public void testExtFunction() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceWithSafeCall/extFunction.kt"); + doTest(fileName); + } + + @TestMetadata("functionCall.kt") + public void testFunctionCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceWithSafeCall/functionCall.kt"); + doTest(fileName); + } + + @TestMetadata("let.kt") + public void testLet() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceWithSafeCall/let.kt"); + doTest(fileName); + } + + @TestMetadata("letWithParameter.kt") + public void testLetWithParameter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceWithSafeCall/letWithParameter.kt"); + doTest(fileName); + } + + @TestMetadata("noReplaceWithSafeCallForImplicitReceiver.kt") + public void testNoReplaceWithSafeCallForImplicitReceiver() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceWithSafeCall/noReplaceWithSafeCallForImplicitReceiver.kt"); + doTest(fileName); + } + + @TestMetadata("normal.kt") + public void testNormal() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/replaceWithSafeCall/normal.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/quickfix/simplifyComparison") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)