diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt index c29d4342589..da162ffb479 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt @@ -10,6 +10,7 @@ import org.jetbrains.kotlin.idea.fir.api.fixes.KtQuickFixesList import org.jetbrains.kotlin.idea.fir.api.fixes.KtQuickFixesListBuilder import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic import org.jetbrains.kotlin.idea.quickfix.fixes.ChangeTypeQuickFix +import org.jetbrains.kotlin.idea.quickfix.fixes.ReplaceCallFixFactories class MainKtQuickFixRegistrar : KtQuickFixRegistrar() { private val modifiers = KtQuickFixesListBuilder.registerPsiQuickFix { @@ -69,9 +70,14 @@ class MainKtQuickFixRegistrar : KtQuickFixRegistrar() { registerPsiQuickFixes(KtFirDiagnostic.MustBeInitialized::class, ChangeVariableMutabilityFix.MUST_BE_INITIALIZED_FACTORY) } + private val expressions = KtQuickFixesListBuilder.registerPsiQuickFix { + registerApplicator(ReplaceCallFixFactories.unsafeCallFactory) + } + override val list: KtQuickFixesList = KtQuickFixesList.createCombined( modifiers, overrides, mutability, + expressions, ) } diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/ReplaceCallFixFactories.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/ReplaceCallFixFactories.kt new file mode 100644 index 00000000000..91e3d0598f4 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/ReplaceCallFixFactories.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.fixes + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicatorInput +import org.jetbrains.kotlin.idea.fir.api.applicator.applicatorByQuickFix +import org.jetbrains.kotlin.idea.fir.api.fixes.HLApplicatorWithTargetAndInput +import org.jetbrains.kotlin.idea.fir.api.fixes.diagnosticFixFactory +import org.jetbrains.kotlin.idea.fir.api.fixes.withInput +import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic +import org.jetbrains.kotlin.idea.frontend.api.types.KtTypeNullability +import org.jetbrains.kotlin.idea.frontend.api.types.KtTypeWithNullability +import org.jetbrains.kotlin.idea.quickfix.ReplaceCallFix +import org.jetbrains.kotlin.idea.quickfix.ReplaceImplicitReceiverCallFix +import org.jetbrains.kotlin.idea.quickfix.ReplaceWithSafeCallFix +import org.jetbrains.kotlin.psi.* + +object ReplaceCallFixFactories { + private val replaceWithSafeCallFixApplicator = + applicatorByQuickFix( + getFamilyName = KotlinBundle.lazyMessage("replace.with.safe.call"), + isApplicableByPsi = { psi -> psi is KtDotQualifiedExpression } + ) { psi, input -> + ReplaceWithSafeCallFix(psi as KtDotQualifiedExpression, input.notNullNeeded) + } + + private val replaceImplicitReceiverCallFixApplicator = + applicatorByQuickFix( + getFamilyName = KotlinBundle.lazyMessage("replace.with.safe.this.call") + ) { psi, input -> + ReplaceImplicitReceiverCallFix(psi, input.notNullNeeded) + } + + class Input(val notNullNeeded: Boolean) : HLApplicatorInput + + val unsafeCallFactory = + diagnosticFixFactory { diagnostic -> + fun KtExpression.shouldHaveNotNullType(): Boolean { + // This function is used to determine if we may need to add an elvis operator after the safe call. For example, to replace + // `s.length` in `1 + s.length` with a safe call, it should be replaced with `(s.length ?: )`. + // NOTE: Even though we could, we don't check to see if the call is an argument and if the parameter type is nullable. + // FE1.0 doesn't either (see ReplaceWithSafeCallFixFactory). + val type = when (val parent = parent) { + is KtBinaryExpression -> parent.left?.getKtType() + is KtProperty -> { + // Case: `val i = s.length`. `parent.getReturnKtType()` would return Int and therefore an elvis would be added, + // which does not match FE1.0 behavior. We want to see if there is an explicit type, e.g., `val i: Int = s.length` + // and default to the replacement being nullable if there is no explicit type. + if (parent.typeReference != null) { + parent.getReturnKtType() + } else null + } + else -> null + } + return (type as? KtTypeWithNullability)?.nullability == KtTypeNullability.NON_NULLABLE + } + + when (val psi = diagnostic.psi) { + is KtDotQualifiedExpression -> listOf( + HLApplicatorWithTargetAndInput(replaceWithSafeCallFixApplicator, psi withInput Input(psi.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( + HLApplicatorWithTargetAndInput( + replaceImplicitReceiverCallFixApplicator, + psi withInput Input(psi.shouldHaveNotNullType()) + ) + ) + } + else -> emptyList() + } + } +} diff --git a/idea/testData/quickfix/expressions/unsafeCall1.kt b/idea/testData/quickfix/expressions/unsafeCall1.kt index c15a29f54c4..39005f2dffd 100644 --- a/idea/testData/quickfix/expressions/unsafeCall1.kt +++ b/idea/testData/quickfix/expressions/unsafeCall1.kt @@ -1,4 +1,4 @@ // "Replace with safe (?.) call" "true" fun foo(a: Int?) { a.plus(1) -} +} \ No newline at end of file diff --git a/idea/testData/quickfix/expressions/unsafeCall1.kt.after b/idea/testData/quickfix/expressions/unsafeCall1.kt.after index 52682323884..29fdce55a65 100644 --- a/idea/testData/quickfix/expressions/unsafeCall1.kt.after +++ b/idea/testData/quickfix/expressions/unsafeCall1.kt.after @@ -1,4 +1,4 @@ // "Replace with safe (?.) call" "true" fun foo(a: Int?) { a?.plus(1) -} +} \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/apply.kt b/idea/testData/quickfix/replaceWithSafeCall/apply.kt index a541e684f8c..a795c761687 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/apply.kt +++ b/idea/testData/quickfix/replaceWithSafeCall/apply.kt @@ -4,4 +4,5 @@ fun foo(a: String?) { a.apply { this.length } -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/apply.kt.after b/idea/testData/quickfix/replaceWithSafeCall/apply.kt.after index 553377f2257..25afc8d306d 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/apply.kt.after +++ b/idea/testData/quickfix/replaceWithSafeCall/apply.kt.after @@ -4,4 +4,5 @@ fun foo(a: String?) { a.apply { this?.length } -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameter.kt b/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameter.kt index 1c278c5f944..6bc5cbe1deb 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameter.kt +++ b/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameter.kt @@ -4,4 +4,5 @@ fun foo(a: String?) { a.apply { length } -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameter.kt.after b/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameter.kt.after index 2dc2952a0ad..3697f07ca83 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameter.kt.after +++ b/idea/testData/quickfix/replaceWithSafeCall/applyWithImplicitParameter.kt.after @@ -4,4 +4,5 @@ fun foo(a: String?) { a.apply { this?.length } -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/assignment.kt b/idea/testData/quickfix/replaceWithSafeCall/assignment.kt index 997a7852951..d52c9faa961 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/assignment.kt +++ b/idea/testData/quickfix/replaceWithSafeCall/assignment.kt @@ -4,4 +4,5 @@ var i = 0 fun foo(s: String?) { i = s.length -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/assignment.kt.after b/idea/testData/quickfix/replaceWithSafeCall/assignment.kt.after index 6ed1576a090..1404a9ac863 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/assignment.kt.after +++ b/idea/testData/quickfix/replaceWithSafeCall/assignment.kt.after @@ -4,4 +4,5 @@ var i = 0 fun foo(s: String?) { i = s?.length ?: -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/assignmentFromImplicitParameter.kt b/idea/testData/quickfix/replaceWithSafeCall/assignmentFromImplicitParameter.kt index f2042d6602b..966938626ad 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/assignmentFromImplicitParameter.kt +++ b/idea/testData/quickfix/replaceWithSafeCall/assignmentFromImplicitParameter.kt @@ -6,4 +6,5 @@ fun foo(a: String?) { a.run { i = length } -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/assignmentFromImplicitParameter.kt.after b/idea/testData/quickfix/replaceWithSafeCall/assignmentFromImplicitParameter.kt.after index 65e2d239815..09382eff2e3 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/assignmentFromImplicitParameter.kt.after +++ b/idea/testData/quickfix/replaceWithSafeCall/assignmentFromImplicitParameter.kt.after @@ -6,4 +6,5 @@ fun foo(a: String?) { a.run { i = this?.length ?: } -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/assignmentToNullable.kt b/idea/testData/quickfix/replaceWithSafeCall/assignmentToNullable.kt index 662276c08d8..04ca582c192 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/assignmentToNullable.kt +++ b/idea/testData/quickfix/replaceWithSafeCall/assignmentToNullable.kt @@ -4,4 +4,5 @@ var i: Int? = 0 fun foo(s: String?) { i = s.length -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/assignmentToNullable.kt.after b/idea/testData/quickfix/replaceWithSafeCall/assignmentToNullable.kt.after index 93bb70dcf05..d84e5b98096 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/assignmentToNullable.kt.after +++ b/idea/testData/quickfix/replaceWithSafeCall/assignmentToNullable.kt.after @@ -4,4 +4,5 @@ var i: Int? = 0 fun foo(s: String?) { i = s?.length -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/assignmentToProperty.kt b/idea/testData/quickfix/replaceWithSafeCall/assignmentToProperty.kt index 7c0269dd04e..cb65459ca9a 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/assignmentToProperty.kt +++ b/idea/testData/quickfix/replaceWithSafeCall/assignmentToProperty.kt @@ -2,4 +2,5 @@ // WITH_RUNTIME class T(s: String?) { var i: Int = s.length -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/assignmentToProperty.kt.after b/idea/testData/quickfix/replaceWithSafeCall/assignmentToProperty.kt.after index dd5bf5c4372..0faf1fce0dc 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/assignmentToProperty.kt.after +++ b/idea/testData/quickfix/replaceWithSafeCall/assignmentToProperty.kt.after @@ -2,4 +2,5 @@ // WITH_RUNTIME class T(s: String?) { var i: Int = s?.length ?: -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/comment.kt b/idea/testData/quickfix/replaceWithSafeCall/comment.kt index 0a36bcb2c0e..8f48b917d5d 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/comment.kt +++ b/idea/testData/quickfix/replaceWithSafeCall/comment.kt @@ -4,4 +4,5 @@ fun foo(a: String?) { val b = a // comment1 // comment2 .length -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/comment.kt.after b/idea/testData/quickfix/replaceWithSafeCall/comment.kt.after index 0e7e8983b0e..2b50aa5091c 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/comment.kt.after +++ b/idea/testData/quickfix/replaceWithSafeCall/comment.kt.after @@ -4,4 +4,5 @@ fun foo(a: String?) { val b = a // comment1 // comment2 ?.length -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/expression.kt b/idea/testData/quickfix/replaceWithSafeCall/expression.kt index 13a323a9882..3fa1d752e4a 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/expression.kt +++ b/idea/testData/quickfix/replaceWithSafeCall/expression.kt @@ -2,4 +2,5 @@ // WITH_RUNTIME fun foo(s: String?) { 1 + s.length -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/expression.kt.after b/idea/testData/quickfix/replaceWithSafeCall/expression.kt.after index a2af56cf8c8..b4aa12ccd92 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/expression.kt.after +++ b/idea/testData/quickfix/replaceWithSafeCall/expression.kt.after @@ -2,4 +2,5 @@ // WITH_RUNTIME fun foo(s: String?) { 1 + (s?.length ?: ) -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/hasElvis.kt b/idea/testData/quickfix/replaceWithSafeCall/hasElvis.kt index 4954207bfc5..9d08c97c707 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/hasElvis.kt +++ b/idea/testData/quickfix/replaceWithSafeCall/hasElvis.kt @@ -7,4 +7,5 @@ fun main() { fun foo(): String? { return "" -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/hasElvis.kt.after b/idea/testData/quickfix/replaceWithSafeCall/hasElvis.kt.after index 7584c83c274..2ed06f986dd 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/hasElvis.kt.after +++ b/idea/testData/quickfix/replaceWithSafeCall/hasElvis.kt.after @@ -7,4 +7,5 @@ fun main() { fun foo(): String? { return "" -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/hasElvis2.kt b/idea/testData/quickfix/replaceWithSafeCall/hasElvis2.kt index 8057a617bc2..620680d9fc7 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/hasElvis2.kt +++ b/idea/testData/quickfix/replaceWithSafeCall/hasElvis2.kt @@ -6,4 +6,5 @@ fun foo(a: String?) { a.run { i = length ?: 0 } -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/hasElvis2.kt.after b/idea/testData/quickfix/replaceWithSafeCall/hasElvis2.kt.after index 3a4fd8ed84a..2fa7177fe6e 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/hasElvis2.kt.after +++ b/idea/testData/quickfix/replaceWithSafeCall/hasElvis2.kt.after @@ -6,4 +6,5 @@ fun foo(a: String?) { a.run { i = this?.length ?: 0 } -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/let.kt b/idea/testData/quickfix/replaceWithSafeCall/let.kt index e99a3bb8e34..43c5b3dbe5d 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/let.kt +++ b/idea/testData/quickfix/replaceWithSafeCall/let.kt @@ -4,4 +4,5 @@ fun foo(a: String?) { a.let { it.length } -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/let.kt.after b/idea/testData/quickfix/replaceWithSafeCall/let.kt.after index 70f26cddc0d..f3f835bc5c2 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/let.kt.after +++ b/idea/testData/quickfix/replaceWithSafeCall/let.kt.after @@ -4,4 +4,5 @@ fun foo(a: String?) { a.let { it?.length } -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/letWithParameter.kt b/idea/testData/quickfix/replaceWithSafeCall/letWithParameter.kt index 82bcc2f89a7..521cf774c4b 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/letWithParameter.kt +++ b/idea/testData/quickfix/replaceWithSafeCall/letWithParameter.kt @@ -4,4 +4,5 @@ fun foo(a: String?) { a.let { b -> b.length } -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/letWithParameter.kt.after b/idea/testData/quickfix/replaceWithSafeCall/letWithParameter.kt.after index 6adc9ef1c8e..2ae7d7eb12c 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/letWithParameter.kt.after +++ b/idea/testData/quickfix/replaceWithSafeCall/letWithParameter.kt.after @@ -4,4 +4,5 @@ fun foo(a: String?) { a.let { b -> b?.length } -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/lineBreak.kt b/idea/testData/quickfix/replaceWithSafeCall/lineBreak.kt index 032bb6f1f4d..dde03f04af6 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/lineBreak.kt +++ b/idea/testData/quickfix/replaceWithSafeCall/lineBreak.kt @@ -3,4 +3,5 @@ fun foo(a: String?) { val b = a .length -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/lineBreak.kt.after b/idea/testData/quickfix/replaceWithSafeCall/lineBreak.kt.after index 7fa6f6f649b..19f89e8c0e3 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/lineBreak.kt.after +++ b/idea/testData/quickfix/replaceWithSafeCall/lineBreak.kt.after @@ -3,4 +3,5 @@ fun foo(a: String?) { val b = a ?.length -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/normal.kt b/idea/testData/quickfix/replaceWithSafeCall/normal.kt index 0876a73d81c..c4b4ad58639 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/normal.kt +++ b/idea/testData/quickfix/replaceWithSafeCall/normal.kt @@ -2,4 +2,5 @@ // WITH_RUNTIME fun foo(a: String?) { a.length -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file diff --git a/idea/testData/quickfix/replaceWithSafeCall/normal.kt.after b/idea/testData/quickfix/replaceWithSafeCall/normal.kt.after index e718a236be4..4fb00700edc 100644 --- a/idea/testData/quickfix/replaceWithSafeCall/normal.kt.after +++ b/idea/testData/quickfix/replaceWithSafeCall/normal.kt.after @@ -2,4 +2,5 @@ // WITH_RUNTIME fun foo(a: String?) { a?.length -} \ No newline at end of file +} +/* FIR_COMPARISON */ \ No newline at end of file