diff --git a/compiler/fir/analysis-tests/testData/resolve/smartcasts/kt10240.kt b/compiler/fir/analysis-tests/testData/resolve/smartcasts/kt10240.kt index 4a2b7373d0f..99967fba062 100644 --- a/compiler/fir/analysis-tests/testData/resolve/smartcasts/kt10240.kt +++ b/compiler/fir/analysis-tests/testData/resolve/smartcasts/kt10240.kt @@ -4,7 +4,7 @@ fun a(): Int { if (c == null || 0 < c // FE 1.0: smart cast impossible, see KT-10240 ) c = 0 - return c ?: 0 + return c ?: 0 } var c: Int = 0 diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/DiagnosticData.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/DiagnosticData.kt index 2d55225ab52..6cbd2b5ea21 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/DiagnosticData.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/DiagnosticData.kt @@ -52,6 +52,7 @@ enum class PositioningStrategy(private val strategy: String? = null) { CONST_MODIFIER, ARRAY_ACCESS, SAFE_ACCESS, + USELESS_ELVIS, NAME_OF_NAMED_ARGUMENT, VALUE_ARGUMENTS, SUPERTYPES_LIST, diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index 8512e7fdd73..00a7d143e7c 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -638,6 +638,10 @@ object DIAGNOSTICS_LIST : DiagnosticList() { } val NOT_NULL_ASSERTION_ON_LAMBDA_EXPRESSION by warning(PositioningStrategy.OPERATOR) val NOT_NULL_ASSERTION_ON_CALLABLE_REFERENCE by warning(PositioningStrategy.OPERATOR) + val USELESS_ELVIS by warning(PositioningStrategy.USELESS_ELVIS) { + parameter("receiverType") + } + val USELESS_ELVIS_RIGHT_IS_NULL by warning(PositioningStrategy.USELESS_ELVIS) } val WHEN_EXPRESSIONS by object : DiagnosticGroup("When expressions") { diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index e4586c1267f..0c6c81a9d87 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -378,6 +378,8 @@ object FirErrors { val UNNECESSARY_NOT_NULL_ASSERTION by warning1(SourceElementPositioningStrategies.OPERATOR) val NOT_NULL_ASSERTION_ON_LAMBDA_EXPRESSION by warning0(SourceElementPositioningStrategies.OPERATOR) val NOT_NULL_ASSERTION_ON_CALLABLE_REFERENCE by warning0(SourceElementPositioningStrategies.OPERATOR) + val USELESS_ELVIS by warning1(SourceElementPositioningStrategies.USELESS_ELVIS) + val USELESS_ELVIS_RIGHT_IS_NULL by warning0(SourceElementPositioningStrategies.USELESS_ELVIS) // When expressions val NO_ELSE_IN_WHEN by error1>(SourceElementPositioningStrategies.WHEN_EXPRESSION) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirElvisExpressionChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirElvisExpressionChecker.kt new file mode 100644 index 00000000000..f2ac1f86467 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirElvisExpressionChecker.kt @@ -0,0 +1,38 @@ +/* + * 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.fir.analysis.checkers.expression + +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors +import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn +import org.jetbrains.kotlin.fir.expressions.FirElvisExpression +import org.jetbrains.kotlin.fir.expressions.FirStatement +import org.jetbrains.kotlin.fir.types.ConeKotlinErrorType +import org.jetbrains.kotlin.fir.types.canBeNull +import org.jetbrains.kotlin.fir.types.coneType +import org.jetbrains.kotlin.fir.types.isNullLiteral + +object FirElvisExpressionChecker : FirBasicExpressionChecker() { + override fun check(expression: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) { + if (expression !is FirElvisExpression) return + // If the overall expression is not resolved/completed, the corresponding error will be reported separately. + // See [FirControlFlowStatementsResolveTransformer#transformElvisExpression], + // where an error type is recorded as the expression's return type. + if (expression.typeRef.coneType is ConeKotlinErrorType) return + + val lhsType = expression.lhs.typeRef.coneType + if (lhsType is ConeKotlinErrorType) return + if (!lhsType.canBeNull) { + reporter.reportOn(expression.source, FirErrors.USELESS_ELVIS, lhsType, context) + return + } + + if (expression.rhs.isNullLiteral) { + reporter.reportOn(expression.source, FirErrors.USELESS_ELVIS_RIGHT_IS_NULL, context) + } + } +} diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/collectors/components/ExpressionCheckersDiagnosticComponent.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/collectors/components/ExpressionCheckersDiagnosticComponent.kt index ec1ee38292e..ae2b848c335 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/collectors/components/ExpressionCheckersDiagnosticComponent.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/collectors/components/ExpressionCheckersDiagnosticComponent.kt @@ -77,6 +77,10 @@ class ExpressionCheckersDiagnosticComponent( checkers.allBasicExpressionCheckers.check(checkNotNullCall, data, reporter) } + override fun visitElvisExpression(elvisExpression: FirElvisExpression, data: CheckerContext) { + checkers.allBasicExpressionCheckers.check(elvisExpression, data, reporter) + } + override fun visitSafeCallExpression(safeCallExpression: FirSafeCallExpression, data: CheckerContext) { checkers.basicExpressionCheckers.check(safeCallExpression, data, reporter) } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt index 7dc6ecb6661..84b1d745b5b 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt @@ -263,6 +263,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNSUPPORTED_FEATU import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNUSED_VARIABLE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UPPER_BOUND_IS_EXTENSION_FUNCTION_TYPE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UPPER_BOUND_VIOLATED +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.USELESS_ELVIS +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.USELESS_ELVIS_RIGHT_IS_NULL import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.USELESS_VARARG_ON_PARAMETER import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VALUE_CLASS_CANNOT_BE_CLONEABLE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VALUE_PARAMETER_WITH_NO_TYPE_ANNOTATION @@ -836,6 +838,8 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension { map.put(NOT_NULL_ASSERTION_ON_CALLABLE_REFERENCE, "Non-null assertion (!!) is called on a callable reference expression") map.put(UNNECESSARY_SAFE_CALL, "Unnecessary safe call on a non-null receiver of type {0}", RENDER_TYPE) map.put(UNEXPECTED_SAFE_CALL, "Safe-call is not allowed here") + map.put(USELESS_ELVIS, "Elvis operator (?:) always returns the left operand of non-nullable type {0}", RENDER_TYPE) + map.put(USELESS_ELVIS_RIGHT_IS_NULL, "Right operand of elvis operator (?:) is useless if it is null") // When expressions map.put(NO_ELSE_IN_WHEN, "''when'' expression must be exhaustive, add necessary {0}", WHEN_MISSING_CASES) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/LightTreePositioningStrategies.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/LightTreePositioningStrategies.kt index ae6dd7517aa..29c061193a2 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/LightTreePositioningStrategies.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/LightTreePositioningStrategies.kt @@ -555,6 +555,17 @@ object LightTreePositioningStrategies { } } + val USELESS_ELVIS = object : LightTreePositioningStrategy() { + override fun mark( + node: LighterASTNode, + startOffset: Int, + endOffset: Int, + tree: FlyweightCapableTreeStructure + ): List { + return markRange(tree.operationReference(node) ?: node, tree.lastChild(node) ?: node, startOffset, endOffset, tree, node) + } + } + val RETURN_WITH_LABEL = object : LightTreePositioningStrategy() { override fun mark( node: LighterASTNode, diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/SourceElementPositioningStrategies.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/SourceElementPositioningStrategies.kt index d17059acd72..5d8271f9a34 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/SourceElementPositioningStrategies.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/SourceElementPositioningStrategies.kt @@ -178,6 +178,11 @@ object SourceElementPositioningStrategies { PositioningStrategies.SAFE_ACCESS ) + val USELESS_ELVIS = SourceElementPositioningStrategy( + LightTreePositioningStrategies.USELESS_ELVIS, + PositioningStrategies.USELESS_ELVIS + ) + val RETURN_WITH_LABEL = SourceElementPositioningStrategy( LightTreePositioningStrategies.RETURN_WITH_LABEL, PositioningStrategies.RETURN_WITH_LABEL diff --git a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonExpressionCheckers.kt b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonExpressionCheckers.kt index 99e6a5427f8..601fea04a1c 100644 --- a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonExpressionCheckers.kt +++ b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonExpressionCheckers.kt @@ -17,6 +17,7 @@ object CommonExpressionCheckers : ExpressionCheckers() { get() = setOf( FirAnonymousFunctionChecker, FirCheckNotNullCallChecker, + FirElvisExpressionChecker, FirGetClassCallChecker, FirSafeCallExpressionChecker, ) @@ -36,7 +37,7 @@ object CommonExpressionCheckers : ExpressionCheckers() { FirSealedClassConstructorCallChecker, FirUninitializedEnumChecker, FirFunInterfaceConstructorReferenceChecker, - ) + ) override val functionCallCheckers: Set get() = setOf( diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/types/FirTypeUtils.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/types/FirTypeUtils.kt index efcbda74821..083e7e66842 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/types/FirTypeUtils.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/types/FirTypeUtils.kt @@ -10,7 +10,6 @@ import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall import org.jetbrains.kotlin.fir.expressions.FirConstExpression import org.jetbrains.kotlin.fir.expressions.FirExpression import org.jetbrains.kotlin.fir.render -import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol import org.jetbrains.kotlin.name.StandardClassIds import org.jetbrains.kotlin.fir.types.impl.FirImplicitBuiltinTypeRef import org.jetbrains.kotlin.name.ClassId @@ -43,7 +42,12 @@ val FirTypeRef.isArrayType: Boolean get() = isBuiltinType(StandardClassIds.Array, false) || StandardClassIds.primitiveArrayTypeByElementType.values.any { isBuiltinType(it, false) } -val FirExpression.isNullLiteral: Boolean get() = this is FirConstExpression<*> && this.value == null && this.source != null + +val FirExpression.isNullLiteral: Boolean + get() = this is FirConstExpression<*> && + this.kind == ConstantValueKind.Null && + this.value == null && + this.source != null private val FirTypeRef.classLikeTypeOrNull: ConeClassLikeType? get() = when (this) { diff --git a/compiler/testData/diagnostics/tests/callableReference/generic/specialCalls.fir.kt b/compiler/testData/diagnostics/tests/callableReference/generic/specialCalls.fir.kt deleted file mode 100644 index cd4fc8dce3f..00000000000 --- a/compiler/testData/diagnostics/tests/callableReference/generic/specialCalls.fir.kt +++ /dev/null @@ -1,23 +0,0 @@ -// !DIAGNOSTICS: -UNUSED_PARAMETER, -UNUSED_VARIABLE - -fun baz(i: Int) = i -fun bar(x: T): T = TODO() - -fun nullableFun(): ((Int) -> Int)? = null - -fun test() { - val x1: (Int) -> Int = bar(if (true) ::baz else ::baz) - val x2: (Int) -> Int = bar(nullableFun() ?: ::baz) - val x3: (Int) -> Int = bar(::baz ?: ::baz) - - val i = 0 - val x4: (Int) -> Int = bar(when (i) { - 10 -> ::baz - 20 -> ::baz - else -> ::baz - }) - - val x5: (Int) -> Int = bar(::baz!!) - - (if (true) ::baz else ::baz)(1) -} diff --git a/compiler/testData/diagnostics/tests/callableReference/generic/specialCalls.kt b/compiler/testData/diagnostics/tests/callableReference/generic/specialCalls.kt index 99d2b985943..d524ca746d1 100644 --- a/compiler/testData/diagnostics/tests/callableReference/generic/specialCalls.kt +++ b/compiler/testData/diagnostics/tests/callableReference/generic/specialCalls.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !DIAGNOSTICS: -UNUSED_PARAMETER, -UNUSED_VARIABLE fun baz(i: Int) = i diff --git a/compiler/testData/diagnostics/tests/callableReference/withQuestionMarks.fir.kt b/compiler/testData/diagnostics/tests/callableReference/withQuestionMarks.fir.kt index 6c4a50f5d3e..597dac3e342 100644 --- a/compiler/testData/diagnostics/tests/callableReference/withQuestionMarks.fir.kt +++ b/compiler/testData/diagnostics/tests/callableReference/withQuestionMarks.fir.kt @@ -24,8 +24,8 @@ fun main() { val x17 = logger::info!!?::print?::print // It must be OK - val x18 = String?::hashCode ?: ::foo - val x19 = String::hashCode ?: ::foo + val x18 = String?::hashCode ?: ::foo + val x19 = String::hashCode ?: ::foo val x20 = String?::hashCode::hashCode val x21 = kotlin.String?::hashCode::hashCode } diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/deadCode/deadCodeInBinaryExpressions.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/deadCode/deadCodeInBinaryExpressions.fir.kt index 16c3669ddcc..4fc51660f92 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/deadCode/deadCodeInBinaryExpressions.fir.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/deadCode/deadCodeInBinaryExpressions.fir.kt @@ -8,7 +8,7 @@ fun testBinary2() { } fun testElvis1() { - todo() ?: "" + todo() ?: "" } fun testElvis2(s: String?) { @@ -36,4 +36,4 @@ fun returnInBinary2(): Boolean { } fun todo(): Nothing = throw Exception() -fun bar() {} \ No newline at end of file +fun bar() {} diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/deadCode/deadCodeInWhileFromBreak.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/deadCode/deadCodeInWhileFromBreak.fir.kt index 8f2c49bd960..ae4eb52ba73 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/deadCode/deadCodeInWhileFromBreak.fir.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/deadCode/deadCodeInWhileFromBreak.fir.kt @@ -50,6 +50,6 @@ fun test(arr: Array) { } while (true) { - break ?: null + break ?: null } } diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/elvisNotProcessed.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/elvisNotProcessed.fir.kt index 9ff535e2ed0..d1e29e10ced 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/elvisNotProcessed.fir.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/elvisNotProcessed.fir.kt @@ -2,20 +2,20 @@ // See KT-8277 // NI_EXPECTED_FILE -val v = { true } ?: ( { true } ?:null!! ) +val v = { true } ?: ( { true } ?:null!! ) val w = if (true) { { true } } else { - { true } ?: null!! + { true } ?: null!! } val ww = if (true) { - { true } ?: null!! + { true } ?: null!! } else if (true) { - { true } ?: null!! + { true } ?: null!! } else { null!! @@ -29,11 +29,11 @@ val b = null ?: ( l() ?: false) val bb = null ?: ( l() ?: null!!) -val bbb = null ?: ( l() ?: null) +val bbb = null ?: ( l() ?: null) -val bbbb = ( l() ?: null) ?: ( l() ?: null) +val bbbb = ( l() ?: null) ?: ( l() ?: null) fun f(x : Long?): Long { - var a = x ?: (fun() {} ?: fun() {}) + var a = x ?: (fun() {} ?: fun() {}) return a } diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.fir.kt index f9bcee73563..e1283f32acc 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.fir.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.fir.kt @@ -7,6 +7,6 @@ fun foo(): String { } fun bar(): String { val x = fn() ?: return "" - val y = x?.let { throw Exception() } ?: "unreachable" + val y = x?.let { throw Exception() } ?: "unreachable" return y } diff --git a/compiler/testData/diagnostics/tests/controlStructures/lambdasInExclExclAndElvis.fir.kt b/compiler/testData/diagnostics/tests/controlStructures/lambdasInExclExclAndElvis.fir.kt index 991d512bd56..070380b8332 100644 --- a/compiler/testData/diagnostics/tests/controlStructures/lambdasInExclExclAndElvis.fir.kt +++ b/compiler/testData/diagnostics/tests/controlStructures/lambdasInExclExclAndElvis.fir.kt @@ -8,9 +8,9 @@ fun test() { use({ }!!); // KT-KT-9070 - { } ?: 1 - use({ 2 } ?: 1); + { } ?: 1 + use({ 2 } ?: 1); - 1 ?: { } - use(1 ?: { }) + 1 ?: { } + use(1 ?: { }) } diff --git a/compiler/testData/diagnostics/tests/dataFlowInfoTraversal/Elvis.fir.kt b/compiler/testData/diagnostics/tests/dataFlowInfoTraversal/Elvis.fir.kt index 18b3858584e..1b32bffde52 100644 --- a/compiler/testData/diagnostics/tests/dataFlowInfoTraversal/Elvis.fir.kt +++ b/compiler/testData/diagnostics/tests/dataFlowInfoTraversal/Elvis.fir.kt @@ -4,6 +4,6 @@ fun foo() { val x: Int? = null bar(x ?: 0) - if (x != null) bar(x ?: x) + if (x != null) bar(x ?: x) bar(x) -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/tests/nullableTypes/definitelyNotNullWithNullableBound.fir.kt b/compiler/testData/diagnostics/tests/nullableTypes/definitelyNotNullWithNullableBound.fir.kt index f7f45ccabda..9aaeae8962c 100644 --- a/compiler/testData/diagnostics/tests/nullableTypes/definitelyNotNullWithNullableBound.fir.kt +++ b/compiler/testData/diagnostics/tests/nullableTypes/definitelyNotNullWithNullableBound.fir.kt @@ -4,7 +4,7 @@ fun makeDefinitelyNotNull(arg: D?): D = TODO() fun test(arg: N) { - makeDefinitelyNotNull(arg) ?: 1 + makeDefinitelyNotNull(arg) ?: 1 makeDefinitelyNotNull(arg)!! diff --git a/compiler/testData/diagnostics/tests/nullableTypes/uselessElvis.fir.kt b/compiler/testData/diagnostics/tests/nullableTypes/uselessElvis.fir.kt index 72e3bcca902..b6b5d49b625 100644 --- a/compiler/testData/diagnostics/tests/nullableTypes/uselessElvis.fir.kt +++ b/compiler/testData/diagnostics/tests/nullableTypes/uselessElvis.fir.kt @@ -5,12 +5,12 @@ fun test1(t: Any?): Any { } fun test2(t: Any?): Any { - return t as T ?: "" + return t as T ?: "" } fun test3(t: Any?): Any { if (t != null) { - return t ?: "" + return t ?: "" } return 1 @@ -28,11 +28,11 @@ fun test() { val x: String? = null takeNotNull(dependOn(x) ?: "") takeNotNull(dependOn(dependOn(x)) ?: "") - takeNotNull(dependOn(dependOn(x as String)) ?: "") + takeNotNull(dependOn(dependOn(x as String)) ?: "") if (x != null) { - takeNotNull(dependOn(x) ?: "") - takeNotNull(dependOn(dependOn(x)) ?: "") + takeNotNull(dependOn(x) ?: "") + takeNotNull(dependOn(dependOn(x)) ?: "") takeNotNull(dependOn(dependOn(x) as? String) ?: "") } @@ -45,4 +45,4 @@ fun testFrom13648() { takeNotNull(reifiedNull() ?: "") } -fun bar() = unresolved \ No newline at end of file +fun bar() = unresolved diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/elvis.fir.kt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/elvis.fir.kt index e9ad65b2bfa..e95323e55b0 100644 --- a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/elvis.fir.kt +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/elvis.fir.kt @@ -36,28 +36,28 @@ fun test() { // platform type with no annotation val platformJ = J.staticJ - val v0 = platformNN ?: J() - platformNN ?: J() + val v0 = platformNN ?: J() + platformNN ?: J() platformN ?: J() platformJ ?: J() if (platformNN != null) { - platformNN ?: J() + platformNN ?: J() } if (platformN != null) { - platformN ?: J() + platformN ?: J() } if (platformJ != null) { - platformJ ?: J() + platformJ ?: J() } - takeNotNull(J.staticNN ?: J()) + takeNotNull(J.staticNN ?: J()) takeNotNull(J.staticN ?: J()) takeNotNull(J.staticJ ?: J()) takeNotNull(J.getAny() ?: J()) - takeNotNull(J.getNNAny() ?: J()) + takeNotNull(J.getNNAny() ?: J()) takeNotNull(J.getNAny() ?: J()) val x = unresolved ?: null diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisInCall.fir.kt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisInCall.fir.kt deleted file mode 100644 index d7d368057b7..00000000000 --- a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisInCall.fir.kt +++ /dev/null @@ -1,28 +0,0 @@ -// !DIAGNOSTICS: -UNUSED_PARAMETER - -// FILE: J.java - -import org.jetbrains.annotations.*; - -public class J { - @NotNull - public static J staticNN; -} - -// FILE: k.kt - -fun test() { - // @NotNull platform type - val platformNN = J.staticNN - - foo(platformNN ?: "") - - val bar = Bar() - bar(platformNN ?: "") -} - -fun foo(a: Any) {} - -class Bar { - operator fun invoke(a: Any) {} -} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisInCall.kt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisInCall.kt index abdfb54d12e..17e98822b4d 100644 --- a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisInCall.kt +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisInCall.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !DIAGNOSTICS: -UNUSED_PARAMETER // FILE: J.java diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisRightIsNull.fir.kt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisRightIsNull.fir.kt index d57eeb205b9..7cb868cb003 100644 --- a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisRightIsNull.fir.kt +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisRightIsNull.fir.kt @@ -33,15 +33,15 @@ public interface JJJJ { // FILE: k.kt fun test() { - val a = J.staticN ?: null + val a = J.staticN ?: null foo(a) - val b = JJ.staticNN ?: null + val b = JJ.staticNN ?: null foo(b) - val c = JJJ.staticNNN ?: null + val c = JJJ.staticNNN ?: null foo(c) } fun foo(a: Any?) { } -fun test2(j: JJJJ) = j.get() ?: null +fun test2(j: JJJJ) = j.get() ?: null diff --git a/compiler/testData/diagnostics/tests/resolve/invoke/errors/unsafeCallWithInvoke.fir.kt b/compiler/testData/diagnostics/tests/resolve/invoke/errors/unsafeCallWithInvoke.fir.kt index cde534b69f7..5ee43224bcf 100644 --- a/compiler/testData/diagnostics/tests/resolve/invoke/errors/unsafeCallWithInvoke.fir.kt +++ b/compiler/testData/diagnostics/tests/resolve/invoke/errors/unsafeCallWithInvoke.fir.kt @@ -6,5 +6,5 @@ operator fun String.invoke(i: Int) {} fun foo(s: String?) { s(1) - (s ?: null)(1) + (s ?: null)(1) } diff --git a/compiler/testData/diagnostics/tests/smartCasts/elvisExprNotNull.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/elvisExprNotNull.fir.kt index be351d4825c..5f6122d1c34 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/elvisExprNotNull.fir.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/elvisExprNotNull.fir.kt @@ -31,7 +31,7 @@ fun baz(s: String?, r: String?): String { } fun withNull(s: String?): String { - val t = s ?: null + val t = s ?: null // Error: nullable return t -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/tests/varargs/NullableTypeForVarargArgument.fir.kt b/compiler/testData/diagnostics/tests/varargs/NullableTypeForVarargArgument.fir.kt index ad9295cb666..1e596f43ef7 100644 --- a/compiler/testData/diagnostics/tests/varargs/NullableTypeForVarargArgument.fir.kt +++ b/compiler/testData/diagnostics/tests/varargs/NullableTypeForVarargArgument.fir.kt @@ -62,5 +62,5 @@ fun k() { fun invokeTest(goodArgs: Array) { J.staticFun(*goodArgs) J.staticFun(*args) - J.staticFun(*args ?: null) + J.staticFun(*args ?: null) } diff --git a/compiler/tests-spec/testData/diagnostics/linked/expressions/elvis-operator-expression/p-3/pos/1.1.fir.kt b/compiler/tests-spec/testData/diagnostics/linked/expressions/elvis-operator-expression/p-3/pos/1.1.fir.kt index 2df0cc8b8bb..b5c9a011d93 100644 --- a/compiler/tests-spec/testData/diagnostics/linked/expressions/elvis-operator-expression/p-3/pos/1.1.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/linked/expressions/elvis-operator-expression/p-3/pos/1.1.fir.kt @@ -27,6 +27,6 @@ fun case3() { // TESTCASE NUMBER: 4 fun case4() { - val x = null ?: null + val x = null ?: null x } diff --git a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-2/pos/1.1.fir.kt b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-2/pos/1.1.fir.kt index b8af5206da8..0c369265a26 100644 --- a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-2/pos/1.1.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-2/pos/1.1.fir.kt @@ -122,7 +122,7 @@ fun case_10(value_1: Int, value_2: String?, value_3: String?) { when { value_1 == 1 -> value_2 ?: true value_1 == 2 -> value_2 ?: value_3 ?: true - value_1 == 3 -> value_2!! ?: true + value_1 == 3 -> value_2!! ?: true } } diff --git a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-5/pos/1.1.fir.kt b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-5/pos/1.1.fir.kt index 778bbb75296..fdbcdc52f78 100644 --- a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-5/pos/1.1.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-5/pos/1.1.fir.kt @@ -125,7 +125,7 @@ fun case_10(value_1: Int, value_2: String?, value_3: String?) { when (value_1) { 1 -> value_2 ?: true 2 -> value_2 ?: value_3 ?: true - 3 -> value_2!! ?: true + 3 -> value_2!! ?: true } } diff --git a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/pos/5.1.fir.kt b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/pos/5.1.fir.kt index 77ff9719d19..b64e68d5dfc 100644 --- a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/pos/5.1.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/pos/5.1.fir.kt @@ -87,7 +87,7 @@ fun case_8(value_1: Int, value_2: Int?, value_3: Int?) { when (value_1) { value_2 ?: 0 -> {} value_2 ?: value_3 ?: 0 -> {} - value_2!! ?: 0 -> {} + value_2!! ?: 0 -> {} } } diff --git a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/pos/5.2.fir.kt b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/pos/5.2.fir.kt index 41f47302837..897e4cc24dd 100644 --- a/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/pos/5.2.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/linked/expressions/when-expression/p-6/pos/5.2.fir.kt @@ -66,7 +66,7 @@ fun case_7(value_1: Any, value_2: String, value_3: String) { // TESTCASE NUMBER: 8 fun case_8(value_1: Int, value_2: Int?, value_3: Int?) { when (value_1) { - value_2 ?: 0, value_2 ?: value_3 ?: 0, value_2!! ?: 0 -> {} + value_2 ?: 0, value_2 ?: value_3 ?: 0, value_2!! ?: 0 -> {} } } diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/14.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/14.fir.kt index 79e1fcb4671..e6133da0f6e 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/14.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/14.fir.kt @@ -4,7 +4,7 @@ // TESTCASE NUMBER: 1 fun case_1(x: Int?) { - if ((x is Int) ?: (x is Int)) { + if ((x is Int) ?: (x is Int)) { x x.inv() } diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/7.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/7.fir.kt index 79e1fcb4671..e6133da0f6e 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/7.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/7.fir.kt @@ -4,7 +4,7 @@ // TESTCASE NUMBER: 1 fun case_1(x: Int?) { - if ((x is Int) ?: (x is Int)) { + if ((x is Int) ?: (x is Int)) { x x.inv() } diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/32.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/32.fir.kt index 7088ae32666..742dd2d1a58 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/32.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/32.fir.kt @@ -6,7 +6,7 @@ fun case_1(x: T?, y: K?) { x as T y as K - val z = x ?: y + val z = x ?: y x.equals(10) z diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/35.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/35.fir.kt index 07ebf4bbfa7..67cbbdf5f8e 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/35.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/35.fir.kt @@ -34,7 +34,7 @@ fun case_2(x: Any?) { */ fun case_3(x: Any?) { while (true) { - x ?: return ?: x + x ?: return ?: x } x diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/36.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/36.fir.kt index b3077d4ff86..a26ef8fcdc9 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/36.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/36.fir.kt @@ -137,7 +137,7 @@ fun case_10(x: Any?, z: Any, b: Boolean?) { // TESTCASE NUMBER: 11 fun case_11(x: Any?, z: Any, b: Boolean?) { while (true) { - var y = x ?: if (b == true) continue!! else if (!(b != false)) return else break ?: break::class + var y = x ?: if (b == true) continue!! else if (!(b != false)) return else break ?: break::class z !== y && if (b == true) return else if (b === false) null!!else throw Exception() x y @@ -148,7 +148,7 @@ fun case_11(x: Any?, z: Any, b: Boolean?) { // TESTCASE NUMBER: 12 fun case_12(x: Any?, z: Any, b: Boolean?) { while (true) { - var y = select(x) ?: if (b == true) continue!! else if (!(b != false)) return else break ?: break::class + var y = select(x) ?: if (b == true) continue!! else if (!(b != false)) return else break ?: break::class select(z) !== y && if (b == true) return else if (b === false) null!!else throw Exception() x y diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/37.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/37.fir.kt index ec11d464e77..aea2c696795 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/37.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/37.fir.kt @@ -34,7 +34,7 @@ fun case_2(a: Any?) { */ fun case_3(x: Int?) { while (true) { - x ?: return ?: x + x ?: return ?: x } x diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/71.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/71.fir.kt index 158f8704143..6597b26741d 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/71.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/71.fir.kt @@ -37,7 +37,7 @@ fun case_2(): Int { var c: Int? = null if (c == null || 0 < c) c = 0 c - return c ?: 0 + return c ?: 0 } var c: Int = 0 diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt index d4831d095b2..e511c3370f5 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -1798,6 +1798,19 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.USELESS_ELVIS) { firDiagnostic -> + UselessElvisImpl( + firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a), + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } + add(FirErrors.USELESS_ELVIS_RIGHT_IS_NULL) { firDiagnostic -> + UselessElvisRightIsNullImpl( + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } add(FirErrors.NO_ELSE_IN_WHEN) { firDiagnostic -> NoElseInWhenImpl( firDiagnostic.a.map { whenMissingCase -> diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt index 0c9688b0c99..59e0f8f7e8d 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt @@ -1265,6 +1265,15 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = NotNullAssertionOnCallableReference::class } + abstract class UselessElvis : KtFirDiagnostic() { + override val diagnosticClass get() = UselessElvis::class + abstract val receiverType: KtType + } + + abstract class UselessElvisRightIsNull : KtFirDiagnostic() { + override val diagnosticClass get() = UselessElvisRightIsNull::class + } + abstract class NoElseInWhen : KtFirDiagnostic() { override val diagnosticClass get() = NoElseInWhen::class abstract val missingWhenCases: List diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index 55666d4f8e8..5b64f6869f7 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -2049,6 +2049,21 @@ internal class NotNullAssertionOnCallableReferenceImpl( override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) } +internal class UselessElvisImpl( + override val receiverType: KtType, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.UselessElvis(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + +internal class UselessElvisRightIsNullImpl( + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.UselessElvisRightIsNull(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + internal class NoElseInWhenImpl( override val missingWhenCases: List, firDiagnostic: FirPsiDiagnostic<*>,