[FE 1.0] Prohibit using non exhaustive if and when in rhs of elvis expression

^KT-44705
^KT-49349 Fixed
This commit is contained in:
Dmitriy Novozhilov
2021-10-21 12:49:36 +03:00
committed by teamcityserver
parent 06a26a5a74
commit a2b8493f47
16 changed files with 192 additions and 9 deletions
@@ -6289,6 +6289,18 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
runTest("compiler/testData/diagnostics/tests/controlStructures/nestedLoopsWithMultipleLabels.kt");
}
@Test
@TestMetadata("nonExhaustiveIfInElvis_after.kt")
public void testNonExhaustiveIfInElvis_after() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlStructures/nonExhaustiveIfInElvis_after.kt");
}
@Test
@TestMetadata("nonExhaustiveIfInElvis_before.kt")
public void testNonExhaustiveIfInElvis_before() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlStructures/nonExhaustiveIfInElvis_before.kt");
}
@Test
@TestMetadata("notAFunctionLabel_after.kt")
public void testNotAFunctionLabel_after() throws Exception {
@@ -6289,6 +6289,18 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
runTest("compiler/testData/diagnostics/tests/controlStructures/nestedLoopsWithMultipleLabels.kt");
}
@Test
@TestMetadata("nonExhaustiveIfInElvis_after.kt")
public void testNonExhaustiveIfInElvis_after() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlStructures/nonExhaustiveIfInElvis_after.kt");
}
@Test
@TestMetadata("nonExhaustiveIfInElvis_before.kt")
public void testNonExhaustiveIfInElvis_before() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlStructures/nonExhaustiveIfInElvis_before.kt");
}
@Test
@TestMetadata("notAFunctionLabel_after.kt")
public void testNotAFunctionLabel_after() throws Exception {
@@ -6289,6 +6289,18 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
runTest("compiler/testData/diagnostics/tests/controlStructures/nestedLoopsWithMultipleLabels.kt");
}
@Test
@TestMetadata("nonExhaustiveIfInElvis_after.kt")
public void testNonExhaustiveIfInElvis_after() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlStructures/nonExhaustiveIfInElvis_after.kt");
}
@Test
@TestMetadata("nonExhaustiveIfInElvis_before.kt")
public void testNonExhaustiveIfInElvis_before() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlStructures/nonExhaustiveIfInElvis_before.kt");
}
@Test
@TestMetadata("notAFunctionLabel_after.kt")
public void testNotAFunctionLabel_after() throws Exception {
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.cfg
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil.getParentOfType
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.cfg.TailRecursionKind.*
@@ -43,14 +44,10 @@ import org.jetbrains.kotlin.resolve.bindingContextUtil.getEnclosingDescriptor
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsResultOfLambda
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsStatement
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.checkers.findDestructuredVariable
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
import org.jetbrains.kotlin.resolve.calls.util.getDispatchReceiverWithSmartCast
import org.jetbrains.kotlin.resolve.calls.util.hasThisOrNoDispatchReceiver
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject
import org.jetbrains.kotlin.resolve.calls.util.isSingleUnderscore
import org.jetbrains.kotlin.resolve.calls.util.*
import org.jetbrains.kotlin.resolve.checkers.PlatformDiagnosticSuppressor
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal
import org.jetbrains.kotlin.resolve.descriptorUtil.module
@@ -943,6 +940,13 @@ class ControlFlowInformationProviderImpl private constructor(
} else {
checkImplicitCastOnConditionalExpression(element)
}
} else if (!languageVersionSettings.supportsFeature(LanguageFeature.ProhibitNonExhaustiveIfInRhsOfElvis)) {
val parent = element.deparenthesizedParent
if (parent is KtBinaryExpression) {
if (parent.operationToken === KtTokens.ELVIS) {
trace.report(INVALID_IF_AS_EXPRESSION_WARNING.on(element.getIfKeyword()))
}
}
}
}
}
@@ -1036,6 +1040,18 @@ class ControlFlowInformationProviderImpl private constructor(
}
}
}
if (
!usedAsExpression &&
missingCases.isNotEmpty() &&
!languageVersionSettings.supportsFeature(LanguageFeature.ProhibitNonExhaustiveIfInRhsOfElvis)
) {
val parent = element.deparenthesizedParent
if (parent is KtBinaryExpression) {
if (parent.operationToken === KtTokens.ELVIS) {
trace.report(NO_ELSE_IN_WHEN_WARNING.on(element, missingCases))
}
}
}
}
}
}
@@ -1355,4 +1371,13 @@ class ControlFlowInformationProviderImpl private constructor(
|| diagnosticFactory === UNUSED_ANONYMOUS_PARAMETER
|| diagnosticFactory === UNUSED_CHANGED_VALUE
}
private val PsiElement.deparenthesizedParent: PsiElement
get() {
var result = parent
while (result is KtParenthesizedExpression || result is KtLabeledExpression || result is KtAnnotatedExpression) {
result = result.parent
}
return result
}
}
@@ -346,7 +346,10 @@ class ControlFlowProcessor(
builder.jumpOnTrue(afterElvis, expression, builder.getBoundValue(left))
generateInstructions(right)
builder.bindLabel(afterElvis)
mergeValues(listOf(left, right).filterNotNull(), expression)
mergeValues(listOfNotNull(left, right), expression)
if (right != null && languageVersionSettings?.supportsFeature(LanguageFeature.ProhibitNonExhaustiveIfInRhsOfElvis) == true) {
trace.record(USED_AS_EXPRESSION, right, true)
}
} else {
if (!generateCall(expression)) {
generateBothArgumentsAndMark(expression)
@@ -997,6 +997,7 @@ public interface Errors {
DiagnosticFactory0<KtElement> SENSELESS_NULL_IN_WHEN = DiagnosticFactory0.create(WARNING);
DiagnosticFactory0<PsiElement> INVALID_IF_AS_EXPRESSION = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> INVALID_IF_AS_EXPRESSION_WARNING = DiagnosticFactory0.create(WARNING);
DiagnosticFactoryForDeprecation0<PsiElement> CONFUSING_BRANCH_CONDITION = DiagnosticFactoryForDeprecation0.create(LanguageFeature.ProhibitConfusingSyntaxInWhenBranches);
// Nullability
@@ -1079,6 +1080,7 @@ public interface Errors {
DiagnosticFactory0<KtWhenEntry> ELSE_MISPLACED_IN_WHEN = DiagnosticFactory0.create(ERROR, ELSE_ENTRY);
DiagnosticFactory0<KtWhenEntry> REDUNDANT_ELSE_IN_WHEN = DiagnosticFactory0.create(WARNING, ELSE_ENTRY);
DiagnosticFactory1<KtWhenExpression, List<WhenMissingCase>> NO_ELSE_IN_WHEN = DiagnosticFactory1.create(ERROR, WHEN_EXPRESSION);
DiagnosticFactory1<KtWhenExpression, List<WhenMissingCase>> NO_ELSE_IN_WHEN_WARNING = DiagnosticFactory1.create(WARNING, WHEN_EXPRESSION);
DiagnosticFactory1<KtWhenExpression, List<WhenMissingCase>> NON_EXHAUSTIVE_WHEN = DiagnosticFactory1.create(WARNING, WHEN_EXPRESSION);
DiagnosticFactory2<KtWhenExpression, String, List<WhenMissingCase>> NON_EXHAUSTIVE_WHEN_STATEMENT = DiagnosticFactory2.create(WARNING, WHEN_EXPRESSION);
DiagnosticFactory1<KtWhenExpression, List<WhenMissingCase>>
@@ -632,6 +632,7 @@ public class DefaultErrorMessages {
MAP.put(ILLEGAL_DECLARATION_IN_WHEN_SUBJECT, "Illegal variable declaration in 'when' subject: {0}. Should be a simple val with an initializer", STRING);
MAP.put(NO_ELSE_IN_WHEN, "''when'' expression must be exhaustive, add necessary {0}", RENDER_WHEN_MISSING_CASES);
MAP.put(NO_ELSE_IN_WHEN_WARNING, "''when'' expression must be exhaustive, add necessary {0}", RENDER_WHEN_MISSING_CASES);
MAP.put(NON_EXHAUSTIVE_WHEN, "''when'' expression on enum is recommended to be exhaustive, add {0}", RENDER_WHEN_MISSING_CASES);
MAP.put(NON_EXHAUSTIVE_WHEN_STATEMENT, "Non exhaustive ''when'' statements on {0} will be prohibited in 1.7, add {1}", STRING, RENDER_WHEN_MISSING_CASES);
MAP.put(NON_EXHAUSTIVE_WHEN_ON_SEALED_CLASS, "''when'' expression on sealed classes is recommended to be exhaustive, add {0}", RENDER_WHEN_MISSING_CASES);
@@ -833,6 +834,7 @@ public class DefaultErrorMessages {
MAP.put(SENSELESS_NULL_IN_WHEN, "Expression under 'when' is never equal to null");
MAP.put(INVALID_IF_AS_EXPRESSION, "'if' must have both main and 'else' branches if used as an expression");
MAP.put(INVALID_IF_AS_EXPRESSION_WARNING, "'if' must have both main and 'else' branches if used as an expression");
MAP.put(CONFUSING_BRANCH_CONDITION, "The logical expressions may be understood ambiguously in when with subject branches. Please wrap it with parenthesis");
MAP.put(OVERRIDING_FINAL_MEMBER, "''{0}'' in ''{1}'' is final and cannot be overridden", NAME, NAME);
@@ -56,8 +56,8 @@ fun g6(): Unit = <!TYPE_MISMATCH!><!NO_ELSE_IN_WHEN!>when<!> { true -> 42 }<!>
fun foo1(x: String?) {
"" + <!INVALID_IF_AS_EXPRESSION!>if<!> (true) 42
w@while (true) {
x ?: if (true) break
x ?: when { true -> break@w }
x ?: <!INVALID_IF_AS_EXPRESSION_WARNING!>if<!> (true) break
x ?: <!NO_ELSE_IN_WHEN_WARNING!>when<!> { true -> break@w }
}
}
@@ -0,0 +1,30 @@
// FIR_IDENTICAL
// LANGUAGE: +ProhibitNonExhaustiveIfInRhsOfElvis
// DIAGNOSTICS: -USELESS_ELVIS
// ISSUE: KT-44705
fun test_1(x: String?, y: String?) {
while (true) {
x ?: <!INVALID_IF_AS_EXPRESSION!>if<!> (y == null) break
}
}
fun test_2(x: String?, y: String?) {
while (true) {
val z = x ?: <!INVALID_IF_AS_EXPRESSION!>if<!> (y == null) break
}
}
fun test_3(x: String?, y: String?) {
while (true) {
x ?: <!NO_ELSE_IN_WHEN!>when<!> { true -> break }
x ?: <!NO_ELSE_IN_WHEN!>when<!> (y) { "hello" -> break }
}
}
fun test_4(x: String?, y: String?) {
while (true) {
val a = x ?: <!NO_ELSE_IN_WHEN!>when<!> { true -> break }
val b = x ?: <!NO_ELSE_IN_WHEN!>when<!> (y) { "hello" -> break }
}
}
@@ -0,0 +1,7 @@
package
public fun test_1(/*0*/ x: kotlin.String?, /*1*/ y: kotlin.String?): kotlin.Unit
public fun test_2(/*0*/ x: kotlin.String?, /*1*/ y: kotlin.String?): kotlin.Unit
public fun test_3(/*0*/ x: kotlin.String?, /*1*/ y: kotlin.String?): kotlin.Unit
public fun test_4(/*0*/ x: kotlin.String?, /*1*/ y: kotlin.String?): kotlin.Unit
@@ -0,0 +1,29 @@
// LANGUAGE: -ProhibitNonExhaustiveIfInRhsOfElvis
// DIAGNOSTICS: -USELESS_ELVIS
// ISSUE: KT-44705
fun test_1(x: String?, y: String?) {
while (true) {
x ?: <!INVALID_IF_AS_EXPRESSION!>if<!> (y == null) break
}
}
fun test_2(x: String?, y: String?) {
while (true) {
val z = x ?: <!INVALID_IF_AS_EXPRESSION!>if<!> (y == null) break
}
}
fun test_3(x: String?, y: String?) {
while (true) {
x ?: <!NO_ELSE_IN_WHEN!>when<!> { true -> break }
x ?: <!NO_ELSE_IN_WHEN!>when<!> (y) { "hello" -> break }
}
}
fun test_4(x: String?, y: String?) {
while (true) {
val a = x ?: <!NO_ELSE_IN_WHEN!>when<!> { true -> break }
val b = x ?: <!NO_ELSE_IN_WHEN!>when<!> (y) { "hello" -> break }
}
}
@@ -0,0 +1,29 @@
// LANGUAGE: -ProhibitNonExhaustiveIfInRhsOfElvis
// DIAGNOSTICS: -USELESS_ELVIS
// ISSUE: KT-44705
fun test_1(x: String?, y: String?) {
while (true) {
x ?: <!INVALID_IF_AS_EXPRESSION_WARNING!>if<!> (y == null) break
}
}
fun test_2(x: String?, y: String?) {
while (true) {
val z = x ?: <!INVALID_IF_AS_EXPRESSION!>if<!> (y == null) break
}
}
fun test_3(x: String?, y: String?) {
while (true) {
x ?: <!NO_ELSE_IN_WHEN_WARNING!>when<!> { true -> break }
x ?: <!NO_ELSE_IN_WHEN_WARNING!>when<!> (y) { "hello" -> break }
}
}
fun test_4(x: String?, y: String?) {
while (true) {
val a = x ?: <!NO_ELSE_IN_WHEN!>when<!> { true -> break }
val b = x ?: <!NO_ELSE_IN_WHEN!>when<!> (y) { "hello" -> break }
}
}
@@ -0,0 +1,7 @@
package
public fun test_1(/*0*/ x: kotlin.String?, /*1*/ y: kotlin.String?): kotlin.Unit
public fun test_2(/*0*/ x: kotlin.String?, /*1*/ y: kotlin.String?): kotlin.Unit
public fun test_3(/*0*/ x: kotlin.String?, /*1*/ y: kotlin.String?): kotlin.Unit
public fun test_4(/*0*/ x: kotlin.String?, /*1*/ y: kotlin.String?): kotlin.Unit
@@ -1,6 +1,6 @@
public fun foo(x: String?, y: String?): Int {
while (true) {
x ?: if (y == null) break
x ?: <!INVALID_IF_AS_EXPRESSION_WARNING!>if<!> (y == null) break
// y is nullable if x != null
y<!UNSAFE_CALL!>.<!>length
}
@@ -6295,6 +6295,18 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/controlStructures/nestedLoopsWithMultipleLabels.kt");
}
@Test
@TestMetadata("nonExhaustiveIfInElvis_after.kt")
public void testNonExhaustiveIfInElvis_after() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlStructures/nonExhaustiveIfInElvis_after.kt");
}
@Test
@TestMetadata("nonExhaustiveIfInElvis_before.kt")
public void testNonExhaustiveIfInElvis_before() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlStructures/nonExhaustiveIfInElvis_before.kt");
}
@Test
@TestMetadata("notAFunctionLabel_after.kt")
public void testNotAFunctionLabel_after() throws Exception {
@@ -235,6 +235,7 @@ enum class LanguageFeature(
JvmPermittedSubclassesAttributeForSealed(KOTLIN_1_7),
ForbidUsingExtensionPropertyTypeParameterInDelegate(KOTLIN_1_7, kind = BUG_FIX),
ProhibitConfusingSyntaxInWhenBranches(KOTLIN_1_7, kind = BUG_FIX), // KT-48385
ProhibitNonExhaustiveIfInRhsOfElvis(KOTLIN_1_7, kind = BUG_FIX), // KT-44705
// Temporarily disabled, see KT-27084/KT-22379
SoundSmartcastFromLoopConditionForLoopAssignedVariables(sinceVersion = null, kind = BUG_FIX),