FIR IDE: quickfix for WrapWithSafeLetCall

There is some behavior change regarding the new WrapWithSafeLetCall quickfix

1. it now works correctly on binary expressions by wrapping it with `()`
2. it now looks for a nullable position upward and do the modification there,
   if possible. For example, consider the following code

   ```
   fun bar(s: String): String = s

   fun test(s: String?) {
     bar(bar(bar(<caret>s)))
   }
   ```

   After applying this fix, FE1.0 yields

   ```
   bar(bar(s?.let { bar(it) }))
   ```

   while the new implementation yields

   ```
   s?.let { bar(bar(bar(it))) }
   ```

   This behavior aligns with FE1.0 if `bar` accepts nullable values.
This commit is contained in:
Tianyu Geng
2021-04-28 23:20:53 +02:00
committed by Ilya Kirillov
parent 0eaab6d8a2
commit e1b542314a
62 changed files with 1190 additions and 26 deletions
@@ -377,7 +377,7 @@ private fun checkApplicabilityForArgumentType(
// Reaching here means argument types mismatch, and we want to record whether it's due to the nullability by checking a subtype
// relation with nullable expected type.
val isMismatchDueToNullability = AbstractTypeChecker.isSubtypeOf(
val isMismatchDueToNullability = argumentType.canBeNull && !actualExpectedType.isNullable && AbstractTypeChecker.isSubtypeOf(
context.session.typeContext,
argumentType,
actualExpectedType.withNullability(ConeNullability.NULLABLE, context.session.typeContext)
@@ -347,6 +347,8 @@ fun PsiElement.parameterIndex(): Int {
}
}
val KtValueArgument.argumentIndex: Int get() = (parent as KtValueArgumentList).arguments.indexOf(this)
fun KtModifierListOwner.isPrivate(): Boolean = hasModifier(KtTokens.PRIVATE_KEYWORD)
fun KtModifierListOwner.isProtected(): Boolean = hasModifier(KtTokens.PROTECTED_KEYWORD)
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.name;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class Name implements Comparable<Name> {
@NotNull
@@ -85,6 +86,12 @@ public final class Name implements Comparable<Name> {
}
}
@Nullable
public String getIdentifierOrNullIfSpecial() {
if (special) return null;
return asString();
}
@Override
public String toString() {
return name;
@@ -112,6 +112,12 @@ class MainKtQuickFixRegistrar : KtQuickFixRegistrar() {
// TODO: NON_EXHAUSTIVE_WHEN[_ON_SEALED_CLASS] will be replaced in future. We need to register the fix for those diagnostics as well
registerPsiQuickFixes(KtFirDiagnostic.NoElseInWhen::class, AddWhenElseBranchFix)
registerApplicator(WrapWithSafeLetCallFixFactories.forUnsafeCall)
registerApplicator(WrapWithSafeLetCallFixFactories.forUnsafeImplicitInvokeCall)
registerApplicator(WrapWithSafeLetCallFixFactories.forUnsafeInfixCall)
registerApplicator(WrapWithSafeLetCallFixFactories.forUnsafeOperatorCall)
registerApplicator(WrapWithSafeLetCallFixFactories.forArgumentTypeMismatch)
}
override val list: KtQuickFixesList = KtQuickFixesList.createCombined(
@@ -0,0 +1,342 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.quickfix.fixes
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.diagnostic.Logger
import com.intellij.psi.PsiElement
import com.intellij.psi.SmartPsiElementPointer
import com.intellij.psi.util.parentOfType
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.api.applicator.HLApplicator
import org.jetbrains.kotlin.idea.api.applicator.HLApplicatorInput
import org.jetbrains.kotlin.idea.api.applicator.applicator
import org.jetbrains.kotlin.idea.core.FirKotlinNameSuggester
import org.jetbrains.kotlin.idea.fir.api.fixes.HLQuickFix
import org.jetbrains.kotlin.idea.fir.api.fixes.diagnosticFixFactory
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.calls.KtCall
import org.jetbrains.kotlin.idea.frontend.api.calls.KtVariableWithInvokeFunctionCall
import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.types.expressions.OperatorConventions
object WrapWithSafeLetCallFixFactories {
class Input(
val nullableExpressionPointer: SmartPsiElementPointer<KtExpression>,
val suggestedVariableName: String,
val isImplicitInvokeCallToMemberProperty: Boolean,
) : HLApplicatorInput
private val LOG = Logger.getInstance(this::class.java)
/**
* Applicator that wraps a given target expression inside a `let` call on the input `nullableExpression`.
*
* Consider the following code snippet:
*
* ```
* fun test(s: String?) {
* println(s.length)
* }
* ```
*
* In this case, one use the applicator with the following arguments
* - target expression: `s.length`
* - nullable expression: `s`
* - suggestedVariableName: `myName`
* - isImplicitInvokeCallToMemberProperty: false
*
* Then the applicator changes the code to
*
* ```
* fun test(s: String?) {
* println(s?.let { myName -> myName.length })
* }
* ```
* `isImplicitInvokeCallToMemberProperty` controls the behavior when hoisting up the nullable expression. It should be set to true
* if the call is to a invocable member property.
*/
val applicator: HLApplicator<KtExpression, Input> = applicator {
familyAndActionName(KotlinBundle.lazyMessage("wrap.with.let.call"))
applyTo { targetExpression, input ->
val nullableExpression = input.nullableExpressionPointer.element ?: return@applyTo
if (!nullableExpression.parents.contains(targetExpression)) {
LOG.warn(
"Unexpected input for WrapWithSafeLetCall. Nullable expression '${nullableExpression.text}' should be a descendant" +
" of '${targetExpression.text}'."
)
return@applyTo
}
val suggestedVariableName = input.suggestedVariableName
val factory = KtPsiFactory(targetExpression)
fun getNewExpression(nullableExpressionText: String, expressionUnderLetText: String): KtExpression {
return when (suggestedVariableName) {
"it" -> factory.createExpressionByPattern("$0?.let { $1 }", nullableExpressionText, expressionUnderLetText)
else -> factory.createExpressionByPattern(
"$0?.let { $1 -> $2 }",
nullableExpressionText,
suggestedVariableName,
expressionUnderLetText
)
}
}
val callExpression = nullableExpression.parentOfType<KtCallExpression>(withSelf = true)
val qualifiedExpression = callExpression?.getQualifiedExpressionForSelector()
val receiverExpression = qualifiedExpression?.receiverExpression
if (receiverExpression != null && input.isImplicitInvokeCallToMemberProperty) {
// In this case, the nullable expression is an invocable member. For example consider the following
//
// interface Foo {
// val bar: (() -> Unit)?
// }
// fun test(foo: Foo) {
// foo.bar()
// }
//
// In this case, `foo.bar` is nullable and this fix should change the code to `foo.bar?.let { it() }`. But note that
// the PSI structure of the above code is
//
// - qualifiedExpression: foo.bar()
// - receiver: foo
// - operationTokenNode: .
// - selectorExpression: bar()
// - calleeExpression: bar
// - valueArgumentList: ()
//
// So we need to explicitly construct the nullable expression text `foo.bar`.
val nullableExpressionText =
"${receiverExpression.text}${qualifiedExpression.operationSign.value}${nullableExpression.text}"
val newInvokeCallText =
"${suggestedVariableName}${callExpression.valueArgumentList?.text ?: ""}${
callExpression.lambdaArguments.joinToString(
" ",
prefix = " "
) { it.text }
}"
if (qualifiedExpression == targetExpression) {
targetExpression.replace(getNewExpression(nullableExpressionText, newInvokeCallText))
} else {
qualifiedExpression.replace(factory.createExpression(newInvokeCallText))
targetExpression.replace(getNewExpression(nullableExpressionText, targetExpression.text))
}
} else {
val nullableExpressionText = when (nullableExpression) {
is KtBinaryExpression, is KtBinaryExpressionWithTypeRHS -> "(${nullableExpression.text})"
else -> nullableExpression.text
}
nullableExpression.replace(factory.createExpression(suggestedVariableName))
targetExpression.replace(getNewExpression(nullableExpressionText, targetExpression.text))
}
}
}
val forUnsafeCall = diagnosticFixFactory<KtFirDiagnostic.UnsafeCall> { diagnostic ->
val nullableExpression = diagnostic.receiverExpression
createWrapWithSafeLetCallInputForNullableExpressionIfMoreThanImmediateParentIsWrapped(nullableExpression)
}
val forUnsafeImplicitInvokeCall = diagnosticFixFactory<KtFirDiagnostic.UnsafeImplicitInvokeCall> { diagnostic ->
val callExpression = diagnostic.psi.parentOfType<KtCallExpression>(withSelf = true) ?: return@diagnosticFixFactory emptyList()
val callingFunctionalVariableInLocalScope =
isCallingFunctionalTypeVariableInLocalScope(callExpression) ?: return@diagnosticFixFactory emptyList()
createWrapWithSafeLetCallInputForNullableExpression(
callExpression.calleeExpression,
isImplicitInvokeCallToMemberProperty = !callingFunctionalVariableInLocalScope
)
}
private fun KtAnalysisSession.isCallingFunctionalTypeVariableInLocalScope(callExpression: KtCallExpression): Boolean? {
val calleeName = callExpression.calleeExpression?.text ?: return null
val callSite = callExpression.parent as? KtQualifiedExpression ?: callExpression
val functionalVariableSymbol = (callExpression.resolveCall() as? KtVariableWithInvokeFunctionCall)?.target ?: return false
val localScope = callExpression.containingKtFile.getScopeContextForPosition(callSite)
// If no symbol in the local scope contains the called symbol, then the symbol must be a member symbol.
return localScope.scopes.getCallableSymbols { it.identifierOrNullIfSpecial == calleeName }.any { it == functionalVariableSymbol }
}
val forUnsafeInfixCall = diagnosticFixFactory<KtFirDiagnostic.UnsafeInfixCall> { diagnostic ->
createWrapWithSafeLetCallInputForNullableExpressionIfMoreThanImmediateParentIsWrapped(diagnostic.receiverExpression)
}
val forUnsafeOperatorCall = diagnosticFixFactory<KtFirDiagnostic.UnsafeOperatorCall> { diagnostic ->
createWrapWithSafeLetCallInputForNullableExpressionIfMoreThanImmediateParentIsWrapped(diagnostic.receiverExpression)
}
val forArgumentTypeMismatch = diagnosticFixFactory<KtFirDiagnostic.ArgumentTypeMismatch> { diagnostic ->
if (diagnostic.isMismatchDueToNullability) createWrapWithSafeLetCallInputForNullableExpression(diagnostic.psi.wrappingExpressionOrSelf)
else emptyList()
}
private fun KtAnalysisSession.createWrapWithSafeLetCallInputForNullableExpressionIfMoreThanImmediateParentIsWrapped(
nullableExpression: KtExpression?,
isImplicitInvokeCallToMemberProperty: Boolean = false,
): List<IntentionAction> {
val surroundingExpression = nullableExpression?.surroundingExpression
if (
surroundingExpression == null ||
// If the surrounding expression is at a place that accepts null value, then we don't provide wrap with let call because the
// plain safe call operator (?.) is a better fix.
isExpressionAtNullablePosition(surroundingExpression)
) {
return emptyList()
}
// In addition, if there is no parent that is at a nullable position, then we don't offer wrapping with let either because
// it still doesn't fix the code. Hence, the plain safe call operator is a better fix.
val surroundingNullableExpression = findParentExpressionAtNullablePosition(nullableExpression) ?: return emptyList()
return createWrapWithSafeLetCallInputForNullableExpression(
nullableExpression,
isImplicitInvokeCallToMemberProperty,
surroundingNullableExpression
)
}
private fun KtAnalysisSession.createWrapWithSafeLetCallInputForNullableExpression(
nullableExpression: KtExpression?,
isImplicitInvokeCallToMemberProperty: Boolean = false,
surroundingExpression: KtExpression? = findParentExpressionAtNullablePosition(nullableExpression)
?: nullableExpression?.surroundingExpression
): List<IntentionAction> {
if (nullableExpression == null || surroundingExpression == null) return emptyList()
val existingNames =
nullableExpression.containingKtFile.getScopeContextForPosition(nullableExpression).scopes.getPossibleCallableNames()
.mapNotNull { it.identifierOrNullIfSpecial }
// Note, the order of the candidate matters. We would prefer the default `it` so the generated code won't need to declare the
// variable explicitly.
val candidateNames = listOfNotNull("it", getDeclaredParameterNameForArgument(nullableExpression))
val suggestedName = FirKotlinNameSuggester.suggestNameByMultipleNames(candidateNames) { it !in existingNames }
return listOf(
HLQuickFix(
surroundingExpression,
Input(nullableExpression.createSmartPointer(), suggestedName, isImplicitInvokeCallToMemberProperty),
applicator
)
)
}
private fun KtAnalysisSession.getDeclaredParameterNameForArgument(argumentExpression: KtExpression): String? {
val valueArgument = argumentExpression.parent as? KtValueArgument ?: return null
val successCallTarget =
(argumentExpression.parentOfType<KtCallExpression>()?.resolveCall()?.targetFunction?.candidates?.singleOrNull()) ?: return null
return successCallTarget.valueParameters.getOrNull(valueArgument.argumentIndex)?.name?.identifierOrNullIfSpecial
}
private fun KtAnalysisSession.findParentExpressionAtNullablePosition(expression: KtExpression?): KtExpression? {
if (expression == null) return null
var current = expression.surroundingExpression
while (current != null && !isExpressionAtNullablePosition(current)) {
current = current.surroundingExpression
}
return current
}
private fun KtAnalysisSession.isExpressionAtNullablePosition(expression: KtExpression): Boolean {
val parent = expression.parent
return when {
parent is KtProperty && expression == parent.initializer -> {
if (parent.typeReference == null) return true
val symbol = parent.getSymbol()
(symbol as? KtCallableSymbol)?.annotatedType?.type?.isMarkedNullable ?: true
}
parent is KtValueArgument && expression == parent.getArgumentExpression() -> {
// In the following logic, if call is missing, unresolved, or contains error, we just stop here so the wrapped call would be
// inserted here.
val functionCall = parent.getParentOfType<KtCallExpression>(strict = true) ?: return true
val resolvedCall = functionCall.resolveCall() ?: return true
return doesFunctionAcceptNull(resolvedCall, parent.argumentIndex) ?: true
}
parent is KtBinaryExpression -> {
if (parent.operationToken in KtTokens.ALL_ASSIGNMENTS && parent.left == expression) {
// If current expression is an l-value in an assignment, just keep going up because one cannot assign to a let call.
return false
}
val resolvedCall = parent.resolveCall()
when {
resolvedCall != null -> {
// The binary expression is a call to some function
val isInExpression = parent.operationToken in OperatorConventions.IN_OPERATIONS
val expressionIsArg = when {
parent.left == expression -> isInExpression
parent.right == expression -> !isInExpression
else -> return true
}
doesFunctionAcceptNull(resolvedCall, if (expressionIsArg) 0 else -1) ?: true
}
parent.operationToken == KtTokens.EQ -> {
// The binary expression is a variable assignment
parent.left?.getKtType()?.isMarkedNullable ?: true
}
// The binary expression is some unrecognized constructs so we stop here.
else -> true
}
}
// Qualified expression can always just be updated with a safe call operator to to make it accept nullable receiver. Hence we
// don't want to offer the wrap with let call quickfix.
parent is KtQualifiedExpression && parent.receiverExpression == expression -> true
// Ideally we should do more analysis on the control structure to determine if the type can actually allow null here. But that
// may be too fancy and can be counter-intuitive to user.
parent is KtContainerNodeForControlStructureBody -> true
// Again, for simplicity's sake, we treat block as a place that can accept expression of any type. This is not strictly true
// for lambda expressions, but it results in a more deterministic behavior.
parent is KtBlockExpression -> true
else -> false
}
}
/**
* Checks if the called function can accept null for the argument at the given index. If the index is -1, then we check the receiver
* type. The function returns null if any necessary assumptions are not met. For example, if the call is not resolved to a unique
* function or the function doesn't have a parameter at the given index. Then caller can do whatever needed to cover such cases.
*/
private fun KtAnalysisSession.doesFunctionAcceptNull(call: KtCall, index: Int): Boolean? {
val symbol = call.targetFunction.candidates.singleOrNull() ?: return null
if (index == -1) {
// Null extension receiver means the function does not accept extension receiver and hence cannot be invoked on a nullable
// value.
return (symbol as? KtCallableSymbol)?.receiverType?.type?.isMarkedNullable == true
}
return symbol.valueParameters.getOrNull(index)?.annotatedType?.type?.isMarkedNullable
}
private val KtExpression.surroundingExpression: KtExpression?
get() {
var current: PsiElement? = parent
while (true) {
// Never go above declarations or control structure so that the wrap-with-let quickfix only applies to a "small" scope
// around the nullable expression.
if (current == null ||
current is KtContainerNodeForControlStructureBody ||
current is KtWhenEntry ||
current is KtParameter ||
current is KtProperty ||
current is KtReturnExpression ||
current is KtDeclaration ||
current is KtBlockExpression
) {
return null
}
val parent = current.parent
if (current is KtExpression &&
// We skip parenthesized expression and labeled expressions.
current !is KtParenthesizedExpression && current !is KtLabeledExpression &&
// We skip KtCallExpression if it's the `selectorExpression` of a qualified expression because the selector expression is
// not an actual expression that can be swapped for any arbitrary expressions.
(parent !is KtQualifiedExpression || parent.selectorExpression != current)
) {
return current
}
current = parent
}
}
private val PsiElement.wrappingExpressionOrSelf: KtExpression? get() = parentOfType(withSelf = true)
}
@@ -1645,4 +1645,172 @@ public class HighLevelQuickFixTestGenerated extends AbstractHighLevelQuickFixTes
runTest("idea/testData/quickfix/when/twoElseBranchesInWhen.kt");
}
}
@TestMetadata("idea/testData/quickfix/wrapWithSafeLetCall")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class WrapWithSafeLetCall extends AbstractHighLevelQuickFixTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInWrapWithSafeLetCall() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/wrapWithSafeLetCall"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true);
}
@TestMetadata("argumentNullable.kt")
public void testArgumentNullable() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/argumentNullable.kt");
}
@TestMetadata("callWithLambdaArgument.kt")
public void testCallWithLambdaArgument() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/callWithLambdaArgument.kt");
}
@TestMetadata("chainedCallTypeMismatch.kt")
public void testChainedCallTypeMismatch() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/chainedCallTypeMismatch.kt");
}
@TestMetadata("chainedCallTypeMismatch2.kt")
public void testChainedCallTypeMismatch2() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/chainedCallTypeMismatch2.kt");
}
@TestMetadata("chainedUnsafeCall.kt")
public void testChainedUnsafeCall() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/chainedUnsafeCall.kt");
}
@TestMetadata("expressionUnsafeCall.kt")
public void testExpressionUnsafeCall() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/expressionUnsafeCall.kt");
}
@TestMetadata("extensionMethod.kt")
public void testExtensionMethod() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/extensionMethod.kt");
}
@TestMetadata("extentionFunctionCall.kt")
public void testExtentionFunctionCall() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/extentionFunctionCall.kt");
}
@TestMetadata("extentionFunctionCall2.kt")
public void testExtentionFunctionCall2() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/extentionFunctionCall2.kt");
}
@TestMetadata("inOperator.kt")
public void testInOperator() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/inOperator.kt");
}
@TestMetadata("insideLet.kt")
public void testInsideLet() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/insideLet.kt");
}
@TestMetadata("invokeFuncUnsafe.kt")
public void testInvokeFuncUnsafe() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/invokeFuncUnsafe.kt");
}
@TestMetadata("invokeFunctionType.kt")
public void testInvokeFunctionType() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/invokeFunctionType.kt");
}
@TestMetadata("invokeFunctionType2.kt")
public void testInvokeFunctionType2() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/invokeFunctionType2.kt");
}
@TestMetadata("invokeUnsafe.kt")
public void testInvokeUnsafe() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/invokeUnsafe.kt");
}
@TestMetadata("nameAlreadyInScope.kt")
public void testNameAlreadyInScope() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/nameAlreadyInScope.kt");
}
@TestMetadata("nullableBinaryExpression.kt")
public void testNullableBinaryExpression() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/nullableBinaryExpression.kt");
}
@TestMetadata("nullableReceiver.kt")
public void testNullableReceiver() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/nullableReceiver.kt");
}
@TestMetadata("objectQualifier.kt")
public void testObjectQualifier() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/objectQualifier.kt");
}
@TestMetadata("refactorNullableFunctionTypeProperty1.kt")
public void testRefactorNullableFunctionTypeProperty1() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/refactorNullableFunctionTypeProperty1.kt");
}
@TestMetadata("refactorNullableFunctionTypeProperty2.kt")
public void testRefactorNullableFunctionTypeProperty2() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/refactorNullableFunctionTypeProperty2.kt");
}
@TestMetadata("unsafe.kt")
public void testUnsafe() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/unsafe.kt");
}
@TestMetadata("unsafe2.kt")
public void testUnsafe2() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/unsafe2.kt");
}
@TestMetadata("unsafeForNullableParameter.kt")
public void testUnsafeForNullableParameter() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/unsafeForNullableParameter.kt");
}
@TestMetadata("unstableValue.kt")
public void testUnstableValue() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/unstableValue.kt");
}
@TestMetadata("wrapAllNonNullablePositions1.kt")
public void testWrapAllNonNullablePositions1() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/wrapAllNonNullablePositions1.kt");
}
@TestMetadata("wrapAllNonNullablePositions2.kt")
public void testWrapAllNonNullablePositions2() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/wrapAllNonNullablePositions2.kt");
}
@TestMetadata("wrapAllNonNullablePositions3.kt")
public void testWrapAllNonNullablePositions3() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/wrapAllNonNullablePositions3.kt");
}
@TestMetadata("wrapAllNonNullablePositions4.kt")
public void testWrapAllNonNullablePositions4() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/wrapAllNonNullablePositions4.kt");
}
@TestMetadata("wrapAllNonNullablePositions5.kt")
public void testWrapAllNonNullablePositions5() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/wrapAllNonNullablePositions5.kt");
}
@TestMetadata("wrapAllNonNullablePositions6.kt")
public void testWrapAllNonNullablePositions6() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/wrapAllNonNullablePositions6.kt");
}
}
}
@@ -6,13 +6,15 @@
package org.jetbrains.kotlin.idea.frontend.api.calls
import org.jetbrains.kotlin.idea.frontend.api.diagnostics.KtDiagnostic
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionLikeSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtVariableLikeSymbol
/**
* Represents direct or indirect (via invoke) function call from Kotlin code
*/
sealed class KtCall {
abstract val isErrorCall: Boolean
abstract val targetFunction: KtCallTarget
}
/**
@@ -22,7 +24,10 @@ sealed class KtCall {
* f() // functional type call
* }
*/
class KtFunctionalTypeVariableCall(val target: KtVariableLikeSymbol) : KtCall() {
class KtFunctionalTypeVariableCall(
val target: KtVariableLikeSymbol,
override val targetFunction: KtCallTarget
) : KtCall() {
override val isErrorCall: Boolean get() = false
}
@@ -30,7 +35,6 @@ class KtFunctionalTypeVariableCall(val target: KtVariableLikeSymbol) : KtCall()
* Direct or indirect call of function declared by user
*/
sealed class KtDeclaredFunctionCall : KtCall() {
abstract val targetFunction: KtCallTarget
override val isErrorCall: Boolean
get() = targetFunction is KtErrorCallTarget
}
@@ -46,10 +50,8 @@ sealed class KtDeclaredFunctionCall : KtCall() {
*/
class KtVariableWithInvokeFunctionCall(
val target: KtVariableLikeSymbol,
val invokeFunction: KtCallTarget,
) : KtDeclaredFunctionCall() {
override val targetFunction: KtCallTarget get() = invokeFunction
}
override val targetFunction: KtCallTarget
) : KtDeclaredFunctionCall()
/**
* Simple function call, e.g.,
@@ -10,32 +10,30 @@ import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.expressions.FirSafeCallExpression
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.references.FirErrorNamedReference
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.resolve.calls.FirErrorReferenceWithCandidate
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.idea.fir.getCandidateSymbols
import org.jetbrains.kotlin.idea.fir.isImplicitFunctionCall
import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFir
import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFirSafe
import org.jetbrains.kotlin.idea.frontend.api.*
import org.jetbrains.kotlin.idea.frontend.api.calls.*
import org.jetbrains.kotlin.idea.frontend.api.components.KtCallResolver
import org.jetbrains.kotlin.idea.frontend.api.diagnostics.KtNonBoundToPsiErrorDiagnostic
import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.fir.buildSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionLikeSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtVariableLikeSymbol
import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken
import org.jetbrains.kotlin.idea.frontend.api.withValidityAssertion
import org.jetbrains.kotlin.idea.references.FirReferenceResolveHelper
import org.jetbrains.kotlin.idea.util.getElementTextInContext
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtElement
internal class KtFirCallResolver(
override val analysisSession: KtFirAnalysisSession,
@@ -74,16 +72,19 @@ internal class KtFirCallResolver(
}
}
private fun FirFunctionCall.createCallByVariableLikeSymbolCall(variableLikeSymbol: KtVariableLikeSymbol) =
when (val callReference = calleeReference) {
private fun FirFunctionCall.createCallByVariableLikeSymbolCall(variableLikeSymbol: KtVariableLikeSymbol): KtCall? {
return when (val callReference = calleeReference) {
is FirResolvedNamedReference -> {
val functionSymbol = callReference.resolvedSymbol as? FirNamedFunctionSymbol
when (functionSymbol?.callableId) {
null -> null
in kotlinFunctionInvokeCallableIds -> KtFunctionalTypeVariableCall(variableLikeSymbol)
else -> (callReference.resolvedSymbol.fir.buildSymbol(firSymbolBuilder) as? KtFunctionSymbol)
?.let { KtVariableWithInvokeFunctionCall(variableLikeSymbol, KtSuccessCallTarget(it)) }
}
val callableId = functionSymbol?.callableId ?: return null
(callReference.resolvedSymbol.fir.buildSymbol(firSymbolBuilder) as? KtFunctionSymbol)
?.let {
if (callableId in kotlinFunctionInvokeCallableIds) {
KtFunctionalTypeVariableCall(variableLikeSymbol, KtSuccessCallTarget(it))
} else {
KtVariableWithInvokeFunctionCall(variableLikeSymbol, KtSuccessCallTarget(it))
}
}
}
is FirErrorNamedReference -> KtVariableWithInvokeFunctionCall(
variableLikeSymbol,
@@ -91,6 +92,7 @@ internal class KtFirCallResolver(
)
else -> error("Unexpected call reference ${callReference::class.simpleName}")
}
}
private fun FirFunctionCall.asSimpleFunctionCall(): KtFunctionCall? {
val target = when (val calleeReference = calleeReference) {
@@ -19,7 +19,7 @@ class KtFirInvokeFunctionReference(expression: KtCallExpression) : KtInvokeFunct
override fun KtAnalysisSession.resolveToSymbols(): Collection<KtSymbol> {
val call = expression.resolveCall() ?: return emptyList()
if (call is KtVariableWithInvokeFunctionCall) {
return call.invokeFunction.candidates
return call.targetFunction.candidates
}
return emptyList()
}
@@ -0,0 +1,12 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface Foo {
val f: ((() -> Unit) -> String)?
}
fun test(foo: Foo) {
bar(foo.<caret>f {})
}
fun bar(s: String) {}
@@ -0,0 +1,12 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface Foo {
val f: ((() -> Unit) -> String)?
}
fun test(foo: Foo) {
foo.f?<caret>.let { bar(it {}) }
}
fun bar(s: String) {}
@@ -0,0 +1,14 @@
// "Wrap with '?.let { ... }' call" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
// ERROR: Type mismatch: inferred type is String? but String was expected
// WITH_RUNTIME
interface Foo {
val f: ((() -> Unit) -> String)?
}
fun test(foo: Foo) {
bar(foo.<caret>f {})
}
fun bar(s: String) {}
@@ -0,0 +1,14 @@
// "Wrap with '?.let { ... }' call" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
// ERROR: Type mismatch: inferred type is String? but String was expected
// WITH_RUNTIME
interface Foo {
val f: ((() -> Unit) -> String)?
}
fun test(foo: Foo) {
bar(foo.f?.let { it {} })
}
fun bar(s: String) {}
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun Int.foo(x: Int) = this + x
val arg: Int? = 42
val res = 24.hashCode().foo(<caret>arg) + 1
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun Int.foo(x: Int) = this + x
val arg: Int? = 42
val res = arg?.let { 24.hash<caret>Code().foo(it) + 1 }
@@ -1,8 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
// ERROR: Operator call corresponds to a dot-qualified call 'arg?.let { 24.hashCode().foo(it) }.plus(1)' which is not allowed on a nullable receiver 'arg?.let { 24.hashCode().foo(it) }'.
fun Int.foo(x: Int) = this + x
val arg: Int? = 42
val res = 24.hashCode().foo(<caret>arg)
val res = 24.hashCode().foo(<caret>arg) + 1
@@ -1,8 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
// ERROR: Operator call corresponds to a dot-qualified call 'arg?.let { 24.hashCode().foo(it) }.plus(1)' which is not allowed on a nullable receiver 'arg?.let { 24.hashCode().foo(it) }'.
fun Int.foo(x: Int) = this + x
val arg: Int? = 42
val res = arg?.let { 24.hashCode().foo(it) }
val res = arg?.let { 24.hashCode().foo(it) } + 1
@@ -0,0 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
// ERROR: Operator call corresponds to a dot-qualified call 'arg?.let { 24.hashCode().foo(it) }.plus(1)' which is not allowed on a nullable receiver 'arg?.let { 24.hashCode().foo(it) }'.
fun Int.foo(x: Int) = this + x
val arg: Int? = 42
val res: Int = 24.hashCode().foo(<caret>arg) + 1
@@ -0,0 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
// ERROR: Operator call corresponds to a dot-qualified call 'arg?.let { 24.hashCode().foo(it) }.plus(1)' which is not allowed on a nullable receiver 'arg?.let { 24.hashCode().foo(it) }'.
fun Int.foo(x: Int) = this + x
val arg: Int? = 42
val res: Int = arg?.let { 24.hashCode().foo(it) } + 1
@@ -0,0 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// ACTION: Add non-null asserted (!!) call
// ACTION: Replace overloaded operator with function call
// ACTION: Replace with safe (?.) call
// ACTION: Surround with null check
fun test(l: List<String>?, s: String) {
if (s <caret>in l) {}
}
@@ -0,0 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// ACTION: Add non-null asserted (!!) call
// ACTION: Replace overloaded operator with function call
// ACTION: Replace with safe (?.) call
// ACTION: Surround with null check
fun test(l: List<String>?, s: String) {
l?.let<caret> { if (s in it) {} }
}
@@ -0,0 +1,10 @@
// "Wrap with '?.let { ... }' call" "false"
// ACTION: Add non-null asserted (!!) call
// ACTION: Replace overloaded operator with function call
// ACTION: Replace with safe (?.) call
// ACTION: Surround with null check
// ERROR: Operator call corresponds to a dot-qualified call 'l.contains(s)' which is not allowed on a nullable receiver 'l'.
fun test(l: List<String>?, s: String) {
if (s <caret>in l) {}
}
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun foo(x: String?, y: String) {
y.let { bar(<caret>x, it) }
}
fun bar(s: String, t: String) = s.hashCode() + t.hashCode()
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun foo(x: String?, y: String) {
y.let { x?.l<caret>et { s -> bar(s, it) } }
}
fun bar(s: String, t: String) = s.hashCode() + t.hashCode()
@@ -0,0 +1,11 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
val it = ""
fun test(s: String?) {
val name = ""
bar(<caret>s)
}
fun bar(name: String) {}
@@ -0,0 +1,11 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
val it = ""
fun test(s: String?) {
val name = ""
s?.let { it1 -> bar(it1) }
}
fun bar(name: String) {}
@@ -0,0 +1,12 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface A
operator fun A?.plus(a: A?): A? = this
fun test(a1: A, a2: A) {
notNull(<caret>a1 + a2)
}
fun notNull(t: A): A = t
@@ -0,0 +1,12 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface A
operator fun A?.plus(a: A?): A? = this
fun test(a1: A, a2: A) {
(a1 + a2<caret>)?.let { notNull(it) }
}
fun notNull(t: A): A = t
@@ -0,0 +1,12 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface A
operator fun A?.plus(a: A?): A? = this
fun test(a1: A, a2: A) {
notNull(<caret>a1 + a2)
}
fun notNull(t: A): A = t
@@ -0,0 +1,12 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface A
operator fun A?.plus(a: A?): A? = this
fun test(a1: A, a2: A) {
a1 + a2?.let { notNull(it) }
}
fun notNull(t: A): A = t
@@ -0,0 +1,16 @@
// "Wrap with '?.let { ... }' call" "false"
// ACTION: Add non-null asserted (!!) call
// ACTION: Convert to run
// ACTION: Convert to with
// ACTION: Replace with safe (?.) call
// ACTION: Surround with null check
// ERROR: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type B?
// WITH_RUNTIME
class A {
fun foo() {}
}
class B(val a: A)
fun test(b: B?) {
b<caret>.a.foo() // b.a is UNSAFE_CALL
}
@@ -0,0 +1,15 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface Str {
val foo: (() -> Unit)?
}
object Str2 {
val foo2: (Str.() -> Unit)? = null
fun bar(s: Str) {
s.<caret>foo()
}
}
@@ -0,0 +1,15 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface Str {
val foo: (() -> Unit)?
}
object Str2 {
val foo2: (Str.() -> Unit)? = null
fun bar(s: Str) {
s.foo?.let { it() }
}
}
@@ -0,0 +1,15 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface Str {
val foo: (() -> Unit)?
}
object Str2 {
val foo2: (Str.() -> Unit)? = null
fun bar(s: Str) {
s.<caret>foo2()
}
}
@@ -0,0 +1,15 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface Str {
val foo: (() -> Unit)?
}
object Str2 {
val foo2: (Str.() -> Unit)? = null
fun bar(s: Str) {
fo<caret>o2?.let { s.it() }
}
}
@@ -0,0 +1,15 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
interface Str {
val foo: (() -> Unit)?
}
object Str2 {
val foo2: (Str.() -> Unit)? = null
fun bar(s: Str) {
s.<caret>foo2()
}
}
@@ -0,0 +1,18 @@
// "Wrap with '?.let { ... }' call" "true"
// ERROR: Expression 'it' of type 'Unit' cannot be invoked as a function. The function 'invoke()' is not found
// ERROR: Function invocation 'foo2()' expected
// ERROR: Reference has a nullable type '(Str.() -> Unit)?', use explicit '?.invoke()' to make a function-like call instead
// WITH_RUNTIME
interface Str {
val foo: (() -> Unit)?
}
object Str2 {
val foo2: (Str.() -> Unit)? = null
fun bar(s: Str) {
s.foo2?.let { it() }
}
}
@@ -0,0 +1,12 @@
// "Wrap with '?.let { ... }' call" "false"
// ACTION: Add 's =' to argument
// ACTION: Add non-null asserted (!!) call
// ACTION: Replace with safe (?.) call
// ACTION: Surround with null check
// DISABLE-ERRORS
// WITH_RUNTIME
fun foo(s: String?) {}
fun bar(s: String?) {
foo(s<caret>.substring(1))
}
@@ -0,0 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
nullable(nullable(notNull(notNull(<caret>s))))
}
fun notNull(name: String): String = name
fun nullable(name: String?): String = ""
@@ -0,0 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
nullable(nullable(s?.let { notNull<caret>(notNull(it)) }))
}
fun notNull(name: String): String = name
fun nullable(name: String?): String = ""
@@ -0,0 +1,9 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
nullable(nullable(notNull(notNull(<caret>s))))
}
fun notNull(name: String): String = name
fun nullable(name: String?): String = ""
@@ -0,0 +1,11 @@
// "Wrap with '?.let { ... }' call" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
// ERROR: Type mismatch: inferred type is String? but String was expected
// WITH_RUNTIME
fun test(s: String?) {
nullable(nullable(notNull(s?.let { notNull(it) })))
}
fun notNull(name: String): String = name
fun nullable(name: String?): String = ""
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
val s2 = notNull(notNull(<caret>s))
}
fun notNull(name: String): String = name
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
val s2 = s?.let { notNull<caret>(notNull(it)) }
}
fun notNull(name: String): String = name
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
val s2 = notNull(notNull(<caret>s))
}
fun notNull(name: String): String = name
@@ -0,0 +1,10 @@
// "Wrap with '?.let { ... }' call" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
// ERROR: Type mismatch: inferred type is String? but String was expected
// WITH_RUNTIME
fun test(s: String?) {
val s2 = notNull(s?.let { notNull(it) })
}
fun notNull(name: String): String = name
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
notNull(notNull(<caret>s))
}
fun notNull(name: String): String = name
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
s?.let { notNull<caret>(notNull(it)) }
}
fun notNull(name: String): String = name
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?) {
notNull(notNull(<caret>s))
}
fun notNull(name: String): String = name
@@ -0,0 +1,10 @@
// "Wrap with '?.let { ... }' call" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
// ERROR: Type mismatch: inferred type is String? but String was expected
// WITH_RUNTIME
fun test(s: String?) {
notNull(s?.let { notNull(it) })
}
fun notNull(name: String): String = name
@@ -0,0 +1,10 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?): String? {
if (true) {
notNull(notNull(<caret>s))
}
}
fun notNull(name: String): String = name
@@ -0,0 +1,10 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?): String? {
if (true) {
s?.let { notNull<caret>(notNull(it)) }
}
}
fun notNull(name: String): String = name
@@ -0,0 +1,10 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?): String? {
if (true) {
notNull(notNull(<caret>s))
}
}
fun notNull(name: String): String = name
@@ -0,0 +1,13 @@
// "Wrap with '?.let { ... }' call" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
// ERROR: A 'return' expression required in a function with a block body ('{...}')
// ERROR: Type mismatch: inferred type is String? but String was expected
// WITH_RUNTIME
fun test(s: String?): String? {
if (true) {
notNull(s?.let { notNull(it) })
}
}
fun notNull(name: String): String = name
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?): String {
while (true) notNull(notNull(<caret>s))
}
fun notNull(name: String): String = name
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?): String {
while (true) s?.let { notNull<caret>(notNull(it)) }
}
fun notNull(name: String): String = name
@@ -0,0 +1,8 @@
// "Wrap with '?.let { ... }' call" "true"
// WITH_RUNTIME
fun test(s: String?): String {
while (true) notNull(notNull(<caret>s))
}
fun notNull(name: String): String = name
@@ -0,0 +1,10 @@
// "Wrap with '?.let { ... }' call" "true"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
// ERROR: Type mismatch: inferred type is String? but String was expected
// WITH_RUNTIME
fun test(s: String?): String {
while (true) notNull(s?.let { notNull(it) })
}
fun notNull(name: String): String = name
@@ -0,0 +1,19 @@
// "Wrap with '?.let { ... }' call" "true"
// ACTION: Add 'a =' to argument
// ACTION: Add non-null asserted (!!) call
// ACTION: Flip '+'
// ACTION: Introduce local variable
// ACTION: Replace overloaded operator with function call
// ACTION: Replace with safe (?.) call
// ACTION: Surround with null check
// WITH_RUNTIME
interface A {
operator fun plus(a: A): A = this
}
fun test(a1: A?, a2: A) {
notNull(a1 <caret>+ a2)
}
fun notNull(a: A): A = a
@@ -0,0 +1,19 @@
// "Wrap with '?.let { ... }' call" "true"
// ACTION: Add 'a =' to argument
// ACTION: Add non-null asserted (!!) call
// ACTION: Flip '+'
// ACTION: Introduce local variable
// ACTION: Replace overloaded operator with function call
// ACTION: Replace with safe (?.) call
// ACTION: Surround with null check
// WITH_RUNTIME
interface A {
operator fun plus(a: A): A = this
}
fun test(a1: A?, a2: A) {
a1?.let { n<caret>otNull(it + a2) }
}
fun notNull(a: A): A = a
@@ -0,0 +1,21 @@
// "Wrap with '?.let { ... }' call" "false"
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
// ACTION: Add 'a =' to argument
// ACTION: Add non-null asserted (!!) call
// ACTION: Flip '+'
// ACTION: Introduce local variable
// ACTION: Replace overloaded operator with function call
// ACTION: Replace with safe (?.) call
// ACTION: Surround with null check
// ERROR: Operator call corresponds to a dot-qualified call 'a1.plus(a2)' which is not allowed on a nullable receiver 'a1'.
// WITH_RUNTIME
interface A {
operator fun plus(a: A): A = this
}
fun test(a1: A?, a2: A) {
notNull(a1 <caret>+ a2)
}
fun notNull(a: A): A = a
@@ -15545,11 +15545,21 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/argumentNullable.kt");
}
@TestMetadata("callWithLambdaArgument.kt")
public void testCallWithLambdaArgument() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/callWithLambdaArgument.kt");
}
@TestMetadata("chainedCallTypeMismatch.kt")
public void testChainedCallTypeMismatch() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/chainedCallTypeMismatch.kt");
}
@TestMetadata("chainedCallTypeMismatch2.kt")
public void testChainedCallTypeMismatch2() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/chainedCallTypeMismatch2.kt");
}
@TestMetadata("chainedUnsafeCall.kt")
public void testChainedUnsafeCall() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/chainedUnsafeCall.kt");
@@ -15575,6 +15585,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/extentionFunctionCall2.kt");
}
@TestMetadata("inOperator.kt")
public void testInOperator() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/inOperator.kt");
}
@TestMetadata("insideLet.kt")
public void testInsideLet() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/insideLet.kt");
@@ -15600,11 +15615,36 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/invokeUnsafe.kt");
}
@TestMetadata("nameAlreadyInScope.kt")
public void testNameAlreadyInScope() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/nameAlreadyInScope.kt");
}
@TestMetadata("nullableBinaryExpression.kt")
public void testNullableBinaryExpression() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/nullableBinaryExpression.kt");
}
@TestMetadata("nullableReceiver.kt")
public void testNullableReceiver() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/nullableReceiver.kt");
}
@TestMetadata("objectQualifier.kt")
public void testObjectQualifier() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/objectQualifier.kt");
}
@TestMetadata("refactorNullableFunctionTypeProperty1.kt")
public void testRefactorNullableFunctionTypeProperty1() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/refactorNullableFunctionTypeProperty1.kt");
}
@TestMetadata("refactorNullableFunctionTypeProperty2.kt")
public void testRefactorNullableFunctionTypeProperty2() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/refactorNullableFunctionTypeProperty2.kt");
}
@TestMetadata("unsafe.kt")
public void testUnsafe() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/unsafe.kt");
@@ -15624,6 +15664,36 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
public void testUnstableValue() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/unstableValue.kt");
}
@TestMetadata("wrapAllNonNullablePositions1.kt")
public void testWrapAllNonNullablePositions1() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/wrapAllNonNullablePositions1.kt");
}
@TestMetadata("wrapAllNonNullablePositions2.kt")
public void testWrapAllNonNullablePositions2() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/wrapAllNonNullablePositions2.kt");
}
@TestMetadata("wrapAllNonNullablePositions3.kt")
public void testWrapAllNonNullablePositions3() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/wrapAllNonNullablePositions3.kt");
}
@TestMetadata("wrapAllNonNullablePositions4.kt")
public void testWrapAllNonNullablePositions4() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/wrapAllNonNullablePositions4.kt");
}
@TestMetadata("wrapAllNonNullablePositions5.kt")
public void testWrapAllNonNullablePositions5() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/wrapAllNonNullablePositions5.kt");
}
@TestMetadata("wrapAllNonNullablePositions6.kt")
public void testWrapAllNonNullablePositions6() throws Exception {
runTest("idea/testData/quickfix/wrapWithSafeLetCall/wrapAllNonNullablePositions6.kt");
}
}
@TestMetadata("idea/testData/quickfix/wrongLongSuffix")