diff --git a/plugins/kotlin/base/analysis-api/analysis-api-utils/src/org/jetbrains/kotlin/idea/base/analysis/api/utils/resolveUtils.kt b/plugins/kotlin/base/analysis-api/analysis-api-utils/src/org/jetbrains/kotlin/idea/base/analysis/api/utils/resolveUtils.kt index f9ef4155ccdb..302cf9a1d582 100644 --- a/plugins/kotlin/base/analysis-api/analysis-api-utils/src/org/jetbrains/kotlin/idea/base/analysis/api/utils/resolveUtils.kt +++ b/plugins/kotlin/base/analysis-api/analysis-api-utils/src/org/jetbrains/kotlin/idea/base/analysis/api/utils/resolveUtils.kt @@ -10,6 +10,8 @@ import org.jetbrains.kotlin.analysis.api.calls.KtCallCandidateInfo import org.jetbrains.kotlin.analysis.api.calls.KtCallableMemberCall import org.jetbrains.kotlin.analysis.api.calls.KtFunctionCall import org.jetbrains.kotlin.analysis.api.calls.KtImplicitReceiverValue +import org.jetbrains.kotlin.analysis.api.calls.KtReceiverValue +import org.jetbrains.kotlin.analysis.api.calls.KtSmartCastedReceiverValue import org.jetbrains.kotlin.analysis.api.calls.singleFunctionCallOrNull import org.jetbrains.kotlin.analysis.api.components.buildClassType import org.jetbrains.kotlin.analysis.api.signatures.KtFunctionLikeSignature @@ -185,6 +187,15 @@ fun KtReference.resolveToExpandedSymbol(): KtSymbol? = when (val symbol = resolv else -> symbol } +/** + * @return implicit receivers of [this], including implicit receivers with smart casts, which are unwrapped to [KtImplicitReceiverValue] + */ fun KtCallableMemberCall<*, *>.getImplicitReceivers(): List = partiallyAppliedSymbol .let { listOfNotNull(it.dispatchReceiver, it.extensionReceiver) } - .filterIsInstance() \ No newline at end of file + .map { it.unwrapSmartCasts() } + .filterIsInstance() + +private tailrec fun KtReceiverValue.unwrapSmartCasts(): KtReceiverValue = when (this) { + is KtSmartCastedReceiverValue -> original.unwrapSmartCasts() + else -> this +} \ No newline at end of file diff --git a/plugins/kotlin/base/psi/src/org/jetbrains/kotlin/idea/base/psi/IfThenUtils.kt b/plugins/kotlin/base/psi/src/org/jetbrains/kotlin/idea/base/psi/IfThenUtils.kt index da5a7cef0f37..354190f9701e 100644 --- a/plugins/kotlin/base/psi/src/org/jetbrains/kotlin/idea/base/psi/IfThenUtils.kt +++ b/plugins/kotlin/base/psi/src/org/jetbrains/kotlin/idea/base/psi/IfThenUtils.kt @@ -6,19 +6,28 @@ import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* -fun KtExpression.unwrapBlockOrParenthesis(): KtExpression { +/** + * If [this] is [KtBlockExpression], returns, after deparenthesizing, a single block statement or `null` if multiple statements are present. + * Otherwise, simply returns deparenthesized [this]. + */ +fun KtExpression.getSingleUnwrappedStatement(): KtExpression? { val innerExpression = KtPsiUtil.safeDeparenthesize(this, true) if (innerExpression is KtBlockExpression) { - val statement = innerExpression.statements.singleOrNull() ?: return this + val statement = innerExpression.statements.singleOrNull() ?: return null val deparenthesized = KtPsiUtil.safeDeparenthesize(statement, true) - if (deparenthesized is KtLambdaExpression) return this + if (deparenthesized is KtLambdaExpression) return null return deparenthesized } return innerExpression } +/** + * See [getSingleUnwrappedStatement]. + */ +fun KtExpression.getSingleUnwrappedStatementOrThis(): KtExpression = getSingleUnwrappedStatement() ?: this + fun KtBinaryExpression.expressionComparedToNull(): KtExpression? { val operationToken = this.operationToken if (operationToken != KtTokens.EQEQ && operationToken != KtTokens.EXCLEQ) return null @@ -32,4 +41,4 @@ fun KtBinaryExpression.expressionComparedToNull(): KtExpression? { return if (leftIsNull) right else left } -fun KtExpression?.isNullExpression(): Boolean = this?.unwrapBlockOrParenthesis()?.node?.elementType == KtNodeTypes.NULL \ No newline at end of file +fun KtExpression?.isNullExpression(): Boolean = this?.getSingleUnwrappedStatementOrThis()?.node?.elementType == KtNodeTypes.NULL \ No newline at end of file diff --git a/plugins/kotlin/base/psi/src/org/jetbrains/kotlin/idea/base/psi/KotlinPsiModificationUtils.kt b/plugins/kotlin/base/psi/src/org/jetbrains/kotlin/idea/base/psi/KotlinPsiModificationUtils.kt index 365cdda93631..d3f1669d94b6 100644 --- a/plugins/kotlin/base/psi/src/org/jetbrains/kotlin/idea/base/psi/KotlinPsiModificationUtils.kt +++ b/plugins/kotlin/base/psi/src/org/jetbrains/kotlin/idea/base/psi/KotlinPsiModificationUtils.kt @@ -169,4 +169,20 @@ fun replaceSamConstructorCall(callExpression: KtCallExpression): KtLambdaExpress ?: throw AssertionError("SAM constructor should have a FunctionLiteralExpression as single argument: ${callExpression.getElementTextWithContext()}") val ktExpression = callExpression.getQualifiedExpressionForSelectorOrThis() return runWriteActionIfPhysical(ktExpression) { ktExpression.replace(functionalArgument) as KtLambdaExpression } +} + +/** + * @return the expression which was actually inserted in the tree + */ +fun KtExpression.prependDotQualifiedReceiver(receiver: KtExpression, factory: KtPsiFactory): KtExpression { + val dotQualified = factory.createExpressionByPattern("$0.$1", receiver, this) + return this.replaced(dotQualified) +} + +/** + * @return the expression which was actually inserted in the tree + */ +fun KtExpression.appendDotQualifiedSelector(selector: KtExpression, factory: KtPsiFactory): KtExpression { + val dotQualified = factory.createExpressionByPattern("$0.$1", this, selector) + return this.replaced(dotQualified) } \ No newline at end of file diff --git a/plugins/kotlin/code-insight/api/src/org/jetbrains/kotlin/idea/codeinsight/api/applicators/ApplicabilityRange.kt b/plugins/kotlin/code-insight/api/src/org/jetbrains/kotlin/idea/codeinsight/api/applicators/ApplicabilityRange.kt index 10475a25ad69..e1ab438dd67b 100644 --- a/plugins/kotlin/code-insight/api/src/org/jetbrains/kotlin/idea/codeinsight/api/applicators/ApplicabilityRange.kt +++ b/plugins/kotlin/code-insight/api/src/org/jetbrains/kotlin/idea/codeinsight/api/applicators/ApplicabilityRange.kt @@ -9,6 +9,8 @@ import org.jetbrains.kotlin.idea.base.psi.textRangeIn /** * The ranges are relative to the passed element, * i.e., if range covers the whole element when it should return `[0, element.length)`. + * + * See [org.jetbrains.kotlin.idea.codeinsights.impl.base.applicators.ApplicabilityRanges] for the commonly occurring applicability ranges. */ object ApplicabilityRange { diff --git a/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeInsight/IfThenTransformationUtils.kt b/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeInsight/IfThenTransformationUtils.kt new file mode 100644 index 000000000000..65b346b4ff4e --- /dev/null +++ b/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeInsight/IfThenTransformationUtils.kt @@ -0,0 +1,246 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.codeInsight + +import com.intellij.modcommand.ModPsiUpdater +import com.intellij.psi.util.parentsOfType +import com.intellij.util.concurrency.annotations.RequiresWriteLock +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.calls.KtCallableMemberCall +import org.jetbrains.kotlin.analysis.api.calls.KtSimpleFunctionCall +import org.jetbrains.kotlin.analysis.api.calls.singleCallOrNull +import org.jetbrains.kotlin.analysis.api.calls.successfulCallOrNull +import org.jetbrains.kotlin.idea.base.analysis.api.utils.getImplicitReceivers +import org.jetbrains.kotlin.idea.base.psi.expressionComparedToNull +import org.jetbrains.kotlin.idea.base.psi.getSingleUnwrappedStatement +import org.jetbrains.kotlin.idea.base.psi.prependDotQualifiedReceiver +import org.jetbrains.kotlin.idea.base.psi.replaced +import org.jetbrains.kotlin.idea.codeinsight.utils.getLeftMostReceiverExpressionOrThis +import org.jetbrains.kotlin.idea.codeinsights.impl.base.insertSafeCallsAfterReceiver +import org.jetbrains.kotlin.idea.codeinsights.impl.base.isSimplifiableTo +import org.jetbrains.kotlin.idea.codeinsights.impl.base.replaceVariableCallsWithExplicitInvokeCalls +import org.jetbrains.kotlin.idea.codeinsights.impl.base.wrapWithLet +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType +import org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny +import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment +import org.jetbrains.kotlin.utils.exceptions.withPsiEntry + +@ApiStatus.Internal +data class IfThenTransformationData( + val ifExpression: KtIfExpression, + val condition: KtOperationExpression, + /** + * Expression checked in [condition]. + */ + val checkedExpression: KtExpression, + val baseClause: KtExpression, + val negatedClause: KtExpression?, +) + +@ApiStatus.Internal +enum class TransformIfThenReceiverMode { + ADD_EXPLICIT_THIS, + + /** + * Indicates that the base clause and checked expression are the same. + */ + REPLACE_BASE_CLAUSE, + + /** + * Indicates that checked expression is a receiver in the base clause, and we need to find it and then replace it. + */ + FIND_AND_REPLACE_MATCHING_RECEIVER, +} + +@ApiStatus.Internal +object IfThenTransformationUtils { + @RequiresWriteLock + fun transformBaseClause(data: IfThenTransformationData, strategy: IfThenTransformationStrategy): KtExpression { + val factory = KtPsiFactory(data.baseClause.project) + + val newReceiverExpression = when (val condition = data.condition) { + is KtIsExpression -> { + val typeReference = condition.typeReference + ?: errorWithAttachment("Null type reference in condition") { withPsiEntry("ifExpression", data.ifExpression) } + + factory.createExpressionByPattern("$0 as? $1", condition.leftHandSide, typeReference) + } + + else -> data.checkedExpression + } + + return when (strategy) { + is IfThenTransformationStrategy.WrapWithLet -> data.baseClause.wrapWithLet( + newReceiverExpression, + expressionsToReplaceWithLambdaParameter = collectTextBasedUsages(data) + ) + + is IfThenTransformationStrategy.AddSafeAccess -> { + // step 1. replace variable calls with explicit invoke calls + val newBaseClause = data.baseClause.getLeftMostReceiverExpressionOrThis() + // TODO: use `OperatorToFunctionConverter.convert` instead + .replaceVariableCallsWithExplicitInvokeCalls(strategy.variableCallsToAddInvokeTo) + + // step 2. add an explicit receiver or replace the existing one + val replacedReceiver = when (strategy.transformReceiverMode) { + TransformIfThenReceiverMode.ADD_EXPLICIT_THIS -> { + val leftMostReceiver = newBaseClause.getLeftMostReceiverExpressionOrThis() + val qualified = leftMostReceiver.prependDotQualifiedReceiver(newReceiverExpression, factory) + + (qualified as KtQualifiedExpression).receiverExpression + } + + TransformIfThenReceiverMode.REPLACE_BASE_CLAUSE -> newBaseClause.replaced(newReceiverExpression) + + TransformIfThenReceiverMode.FIND_AND_REPLACE_MATCHING_RECEIVER -> { + val receiverToReplace = newBaseClause.getMatchingReceiver(data.checkedExpression.text) ?: error("") + receiverToReplace.replaced(newReceiverExpression) + } + } + + // step 3. add safe access after replaced receiver + replacedReceiver.insertSafeCallsAfterReceiver() + } + } + } + + fun buildTransformationData(ifExpression: KtIfExpression): IfThenTransformationData? { + val condition = ifExpression.condition?.getSingleUnwrappedStatement() as? KtOperationExpression ?: return null + val thenClause = ifExpression.then?.let { it.getSingleUnwrappedStatement() ?: return null } + val elseClause = ifExpression.`else`?.let { it.getSingleUnwrappedStatement() ?: return null } + val receiverExpression = condition.checkedExpression()?.getSingleUnwrappedStatement() ?: return null + + val (baseClause, negatedClause) = when (condition) { + is KtBinaryExpression -> when (condition.operationToken) { + KtTokens.EQEQ -> elseClause to thenClause + KtTokens.EXCLEQ -> thenClause to elseClause + else -> return null + } + + is KtIsExpression -> { + when (condition.isNegated) { + true -> elseClause to thenClause + false -> thenClause to elseClause + } + } + + else -> return null + } + + if (baseClause == null) return null + + return IfThenTransformationData(ifExpression, condition, receiverExpression, baseClause, negatedClause) + } + + fun KtExpression.checkedExpression(): KtExpression? = when (this) { + is KtBinaryExpression -> expressionComparedToNull() + is KtIsExpression -> leftHandSide + else -> null + } + + /** + * @return usages of [IfThenTransformationData.checkedExpression] based on its text and `KClass`, excluding usages from nested scopes + */ + fun collectTextBasedUsages(data: IfThenTransformationData): List = data.baseClause.collectDescendantsOfType( + canGoInside = { it !is KtBlockExpression }, + predicate = { it::class == data.checkedExpression::class && it.text == data.checkedExpression.text }, + ) +} + +@ApiStatus.Internal +sealed class IfThenTransformationStrategy { + abstract fun withWritableData(updater: ModPsiUpdater): IfThenTransformationStrategy + + /** + * Returns `true` if the transformation is expected to make code more Kotlin-idiomatic, and so it should be suggested. + */ + abstract fun shouldSuggestTransformation(): Boolean + + data object WrapWithLet : IfThenTransformationStrategy() { + override fun withWritableData(updater: ModPsiUpdater): WrapWithLet = WrapWithLet + + override fun shouldSuggestTransformation(): Boolean = false + } + + data class AddSafeAccess( + val variableCallsToAddInvokeTo: Set, + val transformReceiverMode: TransformIfThenReceiverMode, + val newReceiverIsSafeCast: Boolean, + ) : IfThenTransformationStrategy() { + override fun withWritableData(updater: ModPsiUpdater): AddSafeAccess = this.copy( + variableCallsToAddInvokeTo.map { updater.getWritable(it) }.toSet() + ) + + override fun shouldSuggestTransformation(): Boolean { + val newReceiverIsSafeCastInParentheses = + newReceiverIsSafeCast && transformReceiverMode != TransformIfThenReceiverMode.REPLACE_BASE_CLAUSE + + return variableCallsToAddInvokeTo.isEmpty() && !newReceiverIsSafeCastInParentheses + } + } + + companion object { + context(KtAnalysisSession) + fun create(data: IfThenTransformationData): IfThenTransformationStrategy? { + val newReceiverIsSafeCast = data.condition is KtIsExpression + + return if (data.checkedExpression is KtThisExpression && IfThenTransformationUtils.collectTextBasedUsages(data).isEmpty()) { + val leftMostReceiver = data.baseClause.getLeftMostReceiverExpressionOrThis() + if (!leftMostReceiver.hasImplicitReceiverMatchingThisExpression(data.checkedExpression)) return null + + AddSafeAccess(leftMostReceiver.collectVariableCalls(), TransformIfThenReceiverMode.ADD_EXPLICIT_THIS, newReceiverIsSafeCast) + } else { + val receiverToReplace = data.baseClause.getMatchingReceiver(data.checkedExpression.text) ?: return WrapWithLet + val variableCalls = receiverToReplace.collectVariableCalls() + + val transformReceiverMode = if (variableCalls.isEmpty() && data.baseClause.isSimplifiableTo(data.checkedExpression)) { + TransformIfThenReceiverMode.REPLACE_BASE_CLAUSE + } else TransformIfThenReceiverMode.FIND_AND_REPLACE_MATCHING_RECEIVER + + AddSafeAccess(variableCalls, transformReceiverMode, newReceiverIsSafeCast) + } + } + + context(KtAnalysisSession) + private fun KtExpression.hasImplicitReceiverMatchingThisExpression(thisExpression: KtThisExpression): Boolean { + val thisExpressionSymbol = thisExpression.instanceReference.mainReference.resolveToSymbol() ?: return false + // we need to resolve callee instead of call, because in case of variable call, call is resolved to `invoke` + val callableMemberCall = this.getCalleeExpressionIfAny()?.resolveCallableMemberCall() ?: return false + + return callableMemberCall.getImplicitReceivers().any { it.symbol == thisExpressionSymbol } + } + + context(KtAnalysisSession) + private fun KtExpression.resolveCallableMemberCall(): KtCallableMemberCall<*, *>? = this.resolveCall()?.successfulCallOrNull() + + context(KtAnalysisSession) + private fun KtExpression.collectVariableCalls(): Set = this + .parentsOfType(withSelf = true) + .mapNotNull { it.getSelectorOrThis() as? KtCallExpression } + .filter { it.resolveCall()?.singleCallOrNull()?.isImplicitInvoke == true } + .toSet() + } +} + +/** + * Note, that if [IfThenTransformationData.checkedExpression] is used in variable call, variable call will be returned, e.g., for: + * ``` + * if (a is Function0<*>) { + * a().hashCode() + * } else null + * ``` + * `a()` will be returned. + */ +private fun KtExpression.getMatchingReceiver(targetText: String): KtExpression? { + val leftMostReceiver = this.getLeftMostReceiverExpressionOrThis() + + return leftMostReceiver.parentsOfType(withSelf = true).firstOrNull { parent -> + val valueArgumentList = (parent.getSelectorOrThis() as? KtCallExpression)?.valueArgumentList + parent.text.removeSuffix(valueArgumentList?.text.orEmpty()) == targetText + } +} + +private fun KtExpression.getSelectorOrThis(): KtExpression = (this as? KtQualifiedExpression)?.selectorExpression ?: this diff --git a/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeinsights/impl/base/Utils.kt b/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeinsights/impl/base/Utils.kt index ff390b7bce17..f65057de037e 100644 --- a/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeinsights/impl/base/Utils.kt +++ b/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeinsights/impl/base/Utils.kt @@ -4,10 +4,17 @@ package org.jetbrains.kotlin.idea.codeinsights.impl.base import com.intellij.psi.search.LocalSearchScope import com.intellij.psi.search.searches.ReferencesSearch +import org.jetbrains.kotlin.builtins.StandardNames +import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggester +import org.jetbrains.kotlin.idea.base.psi.appendDotQualifiedSelector +import org.jetbrains.kotlin.idea.base.psi.getSingleUnwrappedStatementOrThis import org.jetbrains.kotlin.idea.base.psi.isOneLiner +import org.jetbrains.kotlin.idea.base.psi.replaced import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType +import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver fun KtExpression.isComplexInitializer(): Boolean { fun KtExpression.isElvisExpression(): Boolean = this is KtBinaryExpression && operationToken == KtTokens.ELVIS @@ -33,3 +40,69 @@ fun KtCallableDeclaration.hasUsages(inElements: Collection): Boolean fun KtExpression.isExitStatement(): Boolean = this is KtContinueExpression || this is KtBreakExpression || this is KtThrowExpression || this is KtReturnExpression + +fun KtExpression.isSimplifiableTo(other: KtExpression): Boolean = this.getSingleUnwrappedStatementOrThis().text == other.text + +fun KtExpression.wrapWithLet( + receiverExpression: KtExpression, + expressionsToReplaceWithLambdaParameter: List +): KtExpression { + val factory = KtPsiFactory(project) + + val implicitParameterName = StandardNames.IMPLICIT_LAMBDA_PARAMETER_NAME.identifier + val lambdaParameterName = KotlinNameSuggester.suggestNameByName(implicitParameterName) { candidate -> + collectDescendantsOfType { it.text == candidate }.isEmpty() + } + + for (expressionToReplace in expressionsToReplaceWithLambdaParameter) { + expressionToReplace.replace(factory.createExpression(lambdaParameterName)) + } + val lambdaParameterPattern = if (lambdaParameterName != implicitParameterName) "$lambdaParameterName -> " else "" + + return factory.createExpressionByPattern("$0?.let { $lambdaParameterPattern$1 }", receiverExpression, this) +} + +tailrec fun KtExpression.insertSafeCallsAfterReceiver(): KtExpression { + return when (val qualified = this.getQualifiedExpressionForReceiver()) { + is KtDotQualifiedExpression -> { + val factory = KtPsiFactory(project) + val selector = qualified.selectorExpression ?: return this + val newQualified = factory.createExpressionByPattern("$0?.$1", qualified.receiverExpression, selector) + + qualified.replaced(newQualified).insertSafeCallsAfterReceiver() + } + + is KtSafeQualifiedExpression -> qualified.insertSafeCallsAfterReceiver() + else -> this + } +} + +/** + * Replaces calls present in [variableCalls] with variable access + `invoke()`, starting with selector of [this] and continuing with calls + * that follow [this]. + * E.g., if `foo().bar()` from `foo().bar().baz()` is provided, with all calls being variable calls, + * then the selector `bar()` and the following call `baz()` are replaced, resulting in `foo().bar.invoke().baz.invoke()`. + */ +// TODO: remove this function and replace its usages with `OperatorToFunctionConverter.convert` +tailrec fun KtExpression.replaceVariableCallsWithExplicitInvokeCalls(variableCalls: Set): KtExpression { + val factory = KtPsiFactory(project) + + val callExpression = when (this) { + is KtCallExpression -> this + is KtQualifiedExpression -> selectorExpression as? KtCallExpression + else -> null + } + val valueArgumentList = callExpression?.valueArgumentList + + val newExpression = if (callExpression in variableCalls && valueArgumentList != null) { + val newInvokeCall = factory.createExpressionByPattern("invoke$0", valueArgumentList.text) + + valueArgumentList.delete() + + this.appendDotQualifiedSelector(selector = newInvokeCall, factory) + } else this + + val qualified = newExpression.getQualifiedExpressionForReceiver() ?: return newExpression + + return qualified.replaceVariableCallsWithExplicitInvokeCalls(variableCalls) +} \ No newline at end of file diff --git a/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeinsights/impl/base/applicators/ApplicabilityRanges.kt b/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeinsights/impl/base/applicators/ApplicabilityRanges.kt index df8a87277049..b233e8a169dd 100644 --- a/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeinsights/impl/base/applicators/ApplicabilityRanges.kt +++ b/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeinsights/impl/base/applicators/ApplicabilityRanges.kt @@ -6,6 +6,7 @@ import com.intellij.openapi.util.TextRange import com.intellij.psi.tree.TokenSet import com.intellij.psi.util.endOffset import com.intellij.psi.util.startOffset +import org.jetbrains.kotlin.idea.base.psi.textRangeIn import org.jetbrains.kotlin.idea.codeinsight.api.applicators.ApplicabilityRange import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* @@ -70,4 +71,16 @@ object ApplicabilityRanges { fun declarationName(element: KtNamedDeclaration) = ApplicabilityRange.single(element) { it.nameIdentifier } + + fun ifExpressionExcludingBranches(element: KtIfExpression): List { + val rightOffset = element.rightParenthesis?.endOffset + + val textRange = if (rightOffset == null) { + element.ifKeyword.textRangeIn(element) + } else { + TextRange(element.ifKeyword.startOffset, rightOffset).shiftLeft(element.startOffset) + } + + return listOf(textRange) + } } \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/resources/kotlin.code-insight.inspections.k2.xml b/plugins/kotlin/code-insight/inspections-k2/resources/kotlin.code-insight.inspections.k2.xml index 8b721c13aafe..fcc5f922b76b 100644 --- a/plugins/kotlin/code-insight/inspections-k2/resources/kotlin.code-insight.inspections.k2.xml +++ b/plugins/kotlin/code-insight/inspections-k2/resources/kotlin.code-insight.inspections.k2.xml @@ -320,6 +320,13 @@ level="INFORMATION" key="redundant.else" bundle="messages.KotlinBundle"/> + + { - val rightOffset = element.rightParenthesis?.endOffset - - val textRange = if (rightOffset == null) { - element.ifKeyword.textRangeIn(element) - } else { - TextRange(element.ifKeyword.startOffset, rightOffset).shiftLeft(element.startOffset) - } - - return listOf(textRange) - } + override fun getApplicableRanges(element: KtIfExpression): List = ApplicabilityRanges.ifExpressionExcludingBranches(element) override fun isApplicableByPsi(element: KtIfExpression): Boolean { fun KtExpression.isElvisExpression(): Boolean = this is KtBinaryExpression && operationToken == KtTokens.ELVIS diff --git a/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/IfThenToSafeAccessInspection.kt b/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/IfThenToSafeAccessInspection.kt new file mode 100644 index 000000000000..0d2149d5a99d --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/inspections/IfThenToSafeAccessInspection.kt @@ -0,0 +1,121 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.k2.codeinsight.inspections + +import com.intellij.codeInspection.CleanupLocalInspectionTool +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.codeInspection.util.InspectionMessage +import com.intellij.codeInspection.util.IntentionFamilyName +import com.intellij.codeInspection.util.IntentionName +import com.intellij.modcommand.ModPsiUpdater +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.components.KtDiagnosticCheckerFilter +import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KtFirDiagnostic +import org.jetbrains.kotlin.idea.base.psi.isNullExpression +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle +import org.jetbrains.kotlin.idea.codeInsight.IfThenTransformationUtils +import org.jetbrains.kotlin.idea.codeInsight.IfThenTransformationData +import org.jetbrains.kotlin.idea.codeInsight.IfThenTransformationStrategy +import org.jetbrains.kotlin.idea.codeInsight.TransformIfThenReceiverMode +import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinApplicableInspectionBase +import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinModCommandQuickFix +import org.jetbrains.kotlin.idea.codeinsights.impl.base.applicators.ApplicabilityRanges +import org.jetbrains.kotlin.psi.* + +internal class IfThenToSafeAccessInspection : + KotlinApplicableInspectionBase.Simple(), + CleanupLocalInspectionTool { + + override fun getProblemDescription(element: KtIfExpression, context: IfThenTransformationStrategy): @InspectionMessage String = + KotlinBundle.message("foldable.if.then") + + override fun getProblemHighlightType(element: KtIfExpression, context: IfThenTransformationStrategy): ProblemHighlightType { + if (!context.shouldSuggestTransformation()) return ProblemHighlightType.INFORMATION + + return super.getProblemHighlightType(element, context) + } + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): KtVisitor<*, *> = object : KtVisitorVoid() { + override fun visitIfExpression(expression: KtIfExpression) { + visitTargetElement(expression, holder, isOnTheFly) + } + } + + override fun getApplicableRanges(element: KtIfExpression): List = ApplicabilityRanges.ifExpressionExcludingBranches(element) + + override fun isApplicableByPsi(element: KtIfExpression): Boolean { + val data = IfThenTransformationUtils.buildTransformationData(element) ?: return false + + // negated clause is present, but it does not evaluate to `null` + if (data.negatedClause?.isNullExpression() == false) return false + + val condition = data.condition + if (condition is KtIsExpression && condition.typeReference == null) return false + + // there are no usages of expression, except possibly at nested levels, which are currently not supported + if (data.checkedExpression !is KtThisExpression && IfThenTransformationUtils.collectTextBasedUsages(data).isEmpty()) return false + + return true + } + + context(KtAnalysisSession) + override fun prepareContext(element: KtIfExpression): IfThenTransformationStrategy? { + val data = IfThenTransformationUtils.buildTransformationData(element) as IfThenTransformationData + + if (data.negatedClause == null && data.baseClause.isUsedAsExpression()) return null + + // every usage is expected to have smart cast info; + // if smart cast is unstable, replacing usage with `it` can break code logic + if (IfThenTransformationUtils.collectTextBasedUsages(data).any { it.doesNotHaveStableSmartCast() }) return null + + if (conditionIsSenseless(data)) return null + + return IfThenTransformationStrategy.create(data) + } + + override fun createQuickFix( + element: KtIfExpression, + context: IfThenTransformationStrategy, + ): KotlinModCommandQuickFix = object : KotlinModCommandQuickFix() { + override fun getFamilyName(): @IntentionFamilyName String = KotlinBundle.message("simplify.foldable.if.then") + + override fun getName(): @IntentionName String { + val transformReceiverMode = (context as? IfThenTransformationStrategy.AddSafeAccess)?.transformReceiverMode + + return if (transformReceiverMode == TransformIfThenReceiverMode.REPLACE_BASE_CLAUSE) { + if (context.newReceiverIsSafeCast) { + KotlinBundle.message("replace.if.expression.with.safe.cast.expression") + } else { + KotlinBundle.message("remove.redundant.if.expression") + } + } else { + KotlinBundle.message("replace.if.expression.with.safe.access.expression") + } + } + + override fun applyFix(project: Project, element: KtIfExpression, updater: ModPsiUpdater) { + val data = IfThenTransformationUtils.buildTransformationData(element) as IfThenTransformationData + val transformedBaseClause = IfThenTransformationUtils.transformBaseClause(data, context.withWritableData(updater)) + + element.replace(transformedBaseClause) + } + } + + context(KtAnalysisSession) + private fun KtExpression.doesNotHaveStableSmartCast(): Boolean { + val expressionToCheck = when (this) { + is KtThisExpression -> instanceReference + else -> this + } + return expressionToCheck.getSmartCastInfo()?.isStable != true + } + + + context(KtAnalysisSession) + private fun conditionIsSenseless(data: IfThenTransformationData): Boolean = data.condition + .getDiagnostics(KtDiagnosticCheckerFilter.ONLY_COMMON_CHECKERS) + .map { it.diagnosticClass } + .any { it == KtFirDiagnostic.SenselessComparison::class || it == KtFirDiagnostic.UselessIsCheck::class } +} \ No newline at end of file diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java index 09e12d5803aa..4278dd383563 100644 --- a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java +++ b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java @@ -488,6 +488,459 @@ public abstract class K2LocalInspectionTestGenerated extends AbstractK2LocalInsp } } + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess") + public static class IfThenToSafeAccess extends AbstractK2LocalInspectionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + @TestMetadata("argumentInCallWithReceiver.kt") + public void testArgumentInCallWithReceiver() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/argumentInCallWithReceiver.kt"); + } + + @TestMetadata("blockHasMoreThanOneStatement.kt") + public void testBlockHasMoreThanOneStatement() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/blockHasMoreThanOneStatement.kt"); + } + + @TestMetadata("blockUsesDifferentVar.kt") + public void testBlockUsesDifferentVar() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/blockUsesDifferentVar.kt"); + } + + @TestMetadata("call.kt") + public void testCall() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/call.kt"); + } + + @TestMetadata("call2.kt") + public void testCall2() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/call2.kt"); + } + + @TestMetadata("call3.kt") + public void testCall3() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/call3.kt"); + } + + @TestMetadata("call4.kt") + public void testCall4() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/call4.kt"); + } + + @TestMetadata("callChainWithImplicitReceiver.kt") + public void testCallChainWithImplicitReceiver() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callChainWithImplicitReceiver.kt"); + } + + @TestMetadata("callChainWithVariableCalls.kt") + public void testCallChainWithVariableCalls() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callChainWithVariableCalls.kt"); + } + + @TestMetadata("callInvokeOperator.kt") + public void testCallInvokeOperator() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator.kt"); + } + + @TestMetadata("callInvokeOperator2.kt") + public void testCallInvokeOperator2() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator2.kt"); + } + + @TestMetadata("callInvokeOperator3.kt") + public void testCallInvokeOperator3() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator3.kt"); + } + + @TestMetadata("callInvokeOperator4.kt") + public void testCallInvokeOperator4() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator4.kt"); + } + + @TestMetadata("callVariable.kt") + public void testCallVariable() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callVariable.kt"); + } + + @TestMetadata("callVariable2.kt") + public void testCallVariable2() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callVariable2.kt"); + } + + @TestMetadata("callWithNamedArgument.kt") + public void testCallWithNamedArgument() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callWithNamedArgument.kt"); + } + + @TestMetadata("conditionComparesNullWithNull.kt") + public void testConditionComparesNullWithNull() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionComparesNullWithNull.kt"); + } + + @TestMetadata("conditionInParentheses.kt") + public void testConditionInParentheses() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionInParentheses.kt"); + } + + @TestMetadata("conditionInvalidBinaryExp.kt") + public void testConditionInvalidBinaryExp() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionInvalidBinaryExp.kt"); + } + + @TestMetadata("conditionNotBinaryExpr.kt") + public void testConditionNotBinaryExpr() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionNotBinaryExpr.kt"); + } + + @TestMetadata("doesNotinlineValueIfUsedMoreThanOnce.kt") + public void testDoesNotinlineValueIfUsedMoreThanOnce() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/doesNotinlineValueIfUsedMoreThanOnce.kt"); + } + + @TestMetadata("doesNotinlineValueOutsideOfScope.kt") + public void testDoesNotinlineValueOutsideOfScope() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/doesNotinlineValueOutsideOfScope.kt"); + } + + @TestMetadata("emptyCondition.kt") + public void testEmptyCondition() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/emptyCondition.kt"); + } + + @TestMetadata("emptyElseBlock.kt") + public void testEmptyElseBlock() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/emptyElseBlock.kt"); + } + + @TestMetadata("emptyThenBlock.kt") + public void testEmptyThenBlock() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/emptyThenBlock.kt"); + } + + @TestMetadata("ifAndElseBothInBlocks.kt") + public void testIfAndElseBothInBlocks() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks.kt"); + } + + @TestMetadata("ifAndElseBothInBlocks2.kt") + public void testIfAndElseBothInBlocks2() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks2.kt"); + } + + @TestMetadata("ifAndElseNotInBlocks.kt") + public void testIfAndElseNotInBlocks() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks.kt"); + } + + @TestMetadata("ifAndElseNotInBlocks2.kt") + public void testIfAndElseNotInBlocks2() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks2.kt"); + } + + @TestMetadata("ifAsExpression.kt") + public void testIfAsExpression() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAsExpression.kt"); + } + + @TestMetadata("implicitReceiver.kt") + public void testImplicitReceiver() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiver.kt"); + } + + @TestMetadata("implicitReceiver2.kt") + public void testImplicitReceiver2() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiver2.kt"); + } + + @TestMetadata("implicitReceiver3.kt") + public void testImplicitReceiver3() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiver3.kt"); + } + + @TestMetadata("implicitReceiverInApply.kt") + public void testImplicitReceiverInApply() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiverInApply.kt"); + } + + @TestMetadata("implicitReceiverIsNotLeftMostReceiver.kt") + public void testImplicitReceiverIsNotLeftMostReceiver() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiverIsNotLeftMostReceiver.kt"); + } + + @TestMetadata("implicitReceiverVariableCall.kt") + public void testImplicitReceiverVariableCall() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiverVariableCall.kt"); + } + + @TestMetadata("isCheckSimple.kt") + public void testIsCheckSimple() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isCheckSimple.kt"); + } + + @TestMetadata("isCheckWithSelectorChain.kt") + public void testIsCheckWithSelectorChain() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isCheckWithSelectorChain.kt"); + } + + @TestMetadata("isCondition.kt") + public void testIsCondition() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isCondition.kt"); + } + + @TestMetadata("isNotCondition.kt") + public void testIsNotCondition() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNotCondition.kt"); + } + + @TestMetadata("isNotNullable.kt") + public void testIsNotNullable() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNotNullable.kt"); + } + + @TestMetadata("isNullable.kt") + public void testIsNullable() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNullable.kt"); + } + + @TestMetadata("lhsEqualsNull.kt") + public void testLhsEqualsNull() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsEqualsNull.kt"); + } + + @TestMetadata("lhsNotEqualsNull.kt") + public void testLhsNotEqualsNull() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull.kt"); + } + + @TestMetadata("lhsNotEqualsNull2.kt") + public void testLhsNotEqualsNull2() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull2.kt"); + } + + @TestMetadata("localVar.kt") + public void testLocalVar() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/localVar.kt"); + } + + @TestMetadata("missingNecessaryElseClause.kt") + public void testMissingNecessaryElseClause() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/missingNecessaryElseClause.kt"); + } + + @TestMetadata("missingNecessaryThenClause.kt") + public void testMissingNecessaryThenClause() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/missingNecessaryThenClause.kt"); + } + + @TestMetadata("multipleUsagesNullCheck.kt") + public void testMultipleUsagesNullCheck() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesNullCheck.kt"); + } + + @TestMetadata("multipleUsagesThis.kt") + public void testMultipleUsagesThis() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesThis.kt"); + } + + @TestMetadata("multipleUsagesTypeCheck.kt") + public void testMultipleUsagesTypeCheck() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesTypeCheck.kt"); + } + + @TestMetadata("multipleUsagesVariableCalls.kt") + public void testMultipleUsagesVariableCalls() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesVariableCalls.kt"); + } + + @TestMetadata("noCondition.kt") + public void testNoCondition() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noCondition.kt"); + } + + @TestMetadata("noElseBlock.kt") + public void testNoElseBlock() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock.kt"); + } + + @TestMetadata("noElseBlock2.kt") + public void testNoElseBlock2() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock2.kt"); + } + + @TestMetadata("noElseBlockAsExpression.kt") + public void testNoElseBlockAsExpression() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlockAsExpression.kt"); + } + + @TestMetadata("noNullInCondition.kt") + public void testNoNullInCondition() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noNullInCondition.kt"); + } + + @TestMetadata("noNullInCondition2.kt") + public void testNoNullInCondition2() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noNullInCondition2.kt"); + } + + @TestMetadata("noStdResultInCallChain.kt") + public void testNoStdResultInCallChain() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noStdResultInCallChain.kt"); + } + + @TestMetadata("noThenBlock.kt") + public void testNoThenBlock() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock.kt"); + } + + @TestMetadata("noThenBlock2.kt") + public void testNoThenBlock2() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock2.kt"); + } + + @TestMetadata("notApplicableForFunction.kt") + public void testNotApplicableForFunction() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableForFunction.kt"); + } + + @TestMetadata("notApplicableForNonLocalVar.kt") + public void testNotApplicableForNonLocalVar() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableForNonLocalVar.kt"); + } + + @TestMetadata("notApplicableResultInCallChain.kt") + public void testNotApplicableResultInCallChain() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableResultInCallChain.kt"); + } + + @TestMetadata("nullCheckSimple.kt") + public void testNullCheckSimple() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/nullCheckSimple.kt"); + } + + @TestMetadata("nullCheckWithSelectorCallChain.kt") + public void testNullCheckWithSelectorCallChain() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/nullCheckWithSelectorCallChain.kt"); + } + + @TestMetadata("otherBlockHasMoreThanOneStatement.kt") + public void testOtherBlockHasMoreThanOneStatement() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/otherBlockHasMoreThanOneStatement.kt"); + } + + @TestMetadata("property.kt") + public void testProperty() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/property.kt"); + } + + @TestMetadata("propertyNotNull.kt") + public void testPropertyNotNull() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyNotNull.kt"); + } + + @TestMetadata("propertyVariableCall.kt") + public void testPropertyVariableCall() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyVariableCall.kt"); + } + + @TestMetadata("propertyWithProperty.kt") + public void testPropertyWithProperty() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyWithProperty.kt"); + } + + @TestMetadata("replaceWithLetInMember.kt") + public void testReplaceWithLetInMember() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/replaceWithLetInMember.kt"); + } + + @TestMetadata("replaceWithLetInMemberWithIs.kt") + public void testReplaceWithLetInMemberWithIs() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/replaceWithLetInMemberWithIs.kt"); + } + + @TestMetadata("resultCall.kt") + public void testResultCall() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/resultCall.kt"); + } + + @TestMetadata("rhsEqualsNull.kt") + public void testRhsEqualsNull() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull.kt"); + } + + @TestMetadata("rhsEqualsNull2.kt") + public void testRhsEqualsNull2() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull2.kt"); + } + + @TestMetadata("rhsNotEqualsNull.kt") + public void testRhsNotEqualsNull() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull.kt"); + } + + @TestMetadata("rhsNotEqualsNull2.kt") + public void testRhsNotEqualsNull2() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull2.kt"); + } + + @TestMetadata("senselessComparison.kt") + public void testSenselessComparison() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/senselessComparison.kt"); + } + + @TestMetadata("thenAndElseBothNull.kt") + public void testThenAndElseBothNull() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/thenAndElseBothNull.kt"); + } + + @TestMetadata("thenAndElseNotNull.kt") + public void testThenAndElseNotNull() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/thenAndElseNotNull.kt"); + } + + @TestMetadata("unacceptableEmptyElseBlock.kt") + public void testUnacceptableEmptyElseBlock() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/unacceptableEmptyElseBlock.kt"); + } + + @TestMetadata("unacceptableEmptyThenBlock.kt") + public void testUnacceptableEmptyThenBlock() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/unacceptableEmptyThenBlock.kt"); + } + + @TestMetadata("unacceptableNoElseBlock.kt") + public void testUnacceptableNoElseBlock() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/unacceptableNoElseBlock.kt"); + } + + @TestMetadata("unacceptableNoThenBlock.kt") + public void testUnacceptableNoThenBlock() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/unacceptableNoThenBlock.kt"); + } + + @TestMetadata("usageInBlock.kt") + public void testUsageInBlock() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/usageInBlock.kt"); + } + + @TestMetadata("usageInBlock2.kt") + public void testUsageInBlock2() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/usageInBlock2.kt"); + } + + @TestMetadata("uselessIsCheck.kt") + public void testUselessIsCheck() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/uselessIsCheck.kt"); + } + + @TestMetadata("willNotInlineClassProperty.kt") + public void testWillNotInlineClassProperty() throws Exception { + runTest("../../../idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/willNotInlineClassProperty.kt"); + } + } + @RunWith(JUnit3RunnerWithInners.class) @TestMetadata("../../../idea/tests/testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet") public static class ReplaceGetOrSet extends AbstractK2LocalInspectionTest { diff --git a/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/IfToWhenIntention.kt b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/IfToWhenIntention.kt index 9e227cf772ee..9c0497aa23c6 100644 --- a/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/IfToWhenIntention.kt +++ b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/IfToWhenIntention.kt @@ -11,7 +11,7 @@ import org.jetbrains.kotlin.analysis.api.analyze import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings import org.jetbrains.kotlin.idea.base.psi.replaced -import org.jetbrains.kotlin.idea.base.psi.unwrapBlockOrParenthesis +import org.jetbrains.kotlin.idea.base.psi.getSingleUnwrappedStatementOrThis import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.base.util.reformat import org.jetbrains.kotlin.idea.codeinsight.api.applicable.intentions.KotlinApplicableModCommandAction @@ -205,7 +205,7 @@ internal class IfToWhenIntention : KotlinApplicableModCommandAction.appendElseBlock(block: KtExpression?, unwrapBlockOrParenthesis: Boolean = false) { appendFixedText("else->") - appendExpression(if (unwrapBlockOrParenthesis) block?.unwrapBlockOrParenthesis() else block) + appendExpression(if (unwrapBlockOrParenthesis) block?.getSingleUnwrappedStatementOrThis() else block) appendFixedText("\n") } diff --git a/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/KotlinPsiUtils.kt b/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/KotlinPsiUtils.kt index 48f25eefa01f..0d863b239878 100644 --- a/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/KotlinPsiUtils.kt +++ b/plugins/kotlin/code-insight/utils/src/org/jetbrains/kotlin/idea/codeinsight/utils/KotlinPsiUtils.kt @@ -204,8 +204,11 @@ private fun getNegatedOperatorText(token: IElementType): String { return negatedOperator.value } -fun KtDotQualifiedExpression.getLeftMostReceiverExpression(): KtExpression = - (receiverExpression as? KtDotQualifiedExpression)?.getLeftMostReceiverExpression() ?: receiverExpression +fun KtQualifiedExpression.getLeftMostReceiverExpression(): KtExpression = + (receiverExpression as? KtQualifiedExpression)?.getLeftMostReceiverExpression() ?: receiverExpression + +fun KtExpression.getLeftMostReceiverExpressionOrThis(): KtExpression = + (this as? KtQualifiedExpression)?.getLeftMostReceiverExpression() ?: this fun KtDotQualifiedExpression.replaceFirstReceiver( factory: KtPsiFactory, diff --git a/plugins/kotlin/compiler-plugins/parcelize/common/src/org/jetbrains/kotlin/idea/compilerPlugin/parcelize/quickfixes/ParcelMigrateToParcelizeQuickFixApplicator.kt b/plugins/kotlin/compiler-plugins/parcelize/common/src/org/jetbrains/kotlin/idea/compilerPlugin/parcelize/quickfixes/ParcelMigrateToParcelizeQuickFixApplicator.kt index 6e88451e881c..c916f90e020e 100644 --- a/plugins/kotlin/compiler-plugins/parcelize/common/src/org/jetbrains/kotlin/idea/compilerPlugin/parcelize/quickfixes/ParcelMigrateToParcelizeQuickFixApplicator.kt +++ b/plugins/kotlin/compiler-plugins/parcelize/common/src/org/jetbrains/kotlin/idea/compilerPlugin/parcelize/quickfixes/ParcelMigrateToParcelizeQuickFixApplicator.kt @@ -5,7 +5,7 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.psi.SmartPsiElementPointer import com.intellij.psi.createSmartPointer import org.jetbrains.kotlin.idea.base.psi.getOrCreateCompanionObject -import org.jetbrains.kotlin.idea.base.psi.unwrapBlockOrParenthesis +import org.jetbrains.kotlin.idea.base.psi.getSingleUnwrappedStatementOrThis import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName @@ -181,7 +181,7 @@ class ParcelMigrateToParcelizeQuickFixApplicator( ?.toList() ?: emptyList() val describeContentsFunctionToRemove = parcelableClass.findDescribeContentsOverride()?.takeIf { - val returnExpr = it.bodyExpression?.unwrapBlockOrParenthesis() + val returnExpr = it.bodyExpression?.getSingleUnwrappedStatementOrThis() return@takeIf ( returnExpr is KtReturnExpression && returnExpr.getTargetLabel() == null diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ConstantConditionIfInspection.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ConstantConditionIfInspection.kt index de42537e4d5a..ba4420135b42 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ConstantConditionIfInspection.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ConstantConditionIfInspection.kt @@ -11,7 +11,7 @@ import com.intellij.psi.search.LocalSearchScope import com.intellij.psi.search.searches.ReferencesSearch import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.idea.base.psi.replaced -import org.jetbrains.kotlin.idea.base.psi.unwrapBlockOrParenthesis +import org.jetbrains.kotlin.idea.base.psi.getSingleUnwrappedStatementOrThis import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection @@ -66,7 +66,7 @@ private class SimplifyFix( override fun applyFix(ifExpression: KtIfExpression) { val branch = ifExpression.branch(conditionValue)?.let { - if (keepBraces) it else it.unwrapBlockOrParenthesis() + if (keepBraces) it else it.getSingleUnwrappedStatementOrThis() } ?: return ifExpression.replaceWithBranch(branch, isUsedAsExpression, keepBraces) } diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ConvertTwoComparisonsToRangeCheckInspection.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ConvertTwoComparisonsToRangeCheckInspection.kt index b5b235d0e4ad..ba9c7a3950b1 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ConvertTwoComparisonsToRangeCheckInspection.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/ConvertTwoComparisonsToRangeCheckInspection.kt @@ -10,7 +10,7 @@ import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyzeAsReplacement import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractApplicabilityBasedInspection -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.evaluatesTo +import org.jetbrains.kotlin.idea.codeinsights.impl.base.isSimplifiableTo import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType @@ -95,7 +95,7 @@ class ConvertTwoComparisonsToRangeCheckInspection : firstLess: KtExpression, firstGreater: KtExpression, firstStrict: Boolean, secondLess: KtExpression, secondGreater: KtExpression, secondStrict: Boolean ) = when { - firstGreater !is KtConstantExpression && firstGreater.evaluatesTo(secondLess) -> + firstGreater !is KtConstantExpression && firstGreater.isSimplifiableTo(secondLess) -> generateRangeExpressionData( firstGreater, min = firstLess, @@ -103,7 +103,7 @@ class ConvertTwoComparisonsToRangeCheckInspection : incrementMinByOne = firstStrict, decrementMaxByOne = secondStrict ) - firstLess !is KtConstantExpression && firstLess.evaluatesTo(secondGreater) -> + firstLess !is KtConstantExpression && firstLess.isSimplifiableTo(secondGreater) -> generateRangeExpressionData( firstLess, min = secondLess, diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/SuspiciousEqualsCombination.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/SuspiciousEqualsCombination.kt index baac837a74bd..d42aef718c42 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/SuspiciousEqualsCombination.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/SuspiciousEqualsCombination.kt @@ -6,7 +6,7 @@ import com.intellij.codeInspection.LocalInspectionToolSession import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder import org.jetbrains.kotlin.idea.base.resources.KotlinBundle -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression +import org.jetbrains.kotlin.idea.base.psi.isNullExpression import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToElvisInspection.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToElvisInspection.kt index 149924ba6138..3fd3ec461ff8 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToElvisInspection.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToElvisInspection.kt @@ -9,17 +9,22 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.idea.base.psi.getSingleUnwrappedStatementOrThis import org.jetbrains.kotlin.idea.base.psi.replaced -import org.jetbrains.kotlin.idea.base.psi.unwrapBlockOrParenthesis import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.safeAnalyzeNonSourceRootCode +import org.jetbrains.kotlin.idea.codeInsight.IfThenTransformationUtils +import org.jetbrains.kotlin.idea.codeInsight.IfThenTransformationData import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractApplicabilityBasedInspection +import org.jetbrains.kotlin.idea.codeinsights.impl.base.isSimplifiableTo import org.jetbrains.kotlin.idea.formatter.rightMarginOrDefault import org.jetbrains.kotlin.idea.inspections.branchedTransformations.IfThenToSafeAccessInspection.Util.renameLetParameter import org.jetbrains.kotlin.idea.intentions.branchedTransformations.* import org.jetbrains.kotlin.idea.util.CommentSaver import org.jetbrains.kotlin.idea.util.application.runWriteActionIfPhysical import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression import org.jetbrains.kotlin.resolve.calls.util.getType import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode @@ -54,14 +59,15 @@ class IfThenToElvisInspection @JvmOverloads constructor( val INTENTION_TEXT get() = KotlinBundle.message("replace.if.expression.with.elvis.expression") fun convert(element: KtIfExpression, editor: Editor?, inlineWithPrompt: Boolean) { - val ifThenToSelectData = element.buildSelectTransformationData() ?: return + val ifThenToSelectData = IfThenTransformationUtils.buildTransformationData(element) ?: return + val context = element.safeAnalyzeNonSourceRootCode() val psiFactory = KtPsiFactory(element.project) val commentSaver = CommentSaver(element, saveLineBreaks = false) val margin = element.containingKtFile.rightMarginOrDefault val elvis = runWriteActionIfPhysical(element) { - val replacedBaseClause = ifThenToSelectData.replacedBaseClause(psiFactory) + val replacedBaseClause = ifThenToSelectData.replacedBaseClause(psiFactory, context) val negatedClause = ifThenToSelectData.negatedClause!! val newExpr = element.replaced( psiFactory.createExpressionByPattern( @@ -83,35 +89,36 @@ class IfThenToElvisInspection @JvmOverloads constructor( } fun isApplicableTo(element: KtIfExpression, expressionShouldBeStable: Boolean): Boolean { - val ifThenToSelectData = element.buildSelectTransformationData() ?: return false + val ifThenToSelectData = IfThenTransformationUtils.buildTransformationData(element) ?: return false + val context = element.safeAnalyzeNonSourceRootCode() if (expressionShouldBeStable && - !ifThenToSelectData.receiverExpression.isStableSimpleExpression(ifThenToSelectData.context) + !ifThenToSelectData.checkedExpression.isStableSimpleExpression(context) ) return false - val type = element.getType(ifThenToSelectData.context) ?: return false + val type = element.getType(context) ?: return false if (KotlinBuiltIns.isUnit(type)) return false - return ifThenToSelectData.clausesReplaceableByElvis() + return ifThenToSelectData.clausesReplaceableByElvis(context) } private fun KtExpression.isNullOrBlockExpression(): Boolean { - val innerExpression = this.unwrapBlockOrParenthesis() + val innerExpression = this.getSingleUnwrappedStatementOrThis() return innerExpression is KtBlockExpression || innerExpression.node.elementType == KtNodeTypes.NULL } - private fun IfThenToSelectData.clausesReplaceableByElvis(): Boolean = + private fun IfThenTransformationData.clausesReplaceableByElvis(context: BindingContext): Boolean = when { - negatedClause == null || negatedClause.isNullOrBlockExpression() -> + negatedClause == null || negatedClause?.isNullOrBlockExpression() == true -> false - negatedClause is KtThrowExpression && negatedClause.throwsNullPointerExceptionWithNoArguments() -> + (negatedClause as? KtThrowExpression)?.throwsNullPointerExceptionWithNoArguments() == true -> false - conditionHasIncompatibleTypes() -> + conditionHasIncompatibleTypes(context) -> false - baseClause.evaluatesTo(receiverExpression) -> + baseClause.isSimplifiableTo(checkedExpression) -> true - baseClause.anyArgumentEvaluatesTo(receiverExpression) -> + baseClause.anyArgumentEvaluatesTo(checkedExpression) -> true - hasImplicitReceiverReplaceableBySafeCall() || baseClause.hasFirstReceiverOf(receiverExpression) -> + hasImplicitReceiverReplaceableBySafeCall(context) || baseClause.hasFirstReceiverOf(checkedExpression) -> !baseClause.hasNullableType(context) else -> false diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToSafeAccessInspection.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToSafeAccessInspection.kt index 6da5c8818e22..2f917e26bc74 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToSafeAccessInspection.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/branchedTransformations/IfThenToSafeAccessInspection.kt @@ -11,10 +11,13 @@ import org.jetbrains.annotations.Nls import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.idea.base.psi.replaced import org.jetbrains.kotlin.idea.base.resources.KotlinBundle +import org.jetbrains.kotlin.idea.caches.resolve.safeAnalyzeNonSourceRootCode +import org.jetbrains.kotlin.idea.codeInsight.IfThenTransformationUtils +import org.jetbrains.kotlin.idea.codeInsight.IfThenTransformationData import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractApplicabilityBasedInspection +import org.jetbrains.kotlin.idea.codeinsights.impl.base.isSimplifiableTo import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.idea.intentions.branchedTransformations.* -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.evaluatesTo import org.jetbrains.kotlin.idea.intentions.callExpression import org.jetbrains.kotlin.idea.refactoring.rename.KotlinVariableInplaceRenameHandler import org.jetbrains.kotlin.name.FqName @@ -49,8 +52,8 @@ class IfThenToSafeAccessInspection @JvmOverloads constructor(private val inlineW object Util { @Nls fun fixTextFor(element: KtIfExpression): String { - val ifThenToSelectData = element.buildSelectTransformationData() - return if (ifThenToSelectData?.let { it.baseClause.evaluatesTo(it.receiverExpression) } == true) { + val ifThenToSelectData = IfThenTransformationUtils.buildTransformationData(element) + return if (ifThenToSelectData?.let { it.baseClause.isSimplifiableTo(it.checkedExpression) } == true) { if (ifThenToSelectData.condition is KtIsExpression) { KotlinBundle.message("replace.if.expression.with.safe.cast.expression") } else { @@ -62,11 +65,12 @@ class IfThenToSafeAccessInspection @JvmOverloads constructor(private val inlineW } fun convert(ifExpression: KtIfExpression, editor: Editor?, inlineWithPrompt: Boolean) { - val ifThenToSelectData = ifExpression.buildSelectTransformationData() ?: return + val ifThenToSelectData = IfThenTransformationUtils.buildTransformationData(ifExpression) ?: return + val context = ifExpression.safeAnalyzeNonSourceRootCode() val psiFactory = KtPsiFactory(ifExpression.project) val resultExpr = runWriteAction { - val replacedBaseClause = ifThenToSelectData.replacedBaseClause(psiFactory) + val replacedBaseClause = ifThenToSelectData.replacedBaseClause(psiFactory, context) val newExpr = ifExpression.replaced(replacedBaseClause) KtPsiUtil.deparenthesize(newExpr) } @@ -78,12 +82,13 @@ class IfThenToSafeAccessInspection @JvmOverloads constructor(private val inlineW } fun isApplicableTo(element: KtIfExpression, expressionShouldBeStable: Boolean): Boolean { - val ifThenToSelectData = element.buildSelectTransformationData() ?: return false + val ifThenToSelectData = IfThenTransformationUtils.buildTransformationData(element) ?: return false + val context = element.safeAnalyzeNonSourceRootCode() if (expressionShouldBeStable && - !ifThenToSelectData.receiverExpression.isStableSimpleExpression(ifThenToSelectData.context) + !ifThenToSelectData.checkedExpression.isStableSimpleExpression(context) ) return false - return ifThenToSelectData.clausesReplaceableBySafeCall() + return ifThenToSelectData.clausesReplaceableBySafeCall(context) } internal fun KtSafeQualifiedExpression.renameLetParameter(editor: Editor) { @@ -97,18 +102,18 @@ class IfThenToSafeAccessInspection @JvmOverloads constructor(private val inlineW } } -private fun IfThenToSelectData.clausesReplaceableBySafeCall(): Boolean = when { - conditionHasIncompatibleTypes() -> false +private fun IfThenTransformationData.clausesReplaceableBySafeCall(context: BindingContext): Boolean = when { + conditionHasIncompatibleTypes(context) -> false negatedClause == null && baseClause.isUsedAsExpression(context) -> false negatedClause != null && !negatedClause.isNullExpression() -> false context.diagnostics.forElement(condition) .any { it.factory == Errors.SENSELESS_COMPARISON || it.factory == Errors.USELESS_IS_CHECK } -> false - baseClause.evaluatesTo(receiverExpression) -> true - (baseClause as? KtCallExpression)?.calleeExpression?.evaluatesTo(receiverExpression) == true + baseClause.isSimplifiableTo(checkedExpression) -> true + (baseClause as? KtCallExpression)?.calleeExpression?.isSimplifiableTo(checkedExpression) == true && baseClause.isCallingInvokeFunction(context) -> true - baseClause.hasFirstReceiverOf(receiverExpression) -> withoutResultInCallChain(baseClause, context) - baseClause.anyArgumentEvaluatesTo(receiverExpression) -> true - receiverExpression is KtThisExpression -> getImplicitReceiver()?.let { it.type == receiverExpression.getType(context) } == true + baseClause.hasFirstReceiverOf(checkedExpression) -> withoutResultInCallChain(baseClause, context) + baseClause.anyArgumentEvaluatesTo(checkedExpression) -> true + checkedExpression is KtThisExpression -> getImplicitReceiver(context)?.let { it.type == checkedExpression.getType(context) } == true else -> false } diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/substring/ReplaceSubstringInspection.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/substring/ReplaceSubstringInspection.kt index 83d531a5ab98..ccf9699b5211 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/substring/ReplaceSubstringInspection.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/substring/ReplaceSubstringInspection.kt @@ -4,7 +4,7 @@ package org.jetbrains.kotlin.idea.inspections.substring import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractApplicabilityBasedInspection -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.evaluatesTo +import org.jetbrains.kotlin.idea.codeinsights.impl.base.isSimplifiableTo import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isStableSimpleExpression import org.jetbrains.kotlin.idea.intentions.callExpression import org.jetbrains.kotlin.idea.intentions.toResolvedCall @@ -32,7 +32,7 @@ abstract class ReplaceSubstringInspection : AbstractApplicabilityBasedInspection protected fun isIndexOfCall(expression: KtExpression?, expectedReceiver: KtExpression): Boolean { return expression is KtDotQualifiedExpression && expression.isMethodCall("kotlin.text.indexOf") - && expression.receiverExpression.evaluatesTo(expectedReceiver) + && expression.receiverExpression.isSimplifiableTo(expectedReceiver) && expression.callExpression!!.valueArguments.size == 1 } diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/substring/ReplaceSubstringWithDropLastInspection.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/substring/ReplaceSubstringWithDropLastInspection.kt index 1cdedb3148f5..79d96d1513a7 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/substring/ReplaceSubstringWithDropLastInspection.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/inspections/substring/ReplaceSubstringWithDropLastInspection.kt @@ -5,7 +5,7 @@ package org.jetbrains.kotlin.idea.inspections.substring import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import org.jetbrains.kotlin.idea.base.resources.KotlinBundle -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.evaluatesTo +import org.jetbrains.kotlin.idea.codeinsights.impl.base.isSimplifiableTo import org.jetbrains.kotlin.idea.intentions.callExpression import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtBinaryExpression @@ -39,5 +39,5 @@ class ReplaceSubstringWithDropLastInspection : ReplaceSubstringInspection() { private fun isLengthAccess(expression: KtExpression?, expectedReceiver: KtExpression): Boolean = expression is KtDotQualifiedExpression && expression.selectorExpression.let { it is KtNameReferenceExpression && it.getReferencedName() == "length" } - && expression.receiverExpression.evaluatesTo(expectedReceiver) + && expression.receiverExpression.isSimplifiableTo(expectedReceiver) } diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertNullablePropertyToLateinitIntention.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertNullablePropertyToLateinitIntention.kt index 2524a8dd05d3..f48b2bf9b99d 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertNullablePropertyToLateinitIntention.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertNullablePropertyToLateinitIntention.kt @@ -12,7 +12,7 @@ import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.codeinsight.api.classic.intentions.SelfTargetingIntention import org.jetbrains.kotlin.idea.core.setType -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression +import org.jetbrains.kotlin.idea.base.psi.isNullExpression import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtNullableType import org.jetbrains.kotlin.psi.KtProperty diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/BranchedFoldingUtils.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/BranchedFoldingUtils.kt index a12e898f2ead..09b111f21c27 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/BranchedFoldingUtils.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/BranchedFoldingUtils.kt @@ -4,6 +4,7 @@ package org.jetbrains.kotlin.idea.intentions.branchedTransformations import org.jetbrains.kotlin.cfg.WhenChecker import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.idea.base.psi.isNullExpression import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.safeAnalyzeNonSourceRootCode import org.jetbrains.kotlin.idea.base.psi.replaced diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/IfThenUtils.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/IfThenUtils.kt index 927a69e83bcd..d74ac1bb144f 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/IfThenUtils.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/IfThenUtils.kt @@ -13,16 +13,19 @@ import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggester -import org.jetbrains.kotlin.idea.base.psi.expressionComparedToNull +import org.jetbrains.kotlin.idea.base.psi.getSingleUnwrappedStatementOrThis +import org.jetbrains.kotlin.idea.base.psi.isNullExpression import org.jetbrains.kotlin.idea.base.psi.replaced import org.jetbrains.kotlin.idea.base.psi.textRangeIn -import org.jetbrains.kotlin.idea.base.psi.unwrapBlockOrParenthesis import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall import org.jetbrains.kotlin.idea.caches.resolve.safeAnalyzeNonSourceRootCode +import org.jetbrains.kotlin.idea.codeInsight.IfThenTransformationData +import org.jetbrains.kotlin.idea.codeInsight.IfThenTransformationUtils.checkedExpression import org.jetbrains.kotlin.idea.codeinsight.utils.getLeftMostReceiverExpression import org.jetbrains.kotlin.idea.codeinsight.utils.replaceFirstReceiver +import org.jetbrains.kotlin.idea.codeinsights.impl.base.isSimplifiableTo import org.jetbrains.kotlin.idea.intentions.callExpression import org.jetbrains.kotlin.idea.refactoring.inline.KotlinInlinePropertyHandler import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.K1IntroduceVariableHandler @@ -50,12 +53,12 @@ import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments import org.jetbrains.kotlin.utils.addToStdlib.constant -fun KtExpression?.isTrivialStatementBody(): Boolean = when (this?.unwrapBlockOrParenthesis()) { +fun KtExpression?.isTrivialStatementBody(): Boolean = when (this?.getSingleUnwrappedStatementOrThis()) { is KtIfExpression, is KtBlockExpression -> false else -> true } -fun KtExpression?.isNullExpression(): Boolean = this?.unwrapBlockOrParenthesis()?.node?.elementType == KtNodeTypes.NULL +fun KtExpression?.isNullExpression(): Boolean = isNullExpression() fun KtExpression?.isNullExpressionOrEmptyBlock(): Boolean = this.isNullExpression() || this is KtBlockExpression && this.statements.isEmpty() @@ -77,13 +80,10 @@ fun KtThrowExpression.throwsNullPointerExceptionWithNoArguments(): Boolean { } && thrownExpression.valueArguments.isEmpty() } -fun KtExpression.evaluatesTo(other: KtExpression): Boolean = - this.unwrapBlockOrParenthesis().text == other.text - fun KtExpression.anyArgumentEvaluatesTo(argument: KtExpression): Boolean { val callExpression = this as? KtCallExpression ?: return false val arguments = callExpression.valueArguments.map { it.getArgumentExpression() } - return arguments.any { it?.evaluatesTo(argument) == true } && arguments.all { it is KtNameReferenceExpression } + return arguments.any { it?.isSimplifiableTo(argument) == true } && arguments.all { it is KtNameReferenceExpression } } fun KtExpression.convertToIfNotNullExpression( @@ -171,161 +171,128 @@ private fun KtExpression.toDataFlowValue(context: BindingContext): DataFlowValue return dataFlowValueFactory.createDataFlowValue(this, expressionType, context, findModuleDescriptor()) } -data class IfThenToSelectData( - val context: BindingContext, - val condition: KtOperationExpression, - val receiverExpression: KtExpression, - val baseClause: KtExpression, - val negatedClause: KtExpression? -) { - internal fun replacedBaseClause(factory: KtPsiFactory): KtExpression { - val newReceiver = (condition as? KtIsExpression)?.let { - factory.createExpressionByPattern( - "$0 as? $1", - it.leftHandSide, - it.typeReference!! - ) - } +internal fun IfThenTransformationData.replacedBaseClause(factory: KtPsiFactory, context: BindingContext): KtExpression { + val baseClause = baseClause + val newReceiver = (condition as? KtIsExpression)?.let { + factory.createExpressionByPattern( + "$0 as? $1", + it.leftHandSide, + it.typeReference!! + ) + } - return if (baseClause.evaluatesTo(receiverExpression)) { - if (condition is KtIsExpression) newReceiver!! else baseClause - } else { - when { - condition is KtIsExpression -> { - when { - baseClause is KtDotQualifiedExpression -> baseClause.replaceFirstReceiver( - factory, newReceiver!!, safeAccess = true - ) + return if (baseClause.isSimplifiableTo(checkedExpression)) { + if (condition is KtIsExpression) newReceiver!! else baseClause + } else { + when { + condition is KtIsExpression -> { + when { + baseClause is KtDotQualifiedExpression -> baseClause.replaceFirstReceiver( + factory, newReceiver!!, safeAccess = true + ) - hasImplicitReceiverReplaceableBySafeCall() -> factory.createExpressionByPattern( - "$0?.$1", - newReceiver!!, - baseClause - ).insertSafeCalls( - factory - ) + hasImplicitReceiverReplaceableBySafeCall(context) -> factory.createExpressionByPattern( + "$0?.$1", + newReceiver!!, + baseClause + ).insertSafeCalls( + factory + ) - baseClause is KtCallExpression -> baseClause.replaceCallWithLet(newReceiver!!, factory) - else -> error("Illegal state") - } - } - - hasImplicitReceiverReplaceableBySafeCall() -> factory.createExpressionByPattern( - "$0?.$1", - receiverExpression, - baseClause - ).insertSafeCalls(factory) - - baseClause is KtCallExpression -> { - val callee = baseClause.calleeExpression - if (callee != null && baseClause.isCallingInvokeFunction(context)) { - factory.createExpressionByPattern("$0?.invoke()", callee) - } else { - baseClause.replaceCallWithLet(receiverExpression, factory) - } - } - - else -> { - var replaced = baseClause.insertSafeCalls(factory) - if (replaced is KtQualifiedExpression) { - val call = replaced.callExpression - val callee = call?.calleeExpression - if (callee != null && callee.text != "invoke" && call.isCallingInvokeFunction(context)) { - replaced = factory.createExpressionByPattern("$0?.$1?.invoke()", replaced.receiverExpression, callee) - } - } - replaced + baseClause is KtCallExpression -> baseClause.replaceCallWithLet(newReceiver!!, checkedExpression, factory) + else -> error("Illegal state") } } - } - } - internal fun KtExpression.isCallingInvokeFunction(context: BindingContext): Boolean { - if (this !is KtCallExpression) return false - val resolvedCall = getResolvedCall(context) ?: resolveToCall() ?: return false - val descriptor = resolvedCall.resultingDescriptor as? SimpleFunctionDescriptor ?: return false - return descriptor is FunctionInvokeDescriptor || descriptor.isOperator && descriptor.name.asString() == "invoke" - } + hasImplicitReceiverReplaceableBySafeCall(context) -> factory.createExpressionByPattern( + "$0?.$1", + checkedExpression, + baseClause + ).insertSafeCalls(factory) - internal fun getImplicitReceiver(): ImplicitReceiver? { - val resolvedCall = baseClause.getResolvedCall(context) ?: return null - if (resolvedCall.getExplicitReceiverValue() != null) return null - return resolvedCall.getImplicitReceiverValue() - } - - internal fun hasImplicitReceiverReplaceableBySafeCall(): Boolean = - receiverExpression is KtThisExpression && getImplicitReceiver() != null - - private fun KtCallExpression.replaceCallWithLet( - receiver: KtExpression, - factory: KtPsiFactory - ): KtExpression { - val needExplicitParameter = valueArguments.any { it.getArgumentExpression()?.text == StandardNames.IMPLICIT_LAMBDA_PARAMETER_NAME.identifier } - val parameterName = if (needExplicitParameter) { - val scope = getResolutionScope() - KotlinNameSuggester.suggestNameByName("it") { scope.findVariable(Name.identifier(it), NoLookupLocation.FROM_IDE) == null } - } else { - "it" - } - return factory.buildExpression { - appendExpression(receiver) - appendFixedText("?.let {") - if (needExplicitParameter) appendFixedText(" $parameterName ->") - appendExpression(calleeExpression) - appendFixedText("(") - valueArguments.forEachIndexed { index, arg -> - if (index != 0) appendFixedText(", ") - val argName = arg.getArgumentName()?.asName - if (argName != null) { - appendName(argName) - appendFixedText(" = ") + baseClause is KtCallExpression -> { + val callee = baseClause.calleeExpression + if (callee != null && baseClause.isCallingInvokeFunction(context)) { + factory.createExpressionByPattern("$0?.invoke()", callee) + } else { + baseClause.replaceCallWithLet(checkedExpression, checkedExpression, factory) } - val argExpression = arg.getArgumentExpression() - if (argExpression?.evaluatesTo(receiverExpression) == true) - appendFixedText(parameterName) - else - appendExpression(argExpression) } - appendFixedText(") }") + + else -> { + var replaced = baseClause.insertSafeCalls(factory) + if (replaced is KtQualifiedExpression) { + val call = replaced.callExpression + val callee = call?.calleeExpression + if (callee != null && callee.text != "invoke" && call.isCallingInvokeFunction(context)) { + replaced = factory.createExpressionByPattern("$0?.$1?.invoke()", replaced.receiverExpression, callee) + } + } + replaced + } } } } -internal fun KtIfExpression.buildSelectTransformationData(): IfThenToSelectData? { - val context = safeAnalyzeNonSourceRootCode() - - val condition = condition?.unwrapBlockOrParenthesis() as? KtOperationExpression ?: return null - val thenClause = then?.unwrapBlockOrParenthesis() - val elseClause = `else`?.unwrapBlockOrParenthesis() - val receiverExpression = condition.checkedExpression()?.unwrapBlockOrParenthesis() ?: return null - - val (baseClause, negatedClause) = when (condition) { - is KtBinaryExpression -> when (condition.operationToken) { - KtTokens.EQEQ -> elseClause to thenClause - KtTokens.EXCLEQ -> thenClause to elseClause - else -> return null - } - is KtIsExpression -> { - when (condition.isNegated) { - true -> elseClause to thenClause - false -> thenClause to elseClause - } - } - else -> return null - } - - if (baseClause == null) return null - - return IfThenToSelectData(context, condition, receiverExpression, baseClause, negatedClause) +internal fun KtExpression.isCallingInvokeFunction(context: BindingContext): Boolean { + if (this !is KtCallExpression) return false + val resolvedCall = getResolvedCall(context) ?: resolveToCall() ?: return false + val descriptor = resolvedCall.resultingDescriptor as? SimpleFunctionDescriptor ?: return false + return descriptor is FunctionInvokeDescriptor || descriptor.isOperator && descriptor.name.asString() == "invoke" } -internal fun IfThenToSelectData.conditionHasIncompatibleTypes(): Boolean { - if (condition !is KtIsExpression) return false +internal fun IfThenTransformationData.getImplicitReceiver(context: BindingContext): ImplicitReceiver? { + val resolvedCall = baseClause.getResolvedCall(context) ?: return null + if (resolvedCall.getExplicitReceiverValue() != null) return null + return resolvedCall.getImplicitReceiverValue() +} - val targetType = context[BindingContext.TYPE, condition.typeReference] ?: return true +internal fun IfThenTransformationData.hasImplicitReceiverReplaceableBySafeCall(context: BindingContext): Boolean = + checkedExpression is KtThisExpression && getImplicitReceiver(context) != null + +private fun KtCallExpression.replaceCallWithLet( + newReceiver: KtExpression, + oldReceiver: KtExpression, + factory: KtPsiFactory, +): KtExpression { + val needExplicitParameter = + valueArguments.any { it.getArgumentExpression()?.text == StandardNames.IMPLICIT_LAMBDA_PARAMETER_NAME.identifier } + val parameterName = if (needExplicitParameter) { + val scope = getResolutionScope() + KotlinNameSuggester.suggestNameByName("it") { scope.findVariable(Name.identifier(it), NoLookupLocation.FROM_IDE) == null } + } else { + "it" + } + return factory.buildExpression { + appendExpression(newReceiver) + appendFixedText("?.let {") + if (needExplicitParameter) appendFixedText(" $parameterName ->") + appendExpression(calleeExpression) + appendFixedText("(") + valueArguments.forEachIndexed { index, arg -> + if (index != 0) appendFixedText(", ") + val argName = arg.getArgumentName()?.asName + if (argName != null) { + appendName(argName) + appendFixedText(" = ") + } + val argExpression = arg.getArgumentExpression() + if (argExpression?.isSimplifiableTo(oldReceiver) == true) + appendFixedText(parameterName) + else + appendExpression(argExpression) + } + appendFixedText(") }") + } +} + +internal fun IfThenTransformationData.conditionHasIncompatibleTypes(context: BindingContext): Boolean { + val isExpression = condition as? KtIsExpression ?: return false + + val targetType = context[BindingContext.TYPE, isExpression.typeReference] ?: return true if (TypeUtils.isNullableType(targetType)) return true // TODO: the following check can be removed after fix of KT-14576 - val originalType = receiverExpression.getType(context) ?: return true + val originalType = checkedExpression.getType(context) ?: return true return !targetType.isSubtypeOf(originalType) } @@ -335,7 +302,7 @@ internal fun KtExpression?.isClauseTransformableToLetOnly(receiver: KtExpression fun KtIfExpression.shouldBeTransformed(): Boolean = when (val condition = condition) { is KtBinaryExpression -> { - val baseClause = (if (condition.operationToken == KtTokens.EQEQ) `else` else then)?.unwrapBlockOrParenthesis() + val baseClause = (if (condition.operationToken == KtTokens.EQEQ) `else` else then)?.getSingleUnwrappedStatementOrThis() !baseClause.isClauseTransformableToLetOnly(condition.checkedExpression()) } else -> false @@ -346,12 +313,6 @@ fun KtIfExpression.fromIfKeywordToRightParenthesisTextRangeInThis(): TextRange { return TextRange(ifKeyword.startOffset, rightOffset).shiftLeft(startOffset) } -private fun KtExpression.checkedExpression() = when (this) { - is KtBinaryExpression -> expressionComparedToNull() - is KtIsExpression -> leftHandSide - else -> null -} - internal fun KtExpression.hasNullableType(context: BindingContext): Boolean { val type = getType(context) ?: return true return TypeUtils.isNullableType(type) @@ -359,7 +320,7 @@ internal fun KtExpression.hasNullableType(context: BindingContext): Boolean { internal fun KtExpression.hasFirstReceiverOf(receiver: KtExpression): Boolean { val actualReceiver = (this as? KtDotQualifiedExpression)?.getLeftMostReceiverExpression() ?: return false - return actualReceiver.evaluatesTo(receiver) + return actualReceiver.isSimplifiableTo(receiver) } private fun KtExpression.insertSafeCalls(factory: KtPsiFactory): KtExpression { diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfToWhenIntention.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfToWhenIntention.kt index dcdfa95ae814..7205f7b8c127 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfToWhenIntention.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfToWhenIntention.kt @@ -11,7 +11,7 @@ import com.intellij.psi.codeStyle.CodeStyleManager import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings import org.jetbrains.kotlin.idea.base.psi.replaced -import org.jetbrains.kotlin.idea.base.psi.unwrapBlockOrParenthesis +import org.jetbrains.kotlin.idea.base.psi.getSingleUnwrappedStatementOrThis import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.codeinsight.api.classic.intentions.SelfTargetingRangeIntention import org.jetbrains.kotlin.idea.codeinsights.impl.base.quickFix.AddLoopLabelFix @@ -117,7 +117,7 @@ class IfToWhenIntention : SelfTargetingRangeIntention( private fun BuilderByPattern<*>.appendElseBlock(block: KtExpression?, unwrapBlockOrParenthesis: Boolean = false) { appendFixedText("else->") - appendExpression(if (unwrapBlockOrParenthesis) block?.unwrapBlockOrParenthesis() else block) + appendExpression(if (unwrapBlockOrParenthesis) block?.getSingleUnwrappedStatementOrThis() else block) appendFixedText("\n") } diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindTransformationMatcher.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindTransformationMatcher.kt index 71d294ca1862..2d6501699eb7 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindTransformationMatcher.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindTransformationMatcher.kt @@ -8,7 +8,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor import org.jetbrains.kotlin.idea.codeinsight.utils.isFalseConstant import org.jetbrains.kotlin.idea.codeinsight.utils.isTrueConstant import org.jetbrains.kotlin.idea.codeinsights.impl.base.hasUsages -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression +import org.jetbrains.kotlin.idea.base.psi.isNullExpression import org.jetbrains.kotlin.idea.intentions.loopToCallChain.* import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.Condition import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformationBase diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FilterTransformation.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FilterTransformation.kt index 444459a4204d..bbbf174e38c5 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FilterTransformation.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FilterTransformation.kt @@ -4,7 +4,7 @@ package org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.codeinsights.impl.base.hasUsages -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression +import org.jetbrains.kotlin.idea.base.psi.isNullExpression import org.jetbrains.kotlin.idea.intentions.loopToCallChain.* import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.FindTransformationMatcher import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.MaxOrMinTransformation diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/AddExclExclCallFix.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/AddExclExclCallFix.kt index 249745f77867..146bb443da0b 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/AddExclExclCallFix.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/AddExclExclCallFix.kt @@ -14,7 +14,7 @@ import org.jetbrains.kotlin.diagnostics.Errors.UNSAFE_CALL import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression +import org.jetbrains.kotlin.idea.base.psi.isNullExpression import org.jetbrains.kotlin.idea.resolve.dataFlowValueFactory import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.lexer.KtTokens diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeVariableTypeFix.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeVariableTypeFix.kt index 427530fca94b..67c7f711e628 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeVariableTypeFix.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeVariableTypeFix.kt @@ -16,7 +16,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.KotlinQuickFixAction import org.jetbrains.kotlin.idea.core.ShortenReferences -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression +import org.jetbrains.kotlin.idea.base.psi.isNullExpression import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.lexer.KtTokens diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/WrapWithCollectionLiteralCallFix.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/WrapWithCollectionLiteralCallFix.kt index 54343294710d..f91268cbdf79 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/WrapWithCollectionLiteralCallFix.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/WrapWithCollectionLiteralCallFix.kt @@ -7,7 +7,7 @@ import com.intellij.openapi.project.Project import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.base.psi.replaced import org.jetbrains.kotlin.idea.codeinsight.api.classic.quickfixes.KotlinQuickFixAction -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression +import org.jetbrains.kotlin.idea.base.psi.isNullExpression import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/InspectionTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/InspectionTestGenerated.java index 42333bfeb195..f1cb70a8500e 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/InspectionTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/InspectionTestGenerated.java @@ -411,11 +411,6 @@ public abstract class InspectionTestGenerated extends AbstractInspectionTest { runTest("testData/inspectionsLocal/branched/ifThenToElvis/inspectionData/inspections.test"); } - @TestMetadata("branched/ifThenToSafeAccess/inspectionData/inspections.test") - public void testBranched_ifThenToSafeAccess_inspectionData_Inspections_test() throws Exception { - runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/inspectionData/inspections.test"); - } - @TestMetadata("conventionNameCalls/replaceGetOrSet/inspectionData/inspections.test") public void testConventionNameCalls_replaceGetOrSet_inspectionData_Inspections_test() throws Exception { runTest("testData/inspectionsLocal/conventionNameCalls/replaceGetOrSet/inspectionData/inspections.test"); diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 70573448d910..d0dad9af1f2e 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -446,6 +446,11 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); } + @TestMetadata("argumentInCallWithReceiver.kt") + public void testArgumentInCallWithReceiver() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/argumentInCallWithReceiver.kt"); + } + @TestMetadata("blockHasMoreThanOneStatement.kt") public void testBlockHasMoreThanOneStatement() throws Exception { runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/blockHasMoreThanOneStatement.kt"); @@ -476,6 +481,16 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/call4.kt"); } + @TestMetadata("callChainWithImplicitReceiver.kt") + public void testCallChainWithImplicitReceiver() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/callChainWithImplicitReceiver.kt"); + } + + @TestMetadata("callChainWithVariableCalls.kt") + public void testCallChainWithVariableCalls() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/callChainWithVariableCalls.kt"); + } + @TestMetadata("callInvokeOperator.kt") public void testCallInvokeOperator() throws Exception { runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator.kt"); @@ -561,11 +576,21 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks.kt"); } + @TestMetadata("ifAndElseBothInBlocks2.kt") + public void testIfAndElseBothInBlocks2() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks2.kt"); + } + @TestMetadata("ifAndElseNotInBlocks.kt") public void testIfAndElseNotInBlocks() throws Exception { runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks.kt"); } + @TestMetadata("ifAndElseNotInBlocks2.kt") + public void testIfAndElseNotInBlocks2() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks2.kt"); + } + @TestMetadata("ifAsExpression.kt") public void testIfAsExpression() throws Exception { runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAsExpression.kt"); @@ -596,6 +621,11 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiverIsNotLeftMostReceiver.kt"); } + @TestMetadata("implicitReceiverVariableCall.kt") + public void testImplicitReceiverVariableCall() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiverVariableCall.kt"); + } + @TestMetadata("isCheckSimple.kt") public void testIsCheckSimple() throws Exception { runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/isCheckSimple.kt"); @@ -636,6 +666,16 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull.kt"); } + @TestMetadata("lhsNotEqualsNull2.kt") + public void testLhsNotEqualsNull2() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull2.kt"); + } + + @TestMetadata("localVar.kt") + public void testLocalVar() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/localVar.kt"); + } + @TestMetadata("missingNecessaryElseClause.kt") public void testMissingNecessaryElseClause() throws Exception { runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/missingNecessaryElseClause.kt"); @@ -646,6 +686,26 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/missingNecessaryThenClause.kt"); } + @TestMetadata("multipleUsagesNullCheck.kt") + public void testMultipleUsagesNullCheck() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesNullCheck.kt"); + } + + @TestMetadata("multipleUsagesThis.kt") + public void testMultipleUsagesThis() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesThis.kt"); + } + + @TestMetadata("multipleUsagesTypeCheck.kt") + public void testMultipleUsagesTypeCheck() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesTypeCheck.kt"); + } + + @TestMetadata("multipleUsagesVariableCalls.kt") + public void testMultipleUsagesVariableCalls() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesVariableCalls.kt"); + } + @TestMetadata("noCondition.kt") public void testNoCondition() throws Exception { runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/noCondition.kt"); @@ -656,6 +716,11 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock.kt"); } + @TestMetadata("noElseBlock2.kt") + public void testNoElseBlock2() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock2.kt"); + } + @TestMetadata("noElseBlockAsExpression.kt") public void testNoElseBlockAsExpression() throws Exception { runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlockAsExpression.kt"); @@ -681,14 +746,19 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock.kt"); } + @TestMetadata("noThenBlock2.kt") + public void testNoThenBlock2() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock2.kt"); + } + @TestMetadata("notApplicableForFunction.kt") public void testNotApplicableForFunction() throws Exception { runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableForFunction.kt"); } - @TestMetadata("notApplicableForLocalVar.kt") - public void testNotApplicableForLocalVar() throws Exception { - runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableForLocalVar.kt"); + @TestMetadata("notApplicableForNonLocalVar.kt") + public void testNotApplicableForNonLocalVar() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableForNonLocalVar.kt"); } @TestMetadata("notApplicableResultInCallChain.kt") @@ -721,6 +791,11 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyNotNull.kt"); } + @TestMetadata("propertyVariableCall.kt") + public void testPropertyVariableCall() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyVariableCall.kt"); + } + @TestMetadata("propertyWithProperty.kt") public void testPropertyWithProperty() throws Exception { runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyWithProperty.kt"); @@ -746,11 +821,21 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull.kt"); } + @TestMetadata("rhsEqualsNull2.kt") + public void testRhsEqualsNull2() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull2.kt"); + } + @TestMetadata("rhsNotEqualsNull.kt") public void testRhsNotEqualsNull() throws Exception { runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull.kt"); } + @TestMetadata("rhsNotEqualsNull2.kt") + public void testRhsNotEqualsNull2() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull2.kt"); + } + @TestMetadata("senselessComparison.kt") public void testSenselessComparison() throws Exception { runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/senselessComparison.kt"); @@ -786,6 +871,16 @@ public abstract class LocalInspectionTestGenerated extends AbstractLocalInspecti runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/unacceptableNoThenBlock.kt"); } + @TestMetadata("usageInBlock.kt") + public void testUsageInBlock() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/usageInBlock.kt"); + } + + @TestMetadata("usageInBlock2.kt") + public void testUsageInBlock2() throws Exception { + runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/usageInBlock2.kt"); + } + @TestMetadata("uselessIsCheck.kt") public void testUselessIsCheck() throws Exception { runTest("testData/inspectionsLocal/branched/ifThenToSafeAccess/uselessIsCheck.kt"); diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/.k2Inspection b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/.k2Inspection new file mode 100644 index 000000000000..ffa69ad74296 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/.k2Inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.k2.codeinsight.inspections.IfThenToSafeAccessInspection \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/argumentInCallWithReceiver.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/argumentInCallWithReceiver.kt new file mode 100644 index 000000000000..81cb041089c9 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/argumentInCallWithReceiver.kt @@ -0,0 +1,10 @@ +// FIX: Replace 'if' expression with safe access expression +// HIGHLIGHT: INFORMATION +// IGNORE_K1 +fun String.ext(s: String): String = "" + +fun test(a: Any) { + if (a is String) { + "".ext(a) + } else null +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/argumentInCallWithReceiver.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/argumentInCallWithReceiver.kt.after new file mode 100644 index 000000000000..7e52b81c2ff1 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/argumentInCallWithReceiver.kt.after @@ -0,0 +1,8 @@ +// FIX: Replace 'if' expression with safe access expression +// HIGHLIGHT: INFORMATION +// IGNORE_K1 +fun String.ext(s: String): String = "" + +fun test(a: Any) { + (a as? String)?.let { "".ext(it) } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/call4.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/call4.kt index 1c25e996b9e4..26f41ac43a8b 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/call4.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/call4.kt @@ -1,6 +1,7 @@ // FIX: Replace 'if' expression with safe access expression // WITH_STDLIB // HIGHLIGHT: INFORMATION +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/call4.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/call4.kt.after index 72fbf05e0220..d067f9b8c6df 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/call4.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/call4.kt.after @@ -1,6 +1,7 @@ // FIX: Replace 'if' expression with safe access expression // WITH_STDLIB // HIGHLIGHT: INFORMATION +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callChainWithImplicitReceiver.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callChainWithImplicitReceiver.kt new file mode 100644 index 000000000000..b6e7b07323dd --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callChainWithImplicitReceiver.kt @@ -0,0 +1,8 @@ +// FIX: Replace 'if' expression with safe access expression +// HIGHLIGHT: INFORMATION +// IGNORE_K1 +class My(val x: Int) + +fun Any.foo(): Int? { + return if (this is My) x.hashCode() else null +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callChainWithImplicitReceiver.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callChainWithImplicitReceiver.kt.after new file mode 100644 index 000000000000..41d29135b14a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callChainWithImplicitReceiver.kt.after @@ -0,0 +1,8 @@ +// FIX: Replace 'if' expression with safe access expression +// HIGHLIGHT: INFORMATION +// IGNORE_K1 +class My(val x: Int) + +fun Any.foo(): Int? { + return (this as? My)?.x?.hashCode() +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callChainWithVariableCalls.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callChainWithVariableCalls.kt new file mode 100644 index 000000000000..055bb1140855 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callChainWithVariableCalls.kt @@ -0,0 +1,10 @@ +// FIX: Replace 'if' expression with safe access expression +// HIGHLIGHT: INFORMATION +// IGNORE_K1 +class Foo(val bar: () -> Foo) + +fun test(foo: Foo?) { + if (foo != null) { + foo.bar().bar() + } else null +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callChainWithVariableCalls.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callChainWithVariableCalls.kt.after new file mode 100644 index 000000000000..553d09b2d0a1 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callChainWithVariableCalls.kt.after @@ -0,0 +1,8 @@ +// FIX: Replace 'if' expression with safe access expression +// HIGHLIGHT: INFORMATION +// IGNORE_K1 +class Foo(val bar: () -> Foo) + +fun test(foo: Foo?) { + foo?.bar?.invoke()?.bar?.invoke() +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator2.k2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator2.k2.kt new file mode 100644 index 000000000000..4a8e1799a7e9 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator2.k2.kt @@ -0,0 +1,13 @@ +// HIGHLIGHT: INFORMATION +// FIX: Replace 'if' expression with safe access expression +class Foo(val bar: Bar) + +class Bar { + operator fun invoke() {} +} + +fun test(foo: Foo?) { + if (foo != null) { + foo.bar() + } +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator2.k2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator2.k2.kt.after new file mode 100644 index 000000000000..f481d793ccb7 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator2.k2.kt.after @@ -0,0 +1,11 @@ +// HIGHLIGHT: INFORMATION +// FIX: Replace 'if' expression with safe access expression +class Foo(val bar: Bar) + +class Bar { + operator fun invoke() {} +} + +fun test(foo: Foo?) { + foo?.bar?.invoke() +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator2.kt index 2b81b2bfa20b..8b9ad9e1b2d2 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator2.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator2.kt @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// FIR_COMPARISON class Foo(val bar: Bar) class Bar { diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator2.kt.after index 8676231b6811..79356d89c083 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator2.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callInvokeOperator2.kt.after @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// FIR_COMPARISON class Foo(val bar: Bar) class Bar { diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callVariable2.k2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callVariable2.k2.kt new file mode 100644 index 000000000000..7b63d592a1ca --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callVariable2.k2.kt @@ -0,0 +1,9 @@ +// HIGHLIGHT: INFORMATION +// FIX: Replace 'if' expression with safe access expression +class Foo(val f: () -> Unit) + +fun test(foo: Foo?) { + if (foo != null) { + foo.f() + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callVariable2.k2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callVariable2.k2.kt.after new file mode 100644 index 000000000000..d8c17afab344 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callVariable2.k2.kt.after @@ -0,0 +1,7 @@ +// HIGHLIGHT: INFORMATION +// FIX: Replace 'if' expression with safe access expression +class Foo(val f: () -> Unit) + +fun test(foo: Foo?) { + foo?.f?.invoke() +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callVariable2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callVariable2.kt index e75c93342ad6..e318af45b436 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callVariable2.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callVariable2.kt @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// FIR_COMPARISON class Foo(val f: () -> Unit) fun test(foo: Foo?) { diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callVariable2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callVariable2.kt.after index 9d470b28e50c..1a5d6e8e1f95 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callVariable2.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/callVariable2.kt.after @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// FIR_COMPARISON class Foo(val f: () -> Unit) fun test(foo: Foo?) { diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionInParentheses.k2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionInParentheses.k2.kt new file mode 100644 index 000000000000..fa7bce416b40 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionInParentheses.k2.kt @@ -0,0 +1,11 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression +class Some { + fun bar() {} +} + +fun Some?.foo() { + if (((this) != null)) { + bar() + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionInParentheses.k2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionInParentheses.k2.kt.after new file mode 100644 index 000000000000..7e6cbad5e3be --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionInParentheses.k2.kt.after @@ -0,0 +1,9 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression +class Some { + fun bar() {} +} + +fun Some?.foo() { + this?.bar() +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionInParentheses.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionInParentheses.kt index 9c426ae6fc9c..a81885023f2c 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionInParentheses.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionInParentheses.kt @@ -1,5 +1,6 @@ // HIGHLIGHT: INFORMATION // FIX: Replace 'if' expression with safe access expression +// FIR_COMPARISON class Some { fun bar() {} } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionInParentheses.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionInParentheses.kt.after index 3b10fa933fef..d663ae10bd72 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionInParentheses.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/conditionInParentheses.kt.after @@ -1,5 +1,6 @@ // HIGHLIGHT: INFORMATION // FIX: Replace 'if' expression with safe access expression +// FIR_COMPARISON class Some { fun bar() {} } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks.kt index 3de4521d0926..74920b928e3b 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks.kt @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks.kt.after index 0acf3b1ee1fe..8954189f1c13 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks.kt.after @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks2.kt new file mode 100644 index 000000000000..31cb3870b84f --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks2.kt @@ -0,0 +1,16 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + val foo = maybeFoo() + if (foo != null) { + foo.length + } + else { + null + } + return foo +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks2.kt.after new file mode 100644 index 000000000000..028901c359b7 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseBothInBlocks2.kt.after @@ -0,0 +1,11 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + val foo = maybeFoo() + foo?.length + return foo +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks.kt index 5158f8524036..8e4051345ae2 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks.kt @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks.kt.after index 0acf3b1ee1fe..8954189f1c13 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks.kt.after @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks2.kt new file mode 100644 index 000000000000..7ab0aca8717b --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks2.kt @@ -0,0 +1,14 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + val foo = maybeFoo() + if (foo != null) + foo.length + else + null + return foo +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks2.kt.after new file mode 100644 index 000000000000..028901c359b7 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAndElseNotInBlocks2.kt.after @@ -0,0 +1,11 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + val foo = maybeFoo() + foo?.length + return foo +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAsExpression.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAsExpression.kt index 9a7ca84d948d..ba114e2251ab 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAsExpression.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAsExpression.kt @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAsExpression.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAsExpression.kt.after index 3bdbbc7a54da..4ede21edb3d7 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAsExpression.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/ifAsExpression.kt.after @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiver.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiver.kt index d3f80e0e031a..816c5eb8aa04 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiver.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiver.kt @@ -1,2 +1,4 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression // WITH_STDLIB fun String?.foo() = if (this == null) null else isEmpty() diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiver.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiver.kt.after index e4fa36dc2cef..bcbdccfca8db 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiver.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiver.kt.after @@ -1,2 +1,4 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression // WITH_STDLIB fun String?.foo() = this?.isEmpty() \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiverVariableCall.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiverVariableCall.kt new file mode 100644 index 000000000000..422ab507fb66 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiverVariableCall.kt @@ -0,0 +1,6 @@ +// HIGHLIGHT: INFORMATION +// FIX: Replace 'if' expression with safe access expression +// IGNORE_K1 +class A(val f: () -> Unit) + +fun Any.foo() = if (this !is A) null else f() diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiverVariableCall.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiverVariableCall.kt.after new file mode 100644 index 000000000000..9b63482d93e3 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/implicitReceiverVariableCall.kt.after @@ -0,0 +1,6 @@ +// HIGHLIGHT: INFORMATION +// FIX: Replace 'if' expression with safe access expression +// IGNORE_K1 +class A(val f: () -> Unit) + +fun Any.foo() = (this as? A)?.f?.invoke() diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/inspectionData/expected.xml b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/inspectionData/expected.xml deleted file mode 100644 index d86a2db97dd4..000000000000 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/inspectionData/expected.xml +++ /dev/null @@ -1,201 +0,0 @@ - - - rhsNotEqualsNull.kt - 7 - light_idea_test_case - - If-Then foldable to '?.' - Foldable if-then - - - rhsEqualsNull.kt - 7 - light_idea_test_case - - If-Then foldable to '?.' - Foldable if-then - - - noThenBlock.kt - 7 - light_idea_test_case - - If-Then foldable to '?.' - Foldable if-then - - - noElseBlock.kt - 7 - light_idea_test_case - - If-Then foldable to '?.' - Foldable if-then - - - lhsNotEqualsNull.kt - 7 - light_idea_test_case - - If-Then foldable to '?.' - Foldable if-then - - - lhsEqualsNull.kt - 7 - light_idea_test_case - - If-Then foldable to '?.' - Foldable if-then - - - ifAsExpression.kt - 7 - light_idea_test_case - - If-Then foldable to '?.' - Foldable if-then - - - ifAndElseNotInBlocks.kt - 7 - light_idea_test_case - - If-Then foldable to '?.' - Foldable if-then - - - ifAndElseBothInBlocks.kt - 7 - light_idea_test_case - - If-Then foldable to '?.' - Foldable if-then - - - doesNotinlineValueOutsideOfScope.kt - 8 - light_idea_test_case - - If-Then foldable to '?.' - Foldable if-then - - - doesNotinlineValueIfUsedMoreThanOnce.kt - 10 - light_idea_test_case - - If-Then foldable to '?.' - Foldable if-then - - - willNotInlineClassProperty.kt - 3 - light_idea_test_case - - If-Then foldable to '?.' - Foldable if-then - - - nullCheckWithSelectorCallChain.kt - 5 - light_idea_test_case - - If-Then foldable to '?.' - Foldable if-then - - - nullCheckSimple.kt - 2 - light_idea_test_case - - If-Then foldable to '?.' - Foldable if-then - - - implicitReceiver.kt - 2 - light_idea_test_case - - If-Then foldable to '?.' - Foldable if-then - - - propertyNotNull.kt - 9 - light_idea_test_case - - If-Then foldable to '?.' - Foldable if-then - - - implicitReceiver2.kt - 6 - light_idea_test_case - <default> - - If-Then foldable to '?.' - Foldable if-then - - - resultCall.kt - 6 - light_idea_test_case - resultCall - - If-Then foldable to '?.' - Foldable if-then - - - noStdResultInCallChain.kt - 7 - light_idea_test_case - noStdResultInCallChain - - If-Then foldable to '?.' - Foldable if-then - - - callVariable2.kt - 4 - light_idea_test_case - callVariable2 - - If-Then foldable to '?.' - Foldable if-then - - - callInvokeOperator2.kt - 8 - light_idea_test_case - callInvokeOperator2 - - If-Then foldable to '?.' - Foldable if-then - - - callInvokeOperator3.kt - 2 - light_idea_test_case - callInvokeOperator3 - - If-Then foldable to '?.' - Foldable if-then - if (foo != null) - kotlin - 4 - 16 - - - callInvokeOperator4.kt - 8 - light_idea_test_case - callInvokeOperator4 - - If-Then foldable to '?.' - Foldable if-then - if (foo != null) - kotlin - 4 - 16 - - diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/inspectionData/inspections.test b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/inspectionData/inspections.test deleted file mode 100644 index fe9f7a74630c..000000000000 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/inspectionData/inspections.test +++ /dev/null @@ -1,2 +0,0 @@ -// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.branchedTransformations.IfThenToSafeAccessInspection -// WITH_STDLIB \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isCheckSimple.k2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isCheckSimple.k2.kt new file mode 100644 index 000000000000..268ac2603a2a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isCheckSimple.k2.kt @@ -0,0 +1,7 @@ +// FIX: Replace 'if' expression with safe cast expression +// HIGHLIGHT: WARNING +class My(val x: Int) + +fun foo(arg: Any?): My? { + return if (arg is My) arg else null +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isCheckSimple.k2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isCheckSimple.k2.kt.after new file mode 100644 index 000000000000..d2ee1658811c --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isCheckSimple.k2.kt.after @@ -0,0 +1,7 @@ +// FIX: Replace 'if' expression with safe cast expression +// HIGHLIGHT: WARNING +class My(val x: Int) + +fun foo(arg: Any?): My? { + return arg as? My +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isCheckSimple.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isCheckSimple.kt index 9abe2bcd81ed..a22cfcde919d 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isCheckSimple.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isCheckSimple.kt @@ -1,5 +1,6 @@ // FIX: Replace 'if' expression with safe cast expression // HIGHLIGHT: INFORMATION +// FIR_COMPARISON class My(val x: Int) fun foo(arg: Any?): My? { diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isCheckSimple.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isCheckSimple.kt.after index d415378501d0..acdc2a35fec7 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isCheckSimple.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isCheckSimple.kt.after @@ -1,5 +1,6 @@ // FIX: Replace 'if' expression with safe cast expression // HIGHLIGHT: INFORMATION +// FIR_COMPARISON class My(val x: Int) fun foo(arg: Any?): My? { diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNotNullable.k2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNotNullable.k2.kt new file mode 100644 index 000000000000..ed3183693cc8 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNotNullable.k2.kt @@ -0,0 +1,3 @@ +// FIX: Replace 'if' expression with safe access expression +/* In K2 nullable checks are also subject to the inspection */ +fun foo(arg: Any?) = if (arg !is String?) null else arg?.length \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNotNullable.k2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNotNullable.k2.kt.after new file mode 100644 index 000000000000..00fc2ea9acc1 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNotNullable.k2.kt.after @@ -0,0 +1,3 @@ +// FIX: Replace 'if' expression with safe access expression +/* In K2 nullable checks are also subject to the inspection */ +fun foo(arg: Any?) = (arg as? String?)?.length \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNotNullable.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNotNullable.kt index 1959bf35d115..7bb7bba07386 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNotNullable.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNotNullable.kt @@ -1,2 +1,3 @@ // PROBLEM: none +// FIR_COMPARISON fun foo(arg: Any) = if (arg !is String?) null else arg?.length \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNullable.k2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNullable.k2.kt new file mode 100644 index 000000000000..6e0b05a46459 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNullable.k2.kt @@ -0,0 +1,3 @@ +// FIX: Replace 'if' expression with safe access expression +/* In K2 nullable checks are also subject to the inspection */ +fun foo(arg: Any?) = if (arg is String?) arg?.length else null \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNullable.k2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNullable.k2.kt.after new file mode 100644 index 000000000000..00fc2ea9acc1 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNullable.k2.kt.after @@ -0,0 +1,3 @@ +// FIX: Replace 'if' expression with safe access expression +/* In K2 nullable checks are also subject to the inspection */ +fun foo(arg: Any?) = (arg as? String?)?.length \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNullable.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNullable.kt index f56d6188dd5e..905c7141ac5c 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNullable.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/isNullable.kt @@ -1,2 +1,3 @@ // PROBLEM: none +// FIR_COMPARISON fun foo(arg: Any) = if (arg is String?) arg?.length else null \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsEqualsNull.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsEqualsNull.kt index 21075dddc43e..812f8125423e 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsEqualsNull.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsEqualsNull.kt @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsEqualsNull.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsEqualsNull.kt.after index 0acf3b1ee1fe..8954189f1c13 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsEqualsNull.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsEqualsNull.kt.after @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull.kt index 21dec6f62f9f..df9a253a101b 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull.kt @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull.kt.after index 0acf3b1ee1fe..8954189f1c13 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull.kt.after @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull2.kt new file mode 100644 index 000000000000..c25944819689 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull2.kt @@ -0,0 +1,14 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + val foo = maybeFoo() + if (foo != null) + foo.length + else + null + return foo +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull2.kt.after new file mode 100644 index 000000000000..028901c359b7 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/lhsNotEqualsNull2.kt.after @@ -0,0 +1,11 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + val foo = maybeFoo() + foo?.length + return foo +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableForLocalVar.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/localVar.kt similarity index 57% rename from plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableForLocalVar.kt rename to plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/localVar.kt index 4bc909e0a6b5..7b786e42dab7 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableForLocalVar.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/localVar.kt @@ -1,4 +1,6 @@ -// PROBLEM: none +// FIX: Replace 'if' expression with safe access expression +// HIGHLIGHT: WARNING +// IGNORE_K1 fun maybeFoo(): String? { return "foo" } @@ -8,5 +10,5 @@ fun main(args: Array) { if (foo == null) null else - foo?.length + foo.length } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/localVar.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/localVar.kt.after new file mode 100644 index 000000000000..110481b20311 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/localVar.kt.after @@ -0,0 +1,11 @@ +// FIX: Replace 'if' expression with safe access expression +// HIGHLIGHT: WARNING +// IGNORE_K1 +fun maybeFoo(): String? { + return "foo" +} + +fun main(args: Array) { + var foo = maybeFoo() + foo?.length +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesNullCheck.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesNullCheck.kt new file mode 100644 index 000000000000..540870acb6bf --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesNullCheck.kt @@ -0,0 +1,6 @@ +// FIX: Replace 'if' expression with safe access expression +// HIGHLIGHT: INFORMATION +// IGNORE_K1 +fun test(baz: Int?) { + if (baz != null) baz + baz else null +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesNullCheck.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesNullCheck.kt.after new file mode 100644 index 000000000000..48f443cec4a0 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesNullCheck.kt.after @@ -0,0 +1,6 @@ +// FIX: Replace 'if' expression with safe access expression +// HIGHLIGHT: INFORMATION +// IGNORE_K1 +fun test(baz: Int?) { + baz?.let { it + it } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesThis.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesThis.kt new file mode 100644 index 000000000000..ff27e5352c47 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesThis.kt @@ -0,0 +1,6 @@ +// FIX: Replace 'if' expression with safe access expression +// HIGHLIGHT: INFORMATION +// IGNORE_K1 +fun Any.test() { + if (this is Int) this + this else null +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesThis.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesThis.kt.after new file mode 100644 index 000000000000..21db0ee76fd4 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesThis.kt.after @@ -0,0 +1,6 @@ +// FIX: Replace 'if' expression with safe access expression +// HIGHLIGHT: INFORMATION +// IGNORE_K1 +fun Any.test() { + (this as? Int)?.let { it + it } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesTypeCheck.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesTypeCheck.kt new file mode 100644 index 000000000000..c2978f711d74 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesTypeCheck.kt @@ -0,0 +1,6 @@ +// FIX: Replace 'if' expression with safe access expression +// HIGHLIGHT: INFORMATION +// IGNORE_K1 +fun test(foo: Any) { + if (foo is Int) foo + foo else null +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesTypeCheck.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesTypeCheck.kt.after new file mode 100644 index 000000000000..4e0a91ce45f2 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesTypeCheck.kt.after @@ -0,0 +1,6 @@ +// FIX: Replace 'if' expression with safe access expression +// HIGHLIGHT: INFORMATION +// IGNORE_K1 +fun test(foo: Any) { + (foo as? Int)?.let { it + it } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesVariableCalls.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesVariableCalls.kt new file mode 100644 index 000000000000..788175f4fc19 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/multipleUsagesVariableCalls.kt @@ -0,0 +1,13 @@ +// PROBLEM: none +/* Currently is not supported */ +data class Foo(val a: Any) + +class Bar { + operator fun invoke(): Int = 1 +} + +fun test(foo: Foo) { + if (foo.a is Bar) { + foo.a() + foo.a() + } else null +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock.kt index 0ee83f971262..8ae0591eebf8 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock.kt @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock.kt.after index 0acf3b1ee1fe..8954189f1c13 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock.kt.after @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock2.kt new file mode 100644 index 000000000000..ab40ac1b7c7f --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock2.kt @@ -0,0 +1,13 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + val foo = maybeFoo() + if (foo != null) { + foo.length + } + return foo +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock2.kt.after new file mode 100644 index 000000000000..028901c359b7 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noElseBlock2.kt.after @@ -0,0 +1,11 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + val foo = maybeFoo() + foo?.length + return foo +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock.kt index d72e1d939a32..51c1e6441165 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock.kt @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock.kt.after index 0acf3b1ee1fe..8954189f1c13 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock.kt.after @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock2.kt new file mode 100644 index 000000000000..45ab77dc6fa0 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock2.kt @@ -0,0 +1,13 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + val foo = maybeFoo() + if (foo == null) else { + foo.length + } + return foo +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock2.kt.after new file mode 100644 index 000000000000..d21c4b3ec76c --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/noThenBlock2.kt.after @@ -0,0 +1,11 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + val foo = maybeFoo() + foo?.length + return foo +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableForNonLocalVar.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableForNonLocalVar.kt new file mode 100644 index 000000000000..9834dfc46b1a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableForNonLocalVar.kt @@ -0,0 +1,8 @@ +// PROBLEM: none +fun bar(x: String?) = "" + +class A(private var a: String?) { + fun foo() { + if (a != null) bar(a) else null + } +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableResultInCallChain.k2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableResultInCallChain.k2.kt new file mode 100644 index 000000000000..a16840051234 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableResultInCallChain.k2.kt @@ -0,0 +1,13 @@ +/* In K2 the behavior is different, because limitations regarding `Result` are lifted since Kotlin 1.5 */ +/* See https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/result.md */ +// FIX: Replace 'if' expression with safe access expression +// WITH_STDLIB +// DISABLE-ERRORS + +val someNullableString: String? = "" +fun String.bar(): Result = Result.success("") +val result = if (someNullableString == null) { + null +} else { + someNullableString.bar().getOrNull().let { } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableResultInCallChain.k2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableResultInCallChain.k2.kt.after new file mode 100644 index 000000000000..9374a4e05203 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableResultInCallChain.k2.kt.after @@ -0,0 +1,9 @@ +/* In K2 the behavior is different, because limitations regarding `Result` are lifted since Kotlin 1.5 */ +/* See https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/result.md */ +// FIX: Replace 'if' expression with safe access expression +// WITH_STDLIB +// DISABLE-ERRORS + +val someNullableString: String? = "" +fun String.bar(): Result = Result.success("") +val result = someNullableString?.bar()?.getOrNull()?.let { } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableResultInCallChain.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableResultInCallChain.kt index 122018722ad3..ccf155b9c3b7 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableResultInCallChain.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/notApplicableResultInCallChain.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON // PROBLEM: none // WITH_STDLIB // DISABLE-ERRORS diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/property.k2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/property.k2.kt new file mode 100644 index 000000000000..baa19f28961c --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/property.k2.kt @@ -0,0 +1,11 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe cast expression + +interface Foo +interface Bar : Foo + +data class Data(val foo: Foo) + +fun handle(data: Data) { + val bar = if (data.foo is Bar) data.foo else null +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/property.k2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/property.k2.kt.after new file mode 100644 index 000000000000..9c89d01f3886 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/property.k2.kt.after @@ -0,0 +1,11 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe cast expression + +interface Foo +interface Bar : Foo + +data class Data(val foo: Foo) + +fun handle(data: Data) { + val bar = data.foo as? Bar +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/property.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/property.kt index 95b3d717a9eb..d193c50e2366 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/property.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/property.kt @@ -1,6 +1,6 @@ // HIGHLIGHT: INFORMATION // FIX: Replace 'if' expression with safe cast expression - +// FIR_COMPARISON interface Foo interface Bar : Foo diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/property.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/property.kt.after index 207d9e489ced..ced44ebc1134 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/property.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/property.kt.after @@ -1,6 +1,6 @@ // HIGHLIGHT: INFORMATION // FIX: Replace 'if' expression with safe cast expression - +// FIR_COMPARISON interface Foo interface Bar : Foo diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyNotNull.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyNotNull.kt index f8940f86cda2..2c83c7bba512 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyNotNull.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyNotNull.kt @@ -1,5 +1,4 @@ // HIGHLIGHT: WARNING - // FIX: Remove redundant 'if' expression interface Bar diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyNotNull.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyNotNull.kt.after index 75933ed26f30..6d41a0fc1ffd 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyNotNull.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyNotNull.kt.after @@ -1,5 +1,4 @@ // HIGHLIGHT: WARNING - // FIX: Remove redundant 'if' expression interface Bar diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyVariableCall.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyVariableCall.kt new file mode 100644 index 000000000000..ad8d055558cb --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyVariableCall.kt @@ -0,0 +1,13 @@ +// PROBLEM: none +/* Currently is not supported */ +data class Foo(val a: Any) + +class Bar { + operator fun invoke() {} +} + +fun test(foo: Foo) { + if (foo.a is Bar) { + foo.a() + } else null +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyWithProperty.k2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyWithProperty.k2.kt new file mode 100644 index 000000000000..2213679bd7e4 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyWithProperty.k2.kt @@ -0,0 +1,13 @@ +// FIX: Replace 'if' expression with safe access expression +// HIGHLIGHT: INFORMATION +interface Foo +interface Bar : Foo { + val x: String +} + +data class Data(val foo: Foo) + +fun handle(data: Data) { + // Not available yet (possible in principle) + val bar = if (data.foo is Bar) data.foo.x else null +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyWithProperty.k2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyWithProperty.k2.kt.after new file mode 100644 index 000000000000..d75c618b4802 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyWithProperty.k2.kt.after @@ -0,0 +1,13 @@ +// FIX: Replace 'if' expression with safe access expression +// HIGHLIGHT: INFORMATION +interface Foo +interface Bar : Foo { + val x: String +} + +data class Data(val foo: Foo) + +fun handle(data: Data) { + // Not available yet (possible in principle) + val bar = (data.foo as? Bar)?.x +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyWithProperty.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyWithProperty.kt index 7778c4b0fb2c..71f585049bfc 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyWithProperty.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/propertyWithProperty.kt @@ -1,4 +1,5 @@ // PROBLEM: none +// FIR_COMPARISON interface Foo interface Bar : Foo { diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull.kt index a4829e701c51..416f7fbec13f 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull.kt @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull.kt.after index 0acf3b1ee1fe..8954189f1c13 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull.kt.after @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull2.kt new file mode 100644 index 000000000000..155573f744d2 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull2.kt @@ -0,0 +1,14 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + val foo = maybeFoo() + if (null == foo) + null + else + foo.length + return foo +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull2.kt.after new file mode 100644 index 000000000000..6a96004c1cb0 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsEqualsNull2.kt.after @@ -0,0 +1,12 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + val foo = maybeFoo() + foo?.length + return foo +} + diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull.kt index b207359e2398..6fa18afcc34b 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull.kt +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull.kt @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull.kt.after index 0acf3b1ee1fe..8954189f1c13 100644 --- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull.kt.after +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull.kt.after @@ -1,5 +1,6 @@ // HIGHLIGHT: WARNING // FIX: Replace 'if' expression with safe access expression +// IGNORE_K2 fun maybeFoo(): String? { return "foo" } diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull2.kt new file mode 100644 index 000000000000..fcc15ef3d15a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull2.kt @@ -0,0 +1,14 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + val foo = maybeFoo() + if (null != foo) + foo.length + else + null + return foo +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull2.kt.after new file mode 100644 index 000000000000..028901c359b7 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/rhsNotEqualsNull2.kt.after @@ -0,0 +1,11 @@ +// HIGHLIGHT: WARNING +// FIX: Replace 'if' expression with safe access expression +fun maybeFoo(): String? { + return "foo" +} + +fun test(): String? { + val foo = maybeFoo() + foo?.length + return foo +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/usageInBlock.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/usageInBlock.kt new file mode 100644 index 000000000000..224186334c21 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/usageInBlock.kt @@ -0,0 +1,12 @@ +// HIGHLIGHT: INFORMATION +// FIX: Replace 'if' expression with safe access expression +/* For now usages located in nested scopes are not replaced with `it` because such usages can resolve to scope-specific symbols and */ +/* require additional checks which are currently not implemented */ +// IGNORE_K1 +fun foo(n: Int, lambda: () -> Int) {} + +fun test(a: Any) { + if (a is Int) { + foo(a) { a } + } else null +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/usageInBlock.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/usageInBlock.kt.after new file mode 100644 index 000000000000..7b356e080bd8 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/usageInBlock.kt.after @@ -0,0 +1,10 @@ +// HIGHLIGHT: INFORMATION +// FIX: Replace 'if' expression with safe access expression +/* For now usages located in nested scopes are not replaced with `it` because such usages can resolve to scope-specific symbols and */ +/* require additional checks which are currently not implemented */ +// IGNORE_K1 +fun foo(n: Int, lambda: () -> Int) {} + +fun test(a: Any) { + (a as? Int)?.let { foo(it) { a } } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/usageInBlock2.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/usageInBlock2.kt new file mode 100644 index 000000000000..7bf2972dc7f0 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/usageInBlock2.kt @@ -0,0 +1,14 @@ +// HIGHLIGHT: INFORMATION +// FIX: Replace 'if' expression with safe access expression +/* For now usages located in nested scopes are not replaced with `it` because such usages can resolve to scope-specific symbols and */ +/* require additional checks which are currently not implemented */ +// IGNORE_K1 +class Foo(val a: Any) + +fun foo(n: Int, lambda: Foo.() -> Any) {} + +fun Foo.test() { + if (a is Int) { + foo(a) { a } + } else null +} diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/usageInBlock2.kt.after b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/usageInBlock2.kt.after new file mode 100644 index 000000000000..dfb847184d38 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/branched/ifThenToSafeAccess/usageInBlock2.kt.after @@ -0,0 +1,12 @@ +// HIGHLIGHT: INFORMATION +// FIX: Replace 'if' expression with safe access expression +/* For now usages located in nested scopes are not replaced with `it` because such usages can resolve to scope-specific symbols and */ +/* require additional checks which are currently not implemented */ +// IGNORE_K1 +class Foo(val a: Any) + +fun foo(n: Int, lambda: Foo.() -> Any) {} + +fun Foo.test() { + (a as? Int)?.let { foo(it) { a } } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/quickfix/surroundWithNullCheck/chainedUnsafeCall.kt b/plugins/kotlin/idea/tests/testData/quickfix/surroundWithNullCheck/chainedUnsafeCall.kt index 1d37e73820f3..191d4549c68e 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/surroundWithNullCheck/chainedUnsafeCall.kt +++ b/plugins/kotlin/idea/tests/testData/quickfix/surroundWithNullCheck/chainedUnsafeCall.kt @@ -1,5 +1,7 @@ // "Surround with null check" "false" // ACTION: Add non-null asserted (arg?.hashCode()!!) call +// ACTION: Convert to run +// ACTION: Convert to with // ACTION: Introduce local variable // ACTION: Put calls on separate lines // ACTION: Replace with safe (?.) call diff --git a/plugins/kotlin/idea/tests/testData/quickfix/wrapWithSafeLetCall/chainedUnsafeCall.kt b/plugins/kotlin/idea/tests/testData/quickfix/wrapWithSafeLetCall/chainedUnsafeCall.kt index d404e18b3043..8cbf4d4855ae 100644 --- a/plugins/kotlin/idea/tests/testData/quickfix/wrapWithSafeLetCall/chainedUnsafeCall.kt +++ b/plugins/kotlin/idea/tests/testData/quickfix/wrapWithSafeLetCall/chainedUnsafeCall.kt @@ -1,6 +1,8 @@ // "Wrap with '?.let { ... }' call" "false" // WITH_STDLIB // ACTION: Add non-null asserted (arg?.hashCode()!!) call +// ACTION: Convert to run +// ACTION: Convert to with // ACTION: Introduce local variable // ACTION: Put calls on separate lines // ACTION: Replace with safe (?.) call diff --git a/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/inference/common/BoundTypeCalculator.kt b/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/inference/common/BoundTypeCalculator.kt index 8f5517cdac78..e71575052378 100644 --- a/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/inference/common/BoundTypeCalculator.kt +++ b/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/inference/common/BoundTypeCalculator.kt @@ -4,9 +4,9 @@ package org.jetbrains.kotlin.idea.j2k.post.processing.inference.common import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.idea.base.psi.isNullExpression import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.psi.* diff --git a/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/inference/nullability/NullabilityBoundTypeEnhancer.kt b/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/inference/nullability/NullabilityBoundTypeEnhancer.kt index 293fd8e16205..f62ad12736c3 100644 --- a/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/inference/nullability/NullabilityBoundTypeEnhancer.kt +++ b/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/inference/nullability/NullabilityBoundTypeEnhancer.kt @@ -4,9 +4,9 @@ package org.jetbrains.kotlin.idea.j2k.post.processing.inference.nullability import javaslang.control.Option import org.jetbrains.kotlin.descriptors.ConstructorDescriptor +import org.jetbrains.kotlin.idea.base.psi.isNullExpression import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression import org.jetbrains.kotlin.idea.j2k.post.processing.inference.common.* import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.resolve.ResolutionFacade diff --git a/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/inference/nullability/NullabilityConstraintsCollector.kt b/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/inference/nullability/NullabilityConstraintsCollector.kt index ae3889de144b..0496553ef689 100644 --- a/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/inference/nullability/NullabilityConstraintsCollector.kt +++ b/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/inference/nullability/NullabilityConstraintsCollector.kt @@ -2,7 +2,7 @@ package org.jetbrains.kotlin.idea.j2k.post.processing.inference.nullability -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression +import org.jetbrains.kotlin.idea.base.psi.isNullExpression import org.jetbrains.kotlin.idea.j2k.post.processing.inference.common.BoundTypeCalculator import org.jetbrains.kotlin.idea.j2k.post.processing.inference.common.ConstraintBuilder import org.jetbrains.kotlin.idea.j2k.post.processing.inference.common.InferenceContext diff --git a/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/processings/diagnosticBasedProcessings.kt b/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/processings/diagnosticBasedProcessings.kt index 421bae8b7ff8..a7f3dc068b4a 100644 --- a/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/processings/diagnosticBasedProcessings.kt +++ b/plugins/kotlin/j2k/k1.new.post-processing/src/org/jetbrains/kotlin/idea/j2k/post/processing/processings/diagnosticBasedProcessings.kt @@ -7,8 +7,8 @@ import com.intellij.psi.search.LocalSearchScope import com.intellij.psi.search.searches.ReferencesSearch import org.jetbrains.kotlin.diagnostics.DiagnosticWithParameters2 import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.idea.base.psi.isNullExpression import org.jetbrains.kotlin.idea.caches.resolve.analyze -import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression import org.jetbrains.kotlin.idea.j2k.post.processing.diagnosticBasedProcessing import org.jetbrains.kotlin.idea.quickfix.NumberConversionFix import org.jetbrains.kotlin.idea.quickfix.RemoveUselessCastFix diff --git a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2InspectionTests.kt b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2InspectionTests.kt index 3650f2ad4bf3..707f0207c6a4 100644 --- a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2InspectionTests.kt +++ b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2InspectionTests.kt @@ -28,6 +28,7 @@ internal fun MutableTWorkspace.generateK2InspectionTests() { model("${idea}/inspectionsLocal/implicitThis") model("${idea}/inspectionsLocal/doubleNegation") model("${idea}/inspectionsLocal/enumValuesSoftDeprecate") + model("${idea}/inspectionsLocal/branched/ifThenToSafeAccess", pattern = Patterns.KT_WITHOUT_DOTS) model("${idea}/inspectionsLocal/conventionNameCalls/replaceGetOrSet") model("${idea}/inspectionsLocal/nullableBooleanElvis") model("${idea}/inspectionsLocal/redundantElvisReturnNull")