FIR IDE: Enable AddExclExclCallFix for UNSAFE_CALL,
UNSAFE_OPERATOR_CALL, UNSAFE_INFIX_CALL, ITERATOR_ON_NULLABLE, ARGUMENT_TYPE_MISMATCH, RETURN_TYPE_MISMATCH. TODO: Don't offer fix when target is known to be null (from data flow analysis).
This commit is contained in:
committed by
Ilya Kirillov
parent
71a8d9c0bb
commit
db82797f58
@@ -103,6 +103,12 @@ class MainKtQuickFixRegistrar : KtQuickFixRegistrar() {
|
|||||||
registerPsiQuickFixes(KtFirDiagnostic.UnnecessarySafeCall::class, ReplaceWithDotCallFix)
|
registerPsiQuickFixes(KtFirDiagnostic.UnnecessarySafeCall::class, ReplaceWithDotCallFix)
|
||||||
registerPsiQuickFixes(KtFirDiagnostic.UnnecessaryNotNullAssertion::class, RemoveExclExclCallFix)
|
registerPsiQuickFixes(KtFirDiagnostic.UnnecessaryNotNullAssertion::class, RemoveExclExclCallFix)
|
||||||
registerApplicator(ReplaceCallFixFactories.unsafeCallFactory)
|
registerApplicator(ReplaceCallFixFactories.unsafeCallFactory)
|
||||||
|
registerApplicator(AddExclExclCallFixFactories.unsafeCallFactory)
|
||||||
|
registerApplicator(AddExclExclCallFixFactories.unsafeInfixCallFactory)
|
||||||
|
registerApplicator(AddExclExclCallFixFactories.unsafeOperatorCallFactory)
|
||||||
|
registerApplicator(AddExclExclCallFixFactories.iteratorOnNullableFactory)
|
||||||
|
registerApplicator(TypeMismatchFactories.argumentTypeMismatchFactory)
|
||||||
|
registerApplicator(TypeMismatchFactories.returnTypeMismatchFactory)
|
||||||
|
|
||||||
// TODO: NON_EXHAUSTIVE_WHEN[_ON_SEALED_CLASS] will be replaced in future. We need to register the fix for those diagnostics as well
|
// 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)
|
registerPsiQuickFixes(KtFirDiagnostic.NoElseInWhen::class, AddWhenElseBranchFix)
|
||||||
|
|||||||
+96
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* 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.psi.PsiElement
|
||||||
|
import org.jetbrains.kotlin.idea.fir.api.fixes.diagnosticFixFactory
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol
|
||||||
|
import org.jetbrains.kotlin.idea.quickfix.AddExclExclCallFix
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
|
|
||||||
|
object AddExclExclCallFixFactories {
|
||||||
|
val unsafeCallFactory = diagnosticFixFactory<KtFirDiagnostic.UnsafeCall> { diagnostic ->
|
||||||
|
getFixForUnsafeCall(diagnostic.psi)
|
||||||
|
}
|
||||||
|
|
||||||
|
val unsafeInfixCallFactory = diagnosticFixFactory<KtFirDiagnostic.UnsafeInfixCall> { diagnostic ->
|
||||||
|
getFixForUnsafeCall(diagnostic.psi)
|
||||||
|
}
|
||||||
|
|
||||||
|
val unsafeOperatorCallFactory = diagnosticFixFactory<KtFirDiagnostic.UnsafeOperatorCall> { diagnostic ->
|
||||||
|
getFixForUnsafeCall(diagnostic.psi)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtAnalysisSession.getFixForUnsafeCall(psi: PsiElement): List<IntentionAction> {
|
||||||
|
val (target, hasImplicitReceiver) = when (psi) {
|
||||||
|
// `foo.bar` -> `foo!!.bar`
|
||||||
|
is KtDotQualifiedExpression -> psi.receiverExpression to false
|
||||||
|
|
||||||
|
// `foo[bar]` -> `foo!![bar]`
|
||||||
|
is KtArrayAccessExpression -> psi.arrayExpression to false
|
||||||
|
|
||||||
|
is KtCallableReferenceExpression -> psi.lhs.let { lhs ->
|
||||||
|
if (lhs != null) {
|
||||||
|
// `foo::bar` -> `foo!!::bar`
|
||||||
|
lhs to false
|
||||||
|
} else {
|
||||||
|
// `::bar -> this!!::bar`
|
||||||
|
psi to true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// `bar` -> `this!!.bar`
|
||||||
|
is KtNameReferenceExpression -> psi to true
|
||||||
|
|
||||||
|
// `bar()` -> `this!!.bar()`
|
||||||
|
is KtCallExpression -> psi to true
|
||||||
|
|
||||||
|
// `-foo` -> `-foo!!`
|
||||||
|
// NOTE: Unsafe unary operator call is reported as UNSAFE_CALL, _not_ UNSAFE_OPERATOR_CALL
|
||||||
|
is KtUnaryExpression -> psi.baseExpression to false
|
||||||
|
|
||||||
|
is KtBinaryExpression -> {
|
||||||
|
val receiver = if (KtPsiUtil.isInOrNotInOperation(psi)) {
|
||||||
|
// `bar in foo` -> `bar in foo!!`
|
||||||
|
psi.right
|
||||||
|
} else {
|
||||||
|
// `foo + bar` -> `foo!! + bar` OR `foo infixFun bar` -> `foo!! infixFun bar`
|
||||||
|
psi.left
|
||||||
|
}
|
||||||
|
receiver to false
|
||||||
|
}
|
||||||
|
|
||||||
|
// UNSAFE_INFIX_CALL/UNSAFE_OPERATOR_CALL on KtBinaryExpression is reported on the child KtOperationReferenceExpression
|
||||||
|
is KtOperationReferenceExpression -> return getFixForUnsafeCall(psi.parent)
|
||||||
|
|
||||||
|
else -> return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
return listOfNotNull(target.asAddExclExclCallFix(hasImplicitReceiver = hasImplicitReceiver))
|
||||||
|
}
|
||||||
|
|
||||||
|
val iteratorOnNullableFactory = diagnosticFixFactory<KtFirDiagnostic.IteratorOnNullable> { diagnostic ->
|
||||||
|
val expression = diagnostic.psi as? KtExpression ?: return@diagnosticFixFactory emptyList()
|
||||||
|
val type = expression.getKtType()
|
||||||
|
if (!type.canBeNull) return@diagnosticFixFactory emptyList()
|
||||||
|
|
||||||
|
val typeScope = type.getTypeScope() ?: return@diagnosticFixFactory emptyList()
|
||||||
|
val hasValidIterator = typeScope.getCallableSymbols { it == OperatorNameConventions.ITERATOR }
|
||||||
|
.filter { it is KtFunctionSymbol && it.isOperator && it.valueParameters.isEmpty() }.singleOrNull() != null
|
||||||
|
if (hasValidIterator) {
|
||||||
|
listOfNotNull(expression.asAddExclExclCallFix())
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun PsiElement?.asAddExclExclCallFix(hasImplicitReceiver: Boolean = false) =
|
||||||
|
this?.let { AddExclExclCallFix(it, hasImplicitReceiver) } ?: null
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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.psi.PsiElement
|
||||||
|
import org.jetbrains.kotlin.idea.fir.api.fixes.diagnosticFixFactory
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.types.KtTypeNullability
|
||||||
|
|
||||||
|
object TypeMismatchFactories {
|
||||||
|
val argumentTypeMismatchFactory = diagnosticFixFactory<KtFirDiagnostic.ArgumentTypeMismatch> { diagnostic ->
|
||||||
|
getFixesForTypeMismatch(diagnostic.psi, diagnostic.expectedType, diagnostic.actualType)
|
||||||
|
}
|
||||||
|
|
||||||
|
val returnTypeMismatchFactory = diagnosticFixFactory<KtFirDiagnostic.ReturnTypeMismatch> { diagnostic ->
|
||||||
|
getFixesForTypeMismatch(diagnostic.psi, diagnostic.expectedType, diagnostic.actualType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtAnalysisSession.getFixesForTypeMismatch(
|
||||||
|
psi: PsiElement,
|
||||||
|
expectedType: KtType,
|
||||||
|
actualType: KtType
|
||||||
|
): List<IntentionAction> {
|
||||||
|
// TODO: Add more fixes than just AddExclExclCallFix when available.
|
||||||
|
if (!expectedType.canBeNull && actualType.canBeNull) {
|
||||||
|
val nullableExpectedType = expectedType.withNullability(KtTypeNullability.NULLABLE)
|
||||||
|
if (actualType isSubTypeOf nullableExpectedType) {
|
||||||
|
return listOfNotNull(psi.asAddExclExclCallFix())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+25
@@ -264,6 +264,11 @@ public class HighLevelQuickFixTestGenerated extends AbstractHighLevelQuickFixTes
|
|||||||
runTest("idea/testData/quickfix/addExclExclCall/implicitFunctionCall.kt");
|
runTest("idea/testData/quickfix/addExclExclCall/implicitFunctionCall.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("infixCall.kt")
|
||||||
|
public void testInfixCall() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/addExclExclCall/infixCall.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("iterable.kt")
|
@TestMetadata("iterable.kt")
|
||||||
public void testIterable() throws Exception {
|
public void testIterable() throws Exception {
|
||||||
runTest("idea/testData/quickfix/addExclExclCall/iterable.kt");
|
runTest("idea/testData/quickfix/addExclExclCall/iterable.kt");
|
||||||
@@ -299,6 +304,16 @@ public class HighLevelQuickFixTestGenerated extends AbstractHighLevelQuickFixTes
|
|||||||
runTest("idea/testData/quickfix/addExclExclCall/operationIn.kt");
|
runTest("idea/testData/quickfix/addExclExclCall/operationIn.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("operationPlus.kt")
|
||||||
|
public void testOperationPlus() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/addExclExclCall/operationPlus.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("operationUnary.kt")
|
||||||
|
public void testOperationUnary() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/addExclExclCall/operationUnary.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("idea/testData/quickfix/addExclExclCall/typeMismatch")
|
@TestMetadata("idea/testData/quickfix/addExclExclCall/typeMismatch")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
@@ -311,6 +326,11 @@ public class HighLevelQuickFixTestGenerated extends AbstractHighLevelQuickFixTes
|
|||||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/addExclExclCall/typeMismatch"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true);
|
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/addExclExclCall/typeMismatch"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("argumentAfterNullCheck.kt")
|
||||||
|
public void testArgumentAfterNullCheck() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/addExclExclCall/typeMismatch/argumentAfterNullCheck.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("assignmentRValue.kt")
|
@TestMetadata("assignmentRValue.kt")
|
||||||
public void testAssignmentRValue() throws Exception {
|
public void testAssignmentRValue() throws Exception {
|
||||||
runTest("idea/testData/quickfix/addExclExclCall/typeMismatch/assignmentRValue.kt");
|
runTest("idea/testData/quickfix/addExclExclCall/typeMismatch/assignmentRValue.kt");
|
||||||
@@ -326,6 +346,11 @@ public class HighLevelQuickFixTestGenerated extends AbstractHighLevelQuickFixTes
|
|||||||
runTest("idea/testData/quickfix/addExclExclCall/typeMismatch/memberAccessInExtensionAsAssignmentRValue.kt");
|
runTest("idea/testData/quickfix/addExclExclCall/typeMismatch/memberAccessInExtensionAsAssignmentRValue.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("nullArgument.kt")
|
||||||
|
public void testNullArgument() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/addExclExclCall/typeMismatch/nullArgument.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("qualifiedArgument.kt")
|
@TestMetadata("qualifiedArgument.kt")
|
||||||
public void testQualifiedArgument() throws Exception {
|
public void testQualifiedArgument() throws Exception {
|
||||||
runTest("idea/testData/quickfix/addExclExclCall/typeMismatch/qualifiedArgument.kt");
|
runTest("idea/testData/quickfix/addExclExclCall/typeMismatch/qualifiedArgument.kt");
|
||||||
|
|||||||
+6
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.idea.frontend.api.components
|
|||||||
import org.jetbrains.kotlin.idea.frontend.api.ValidityTokenOwner
|
import org.jetbrains.kotlin.idea.frontend.api.ValidityTokenOwner
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtNamedClassOrObjectSymbol
|
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtNamedClassOrObjectSymbol
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
|
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.types.KtTypeNullability
|
||||||
|
|
||||||
abstract class KtTypeProvider : KtAnalysisSessionComponent() {
|
abstract class KtTypeProvider : KtAnalysisSessionComponent() {
|
||||||
abstract val builtinTypes: KtBuiltinTypes
|
abstract val builtinTypes: KtBuiltinTypes
|
||||||
@@ -15,6 +16,8 @@ abstract class KtTypeProvider : KtAnalysisSessionComponent() {
|
|||||||
abstract fun approximateToSuperPublicDenotableType(type: KtType): KtType?
|
abstract fun approximateToSuperPublicDenotableType(type: KtType): KtType?
|
||||||
|
|
||||||
abstract fun buildSelfClassType(symbol: KtNamedClassOrObjectSymbol): KtType
|
abstract fun buildSelfClassType(symbol: KtNamedClassOrObjectSymbol): KtType
|
||||||
|
|
||||||
|
abstract fun withNullability(type: KtType, newNullability: KtTypeNullability): KtType
|
||||||
}
|
}
|
||||||
|
|
||||||
interface KtTypeProviderMixIn : KtAnalysisSessionMixIn {
|
interface KtTypeProviderMixIn : KtAnalysisSessionMixIn {
|
||||||
@@ -32,6 +35,9 @@ interface KtTypeProviderMixIn : KtAnalysisSessionMixIn {
|
|||||||
|
|
||||||
fun KtNamedClassOrObjectSymbol.buildSelfClassType(): KtType =
|
fun KtNamedClassOrObjectSymbol.buildSelfClassType(): KtType =
|
||||||
analysisSession.typeProvider.buildSelfClassType(this)
|
analysisSession.typeProvider.buildSelfClassType(this)
|
||||||
|
|
||||||
|
fun KtType.withNullability(newNullability: KtTypeNullability): KtType =
|
||||||
|
analysisSession.typeProvider.withNullability(this, newNullability)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("PropertyName")
|
@Suppress("PropertyName")
|
||||||
|
|||||||
+11
-4
@@ -6,25 +6,27 @@
|
|||||||
package org.jetbrains.kotlin.idea.frontend.api.fir.components
|
package org.jetbrains.kotlin.idea.frontend.api.fir.components
|
||||||
|
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
|
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
|
||||||
|
import org.jetbrains.kotlin.fir.typeContext
|
||||||
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
|
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
|
||||||
import org.jetbrains.kotlin.fir.types.impl.ConeTypeParameterTypeImpl
|
import org.jetbrains.kotlin.fir.types.impl.ConeTypeParameterTypeImpl
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken
|
import org.jetbrains.kotlin.fir.types.withNullability
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.components.KtBuiltinTypes
|
import org.jetbrains.kotlin.idea.frontend.api.components.KtBuiltinTypes
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.components.KtTypeProvider
|
import org.jetbrains.kotlin.idea.frontend.api.components.KtTypeProvider
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession
|
import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirNamedClassOrObjectSymbol
|
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirNamedClassOrObjectSymbol
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.fir.types.KtFirType
|
import org.jetbrains.kotlin.idea.frontend.api.fir.types.KtFirType
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.fir.types.PublicTypeApproximator
|
import org.jetbrains.kotlin.idea.frontend.api.fir.types.PublicTypeApproximator
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol
|
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.toConeNullability
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtNamedClassOrObjectSymbol
|
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtNamedClassOrObjectSymbol
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
|
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.types.KtTypeNullability
|
||||||
|
|
||||||
internal class KtFirTypeProvider(
|
internal class KtFirTypeProvider(
|
||||||
override val analysisSession: KtFirAnalysisSession,
|
override val analysisSession: KtFirAnalysisSession,
|
||||||
override val token: ValidityToken,
|
override val token: ValidityToken,
|
||||||
) : KtTypeProvider(), KtFirAnalysisSessionComponent {
|
) : KtTypeProvider(), KtFirAnalysisSessionComponent {
|
||||||
override val builtinTypes: KtBuiltinTypes =
|
override val builtinTypes: KtBuiltinTypes = KtFirBuiltInTypes(rootModuleSession.builtinTypes, firSymbolBuilder, token)
|
||||||
KtFirBuiltInTypes(analysisSession.firResolveState.rootModuleSession.builtinTypes, analysisSession.firSymbolBuilder, token)
|
|
||||||
|
|
||||||
override fun approximateToSuperPublicDenotableType(type: KtType): KtType? {
|
override fun approximateToSuperPublicDenotableType(type: KtType): KtType? {
|
||||||
require(type is KtFirType)
|
require(type is KtFirType)
|
||||||
@@ -49,5 +51,10 @@ internal class KtFirTypeProvider(
|
|||||||
}
|
}
|
||||||
return type.asKtType()
|
return type.asKtType()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun withNullability(type: KtType, newNullability: KtTypeNullability): KtType {
|
||||||
|
require(type is KtFirType)
|
||||||
|
return type.coneType.withNullability(newNullability.toConeNullability(), rootModuleSession.typeContext).asKtType()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
@@ -13,10 +13,12 @@ import org.jetbrains.kotlin.fir.expressions.FirExpression
|
|||||||
import org.jetbrains.kotlin.fir.expressions.FirNamedArgumentExpression
|
import org.jetbrains.kotlin.fir.expressions.FirNamedArgumentExpression
|
||||||
import org.jetbrains.kotlin.fir.resolve.toSymbol
|
import org.jetbrains.kotlin.fir.resolve.toSymbol
|
||||||
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
|
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
|
||||||
|
import org.jetbrains.kotlin.fir.types.ConeNullability
|
||||||
import org.jetbrains.kotlin.fir.types.coneType
|
import org.jetbrains.kotlin.fir.types.coneType
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtConstantValue
|
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtConstantValue
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSimpleConstantValue
|
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSimpleConstantValue
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtUnsupportedConstantValue
|
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtUnsupportedConstantValue
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.types.KtTypeNullability
|
||||||
|
|
||||||
internal fun mapAnnotationParameters(annotationCall: FirAnnotationCall, session: FirSession): Map<String, FirExpression> {
|
internal fun mapAnnotationParameters(annotationCall: FirAnnotationCall, session: FirSession): Map<String, FirExpression> {
|
||||||
|
|
||||||
@@ -55,3 +57,8 @@ internal fun FirExpression.convertConstantExpression(): KtConstantValue =
|
|||||||
is FirConstExpression<*> -> convertConstantExpression()
|
is FirConstExpression<*> -> convertConstantExpression()
|
||||||
else -> KtUnsupportedConstantValue
|
else -> KtUnsupportedConstantValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun KtTypeNullability.toConeNullability() = when (this) {
|
||||||
|
KtTypeNullability.NULLABLE -> ConeNullability.NULLABLE
|
||||||
|
KtTypeNullability.NON_NULLABLE -> ConeNullability.NOT_NULL
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,4 +5,6 @@ class Foo {
|
|||||||
|
|
||||||
fun test(foo: Foo?) {
|
fun test(foo: Foo?) {
|
||||||
val f = foo::f<caret>
|
val f = foo::f<caret>
|
||||||
}
|
}
|
||||||
|
// TODO: Enable when FIR reports UNSAFE_CALL for function reference on nullable (currently UNRESOLVED_REFERENCE)
|
||||||
|
/* IGNORE_FIR */
|
||||||
|
|||||||
@@ -5,4 +5,6 @@ class Foo {
|
|||||||
|
|
||||||
fun test(foo: Foo?) {
|
fun test(foo: Foo?) {
|
||||||
val f = foo!!::f
|
val f = foo!!::f
|
||||||
}
|
}
|
||||||
|
// TODO: Enable when FIR reports UNSAFE_CALL for function reference on nullable (currently UNRESOLVED_REFERENCE)
|
||||||
|
/* IGNORE_FIR */
|
||||||
|
|||||||
@@ -9,4 +9,6 @@ class Bar {
|
|||||||
|
|
||||||
fun test(foo: Foo?) {
|
fun test(foo: Foo?) {
|
||||||
val f = foo?.bar::f<caret>
|
val f = foo?.bar::f<caret>
|
||||||
}
|
}
|
||||||
|
// TODO: Enable when FIR reports UNSAFE_CALL for function reference on nullable (currently UNRESOLVED_REFERENCE)
|
||||||
|
/* IGNORE_FIR */
|
||||||
|
|||||||
@@ -9,4 +9,6 @@ class Bar {
|
|||||||
|
|
||||||
fun test(foo: Foo?) {
|
fun test(foo: Foo?) {
|
||||||
val f = foo?.bar!!::f
|
val f = foo?.bar!!::f
|
||||||
}
|
}
|
||||||
|
// TODO: Enable when FIR reports UNSAFE_CALL for function reference on nullable (currently UNRESOLVED_REFERENCE)
|
||||||
|
/* IGNORE_FIR */
|
||||||
|
|||||||
@@ -5,4 +5,6 @@ class Foo {
|
|||||||
|
|
||||||
fun Foo?.test() {
|
fun Foo?.test() {
|
||||||
val f = ::f<caret>
|
val f = ::f<caret>
|
||||||
}
|
}
|
||||||
|
// TODO: Enable when FIR reports UNSAFE_CALL for function reference on nullable (currently UNRESOLVED_REFERENCE)
|
||||||
|
/* IGNORE_FIR */
|
||||||
|
|||||||
@@ -5,4 +5,6 @@ class Foo {
|
|||||||
|
|
||||||
fun Foo?.test() {
|
fun Foo?.test() {
|
||||||
val f = this!!::f
|
val f = this!!::f
|
||||||
}
|
}
|
||||||
|
// TODO: Enable when FIR reports UNSAFE_CALL for function reference on nullable (currently UNRESOLVED_REFERENCE)
|
||||||
|
/* IGNORE_FIR */
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// "Add non-null asserted (!!) call" "true"
|
||||||
|
|
||||||
|
infix fun Int.bar(i: Int) = this
|
||||||
|
|
||||||
|
fun foo(i: Int?) {
|
||||||
|
i <caret>bar 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// "Add non-null asserted (!!) call" "true"
|
||||||
|
|
||||||
|
infix fun Int.bar(i: Int) = this
|
||||||
|
|
||||||
|
fun foo(i: Int?) {
|
||||||
|
i!! bar 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// "Add non-null asserted (!!) call" "true"
|
||||||
|
|
||||||
|
fun foo(i: Int?) {
|
||||||
|
i <caret>+ 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// "Add non-null asserted (!!) call" "true"
|
||||||
|
|
||||||
|
fun foo(i: Int?) {
|
||||||
|
i!! + 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// "Add non-null asserted (!!) call" "true"
|
||||||
|
|
||||||
|
fun foo(i: Int?) {
|
||||||
|
<caret>-i
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// "Add non-null asserted (!!) call" "true"
|
||||||
|
|
||||||
|
fun foo(i: Int?) {
|
||||||
|
-i!!
|
||||||
|
}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
// "Add non-null asserted (!!) call" "false"
|
||||||
|
// ACTION: Add 'i =' to argument
|
||||||
|
// ACTION: Change parameter 'i' type of function 'other' to 'Int?'
|
||||||
|
// ACTION: Create function 'other'
|
||||||
|
// ACTION: Remove braces from 'if' statement
|
||||||
|
// ACTION: Surround with null check
|
||||||
|
// ACTION: Wrap with '?.let { ... }' call
|
||||||
|
// ERROR: Type mismatch: inferred type is Nothing? but Int was expected
|
||||||
|
fun test(i: Int?) {
|
||||||
|
if (i == null) {
|
||||||
|
other(<caret>i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun other(i: Int) {}
|
||||||
|
|
||||||
|
// TODO: Need data flow info from null check
|
||||||
|
/* IGNORE_FIR */
|
||||||
@@ -3,3 +3,5 @@ fun test() {
|
|||||||
val s: String? = null
|
val s: String? = null
|
||||||
val z: String = <caret>s
|
val z: String = <caret>s
|
||||||
}
|
}
|
||||||
|
// TODO: Enable when FIR reports TYPE_MISMATCH for assignments
|
||||||
|
/* IGNORE_FIR */
|
||||||
|
|||||||
@@ -3,3 +3,5 @@ fun test() {
|
|||||||
val s: String? = null
|
val s: String? = null
|
||||||
val z: String = s!!
|
val z: String = s!!
|
||||||
}
|
}
|
||||||
|
// TODO: Enable when FIR reports TYPE_MISMATCH for assignments
|
||||||
|
/* IGNORE_FIR */
|
||||||
|
|||||||
Vendored
+2
@@ -7,3 +7,5 @@ class C {
|
|||||||
fun C.test() {
|
fun C.test() {
|
||||||
val z: String = <caret>s
|
val z: String = <caret>s
|
||||||
}
|
}
|
||||||
|
// TODO: Enable when FIR reports TYPE_MISMATCH for assignments
|
||||||
|
/* IGNORE_FIR */
|
||||||
|
|||||||
+2
@@ -7,3 +7,5 @@ class C {
|
|||||||
fun C.test() {
|
fun C.test() {
|
||||||
val z: String = s!!
|
val z: String = s!!
|
||||||
}
|
}
|
||||||
|
// TODO: Enable when FIR reports TYPE_MISMATCH for assignments
|
||||||
|
/* IGNORE_FIR */
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// "Add non-null asserted (!!) call" "false"
|
||||||
|
// ACTION: Add 'i =' to argument
|
||||||
|
// ACTION: Change parameter 'i' type of function 'other' to 'Int?'
|
||||||
|
// ACTION: Do not show hints for current method
|
||||||
|
// ERROR: Null can not be a value of a non-null type Int
|
||||||
|
fun test() {
|
||||||
|
other(<caret>null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun other(i: Int) {}
|
||||||
Vendored
+3
-1
@@ -1,6 +1,8 @@
|
|||||||
// "class org.jetbrains.kotlin.idea.quickfix.AddExclExclCallFix" "false"
|
// "Add non-null asserted (!!) call" "false"
|
||||||
// ACTION: Change parameter 's' type of function 'other' to 'String?'
|
// ACTION: Change parameter 's' type of function 'other' to 'String?'
|
||||||
// ACTION: Create function 'other'
|
// ACTION: Create function 'other'
|
||||||
|
// ACTION: Convert to also
|
||||||
|
// ACTION: Convert to apply
|
||||||
// ERROR: Type mismatch: inferred type is String? but Int was expected
|
// ERROR: Type mismatch: inferred type is String? but Int was expected
|
||||||
fun test() {
|
fun test() {
|
||||||
val s: String? = ""
|
val s: String? = ""
|
||||||
|
|||||||
+2
-1
@@ -14,4 +14,5 @@ open class MyClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO: Enable when FIR reports SMARTCAST_IMPOSSIBLE
|
||||||
|
/* IGNORE_FIR */
|
||||||
|
|||||||
+2
-1
@@ -14,4 +14,5 @@ open class MyClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO: Enable when FIR reports SMARTCAST_IMPOSSIBLE
|
||||||
|
/* IGNORE_FIR */
|
||||||
|
|||||||
@@ -7,4 +7,3 @@ class SafeType {
|
|||||||
fun safeB(p: SafeType?) {
|
fun safeB(p: SafeType?) {
|
||||||
val v = p <caret>+ 42
|
val v = p <caret>+ 42
|
||||||
}
|
}
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -7,4 +7,3 @@ class SafeType {
|
|||||||
fun safeB(p: SafeType?) {
|
fun safeB(p: SafeType?) {
|
||||||
val v = p!! + 42
|
val v = p!! + 42
|
||||||
}
|
}
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -7,4 +7,3 @@ class SafeType {
|
|||||||
fun safeB(p: SafeType?) {
|
fun safeB(p: SafeType?) {
|
||||||
val v = p <caret>op 42
|
val v = p <caret>op 42
|
||||||
}
|
}
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -7,4 +7,3 @@ class SafeType {
|
|||||||
fun safeB(p: SafeType?) {
|
fun safeB(p: SafeType?) {
|
||||||
val v = p!! op 42
|
val v = p!! op 42
|
||||||
}
|
}
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -3,4 +3,3 @@ fun <T: Collection<Int>?> foo(c: T) {
|
|||||||
for (i in <caret>c) { }
|
for (i in <caret>c) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* IGNORE_FIR */
|
|
||||||
-1
@@ -3,4 +3,3 @@ fun <T: Collection<Int>?> foo(c: T) {
|
|||||||
for (i in c!!) { }
|
for (i in c!!) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -4,4 +4,3 @@ fun foo() {
|
|||||||
for (i in <caret>test) { }
|
for (i in <caret>test) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -4,4 +4,3 @@ fun foo() {
|
|||||||
for (i in <caret>test!!) { }
|
for (i in <caret>test!!) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -7,4 +7,3 @@ class SafeType {
|
|||||||
fun safeB(p: SafeType?) {
|
fun safeB(p: SafeType?) {
|
||||||
val v = <caret>-p
|
val v = <caret>-p
|
||||||
}
|
}
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -7,4 +7,3 @@ class SafeType {
|
|||||||
fun safeB(p: SafeType?) {
|
fun safeB(p: SafeType?) {
|
||||||
val v = -p!!
|
val v = -p!!
|
||||||
}
|
}
|
||||||
/* IGNORE_FIR */
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// "class org.jetbrains.kotlin.idea.quickfix.AddExclExclCallFix" "false"
|
// "Add non-null asserted (!!) call" "false"
|
||||||
// ACTION: Replace with a 'forEach' function call
|
// ACTION: Replace with a 'forEach' function call
|
||||||
// ACTION: Surround with null check
|
// ACTION: Surround with null check
|
||||||
// ERROR: Not nullable value required to call an 'iterator()' method on for-loop range
|
// ERROR: Not nullable value required to call an 'iterator()' method on for-loop range
|
||||||
|
|||||||
@@ -7,5 +7,3 @@ fun foo() {
|
|||||||
val test: Some? = Some()
|
val test: Some? = Some()
|
||||||
for (i in <caret>test) { }
|
for (i in <caret>test) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* IGNORE_FIR */
|
|
||||||
-2
@@ -7,5 +7,3 @@ fun foo() {
|
|||||||
val test: Some? = Some()
|
val test: Some? = Some()
|
||||||
for (i in test!!) { }
|
for (i in test!!) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -5,4 +5,3 @@ fun callMe(p: String) {}
|
|||||||
fun callIt(p: Any) {
|
fun callIt(p: Any) {
|
||||||
callMe(<caret>p as String?)
|
callMe(<caret>p as String?)
|
||||||
}
|
}
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -5,4 +5,3 @@ fun callMe(p: String) {}
|
|||||||
fun callIt(p: Any) {
|
fun callIt(p: Any) {
|
||||||
callMe(<caret>(p as String?)!!)
|
callMe(<caret>(p as String?)!!)
|
||||||
}
|
}
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -2,5 +2,3 @@
|
|||||||
fun foo(a: Int?) {
|
fun foo(a: Int?) {
|
||||||
a<caret>.plus(1)
|
a<caret>.plus(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -2,5 +2,3 @@
|
|||||||
fun foo(a: Int?) {
|
fun foo(a: Int?) {
|
||||||
a!!.plus(1)
|
a!!.plus(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -2,5 +2,3 @@
|
|||||||
fun foo(a: Int?) {
|
fun foo(a: Int?) {
|
||||||
a.<caret>plus(1)
|
a.<caret>plus(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -2,5 +2,3 @@
|
|||||||
fun foo(a: Int?) {
|
fun foo(a: Int?) {
|
||||||
a!!.plus(1)
|
a!!.plus(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -2,4 +2,3 @@
|
|||||||
|
|
||||||
operator fun Int.get(row: Int, column: Int) = this
|
operator fun Int.get(row: Int, column: Int) = this
|
||||||
fun foo(arg: Int?) = arg<caret>[42, 13]
|
fun foo(arg: Int?) = arg<caret>[42, 13]
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -2,4 +2,3 @@
|
|||||||
|
|
||||||
operator fun Int.get(row: Int, column: Int) = this
|
operator fun Int.get(row: Int, column: Int) = this
|
||||||
fun foo(arg: Int?) = arg!![42, 13]
|
fun foo(arg: Int?) = arg!![42, 13]
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -2,4 +2,3 @@
|
|||||||
|
|
||||||
operator fun Int.get(row: Int, column: Int) = if (row == column) this else null
|
operator fun Int.get(row: Int, column: Int) = if (row == column) this else null
|
||||||
fun foo(arg: Int) = arg[42, 13]<caret>.hashCode()
|
fun foo(arg: Int) = arg[42, 13]<caret>.hashCode()
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -2,4 +2,3 @@
|
|||||||
|
|
||||||
operator fun Int.get(row: Int, column: Int) = if (row == column) this else null
|
operator fun Int.get(row: Int, column: Int) = if (row == column) this else null
|
||||||
fun foo(arg: Int) = arg[42, 13]!!.hashCode()
|
fun foo(arg: Int) = arg[42, 13]!!.hashCode()
|
||||||
/* IGNORE_FIR */
|
|
||||||
@@ -677,6 +677,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
runTest("idea/testData/quickfix/addExclExclCall/implicitFunctionCall.kt");
|
runTest("idea/testData/quickfix/addExclExclCall/implicitFunctionCall.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("infixCall.kt")
|
||||||
|
public void testInfixCall() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/addExclExclCall/infixCall.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("iterable.kt")
|
@TestMetadata("iterable.kt")
|
||||||
public void testIterable() throws Exception {
|
public void testIterable() throws Exception {
|
||||||
runTest("idea/testData/quickfix/addExclExclCall/iterable.kt");
|
runTest("idea/testData/quickfix/addExclExclCall/iterable.kt");
|
||||||
@@ -712,6 +717,16 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
runTest("idea/testData/quickfix/addExclExclCall/operationIn.kt");
|
runTest("idea/testData/quickfix/addExclExclCall/operationIn.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("operationPlus.kt")
|
||||||
|
public void testOperationPlus() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/addExclExclCall/operationPlus.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("operationUnary.kt")
|
||||||
|
public void testOperationUnary() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/addExclExclCall/operationUnary.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("idea/testData/quickfix/addExclExclCall/typeMismatch")
|
@TestMetadata("idea/testData/quickfix/addExclExclCall/typeMismatch")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
@@ -724,6 +739,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/addExclExclCall/typeMismatch"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true);
|
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/addExclExclCall/typeMismatch"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("argumentAfterNullCheck.kt")
|
||||||
|
public void testArgumentAfterNullCheck() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/addExclExclCall/typeMismatch/argumentAfterNullCheck.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("assignmentRValue.kt")
|
@TestMetadata("assignmentRValue.kt")
|
||||||
public void testAssignmentRValue() throws Exception {
|
public void testAssignmentRValue() throws Exception {
|
||||||
runTest("idea/testData/quickfix/addExclExclCall/typeMismatch/assignmentRValue.kt");
|
runTest("idea/testData/quickfix/addExclExclCall/typeMismatch/assignmentRValue.kt");
|
||||||
@@ -739,6 +759,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
runTest("idea/testData/quickfix/addExclExclCall/typeMismatch/memberAccessInExtensionAsAssignmentRValue.kt");
|
runTest("idea/testData/quickfix/addExclExclCall/typeMismatch/memberAccessInExtensionAsAssignmentRValue.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("nullArgument.kt")
|
||||||
|
public void testNullArgument() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/addExclExclCall/typeMismatch/nullArgument.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("qualifiedArgument.kt")
|
@TestMetadata("qualifiedArgument.kt")
|
||||||
public void testQualifiedArgument() throws Exception {
|
public void testQualifiedArgument() throws Exception {
|
||||||
runTest("idea/testData/quickfix/addExclExclCall/typeMismatch/qualifiedArgument.kt");
|
runTest("idea/testData/quickfix/addExclExclCall/typeMismatch/qualifiedArgument.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user