diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.java b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.java index 16321aee816..85efe936e96 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.java @@ -16,7 +16,6 @@ package org.jetbrains.kotlin.cfg; -import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -53,7 +52,6 @@ import org.jetbrains.kotlin.diagnostics.Errors; import org.jetbrains.kotlin.idea.MainFunctionDetector; import org.jetbrains.kotlin.lexer.KtTokens; import org.jetbrains.kotlin.psi.*; -import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt; import org.jetbrains.kotlin.resolve.*; import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt; import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt; @@ -65,14 +63,12 @@ import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils; import java.util.*; -import static org.jetbrains.kotlin.cfg.VariableUseState.*; import static org.jetbrains.kotlin.cfg.TailRecursionKind.*; +import static org.jetbrains.kotlin.cfg.VariableUseState.*; import static org.jetbrains.kotlin.diagnostics.Errors.*; import static org.jetbrains.kotlin.diagnostics.Errors.UNREACHABLE_CODE; import static org.jetbrains.kotlin.resolve.BindingContext.*; -import static org.jetbrains.kotlin.types.TypeUtils.DONT_CARE; -import static org.jetbrains.kotlin.types.TypeUtils.NO_EXPECTED_TYPE; -import static org.jetbrains.kotlin.types.TypeUtils.noExpectedType; +import static org.jetbrains.kotlin.types.TypeUtils.*; public class ControlFlowInformationProvider { @@ -689,15 +685,32 @@ public class ControlFlowInformationProvider { ? ((InstructionWithValue) instruction).getOutputValue() : null; Pseudocode pseudocode = instruction.getOwner(); - boolean isUsedAsExpression = !pseudocode.getUsages(value).isEmpty(); + List usages = pseudocode.getUsages(value); + boolean isUsedAsExpression = !usages.isEmpty(); + boolean isUsedAsResultOfLambda = isUsedAsResultOfLambda(usages); for (KtElement element : pseudocode.getValueElements(value)) { trace.record(BindingContext.USED_AS_EXPRESSION, element, isUsedAsExpression); + trace.record(BindingContext.USED_AS_RESULT_OF_LAMBDA, element, isUsedAsResultOfLambda); } } } ); } + private static boolean isUsedAsResultOfLambda(List usages) { + for (Instruction usage : usages) { + if (usage instanceof ReturnValueInstruction) { + KtElement returnElement = ((ReturnValueInstruction) usage).getElement(); + PsiElement parentElement = returnElement.getParent(); + if (!(returnElement instanceof KtReturnExpression || + parentElement instanceof KtDeclaration && !(parentElement instanceof KtFunctionLiteral))) { + return true; + } + } + } + return false; + } + public void checkIfExpressions() { PseudocodeTraverserKt.traverse( pseudocode, TraversalOrder.FORWARD, new ControlFlowInformationProvider.FunctionVoid1() { @@ -718,7 +731,7 @@ public class ControlFlowInformationProvider { trace.report(INVALID_IF_AS_EXPRESSION.on(ifExpression)); } else { - checkImplicitCastOnConditionalExpression(ifExpression, ImmutableList.of(thenExpression, elseExpression)); + checkImplicitCastOnConditionalExpression(ifExpression); } } } @@ -727,10 +740,41 @@ public class ControlFlowInformationProvider { ); } - private void checkImplicitCastOnConditionalExpression( - @NotNull KtExpression expression, - @NotNull Collection branchExpressions + private static List collectResultingExpressionsOfConditionalExpression(KtExpression expression) { + List leafBranches = new ArrayList(); + collectResultingExpressionsOfConditionalExpressionRec(expression, leafBranches); + return leafBranches; + } + + private static void collectResultingExpressionsOfConditionalExpressionRec( + @Nullable KtExpression expression, + @NotNull List resultingExpressions ) { + if (expression instanceof KtIfExpression) { + KtIfExpression ifExpression = (KtIfExpression) expression; + collectResultingExpressionsOfConditionalExpressionRec(ifExpression.getThen(), resultingExpressions); + collectResultingExpressionsOfConditionalExpressionRec(ifExpression.getElse(), resultingExpressions); + } + else if (expression instanceof KtWhenExpression) { + KtWhenExpression whenExpression = (KtWhenExpression) expression; + for (KtWhenEntry whenEntry : whenExpression.getEntries()) { + collectResultingExpressionsOfConditionalExpressionRec(whenEntry.getExpression(), resultingExpressions); + } + } + else if (expression != null){ + KtExpression resultingExpression = getResultingExpression(expression); + if (resultingExpression instanceof KtIfExpression || resultingExpression instanceof KtWhenExpression) { + collectResultingExpressionsOfConditionalExpressionRec(resultingExpression, resultingExpressions); + } + else { + resultingExpressions.add(resultingExpression); + } + } + } + + private void checkImplicitCastOnConditionalExpression(@NotNull KtExpression expression) { + Collection branchExpressions = collectResultingExpressionsOfConditionalExpression(expression); + KotlinType expectedExpressionType = trace.get(EXPECTED_EXPRESSION_TYPE, expression); if (expectedExpressionType != null && expectedExpressionType != DONT_CARE) return; @@ -739,10 +783,13 @@ public class ControlFlowInformationProvider { return; } if (KotlinBuiltIns.isAnyOrNullableAny(expressionType)) { + boolean isUsedAsResultOfLambda = BindingContextUtilsKt.isUsedAsResultOfLambda(expression, trace.getBindingContext()); for (KtExpression branchExpression : branchExpressions) { if (branchExpression == null) continue; KotlinType branchType = trace.getType(branchExpression); - if (branchType == null || KotlinBuiltIns.isAnyOrNullableAny(branchType)) { + if (branchType == null + || KotlinBuiltIns.isAnyOrNullableAny(branchType) + || (isUsedAsResultOfLambda && KotlinBuiltIns.isUnitOrNullableUnit(branchType))) { return; } } @@ -798,11 +845,7 @@ public class ControlFlowInformationProvider { KtWhenExpression whenExpression = (KtWhenExpression) element; if (BindingContextUtilsKt.isUsedAsExpression(whenExpression, trace.getBindingContext())) { - List branchExpressions = new ArrayList(whenExpression.getEntries().size()); - for (KtWhenEntry whenEntry : whenExpression.getEntries()) { - branchExpressions.add(whenEntry.getExpression()); - } - checkImplicitCastOnConditionalExpression(whenExpression, branchExpressions); + checkImplicitCastOnConditionalExpression(whenExpression); } if (whenExpression.getElseExpression() != null) continue; diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java index 0a1b2324b17..2bfc9118a6c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java @@ -149,6 +149,7 @@ public interface BindingContext { */ WritableSlice PROCESSED = Slices.createSimpleSetSlice(); WritableSlice USED_AS_EXPRESSION = Slices.createSimpleSetSlice(); + WritableSlice USED_AS_RESULT_OF_LAMBDA = Slices.createSimpleSetSlice(); WritableSlice UNREACHABLE_CODE = Slices.createSimpleSetSlice(); WritableSlice CAPTURED_IN_CLOSURE = new BasicWritableSlice(DO_NOTHING); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContextUtils.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContextUtils.kt index a738a199807..b653056a6df 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContextUtils.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContextUtils.kt @@ -55,6 +55,7 @@ fun KtReturnExpression.getTargetFunction(context: BindingContext): KtCallableDec } fun KtExpression.isUsedAsExpression(context: BindingContext): Boolean = context[BindingContext.USED_AS_EXPRESSION, this]!! +fun KtExpression.isUsedAsResultOfLambda(context: BindingContext): Boolean = context[BindingContext.USED_AS_RESULT_OF_LAMBDA, this]!! fun KtExpression.isUsedAsStatement(context: BindingContext): Boolean = !isUsedAsExpression(context) diff --git a/compiler/testData/diagnostics/tests/controlStructures/ifToAnyDiscriminatingUsages.kt b/compiler/testData/diagnostics/tests/controlStructures/ifToAnyDiscriminatingUsages.kt new file mode 100644 index 00000000000..ec1deb4e982 --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlStructures/ifToAnyDiscriminatingUsages.kt @@ -0,0 +1,61 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_VARIABLE + +fun println() {} +fun foo(x: Any) {} +fun fooGeneric(x: T) {} + +fun testResultOfLambda1() = + run { + if (true) 42 else println() + } + +fun testResultOfLambda2() = + run { + if (true) 42 else if (true) 42 else println() + } + +fun testResultOfAnonFun1() = + run(fun () = + if (true) 42 + else println() + ) + +fun testResultOfAnonFun2() = + run(fun () { + if (true) 42 else println() + }) + +fun testReturnFromAnonFun() = + run(fun () { + return if (true) 42 else println() + }) + +fun testReturn1() = + run { + return if (true) 42 + else println() + } + +fun testReturn2() = + run { + return if (true) 42 + else if (true) 42 + else println() + } + +fun testUsage1() = + if (true) 42 + else println() + +fun testUsage2() = + foo(if (true) 42 else println()) + +fun testUsage2Generic() = + fooGeneric(if (true) 42 else println()) + +val testUsage3 = + if (true) 42 + else println() + +val testUsage4: Any get() = + if (true) 42 else println() \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/controlStructures/ifToAnyDiscriminatingUsages.txt b/compiler/testData/diagnostics/tests/controlStructures/ifToAnyDiscriminatingUsages.txt new file mode 100644 index 00000000000..35a6d4cb323 --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlStructures/ifToAnyDiscriminatingUsages.txt @@ -0,0 +1,17 @@ +package + +public val testUsage3: kotlin.Any +public val testUsage4: kotlin.Any +public fun foo(/*0*/ x: kotlin.Any): kotlin.Unit +public fun fooGeneric(/*0*/ x: T): kotlin.Unit +public fun println(): kotlin.Unit +public fun testResultOfAnonFun1(): kotlin.Any +public fun testResultOfAnonFun2(): kotlin.Unit +public fun testResultOfLambda1(): kotlin.Any +public fun testResultOfLambda2(): kotlin.Any +public fun testReturn1(): kotlin.Nothing +public fun testReturn2(): kotlin.Nothing +public fun testReturnFromAnonFun(): kotlin.Unit +public fun testUsage1(): kotlin.Any +public fun testUsage2(): kotlin.Unit +public fun testUsage2Generic(): kotlin.Unit diff --git a/compiler/testData/diagnostics/tests/controlStructures/ifWhenToAnyComplexExpressions.kt b/compiler/testData/diagnostics/tests/controlStructures/ifWhenToAnyComplexExpressions.kt new file mode 100644 index 00000000000..fd2c21c06bf --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlStructures/ifWhenToAnyComplexExpressions.kt @@ -0,0 +1,31 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_VARIABLE + +fun println() {} +fun foo(x: Any) {} +fun fooGeneric(x: T) {} + +fun testMixedIfAndWhen() = + if (true) + when { + true -> if (true) 42 + else 1 + true -> if (true) 42 + else println() + else -> if (true) println() + } + else println() + +fun testWrappedExpressions() = + if (true) { + println() + if (true) { + println() + if (true) { + println() + } + else {} + } + } + else { + (((((42)) + 1))) + } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/controlStructures/ifWhenToAnyComplexExpressions.txt b/compiler/testData/diagnostics/tests/controlStructures/ifWhenToAnyComplexExpressions.txt new file mode 100644 index 00000000000..e8cac3765c6 --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlStructures/ifWhenToAnyComplexExpressions.txt @@ -0,0 +1,7 @@ +package + +public fun foo(/*0*/ x: kotlin.Any): kotlin.Unit +public fun fooGeneric(/*0*/ x: T): kotlin.Unit +public fun println(): kotlin.Unit +public fun testMixedIfAndWhen(): kotlin.Any +public fun testWrappedExpressions(): kotlin.Any diff --git a/compiler/testData/diagnostics/tests/controlStructures/improperElseInExpression.kt b/compiler/testData/diagnostics/tests/controlStructures/improperElseInExpression.kt index 6fd3a30c4d8..f7b6180580d 100644 --- a/compiler/testData/diagnostics/tests/controlStructures/improperElseInExpression.kt +++ b/compiler/testData/diagnostics/tests/controlStructures/improperElseInExpression.kt @@ -17,12 +17,12 @@ fun example() { }(); { - if (true) {} else false + if (true) {} else false }(); { - if (true) true else {} + if (true) true else {} }() fun t(): Boolean { diff --git a/compiler/testData/diagnostics/tests/controlStructures/kt10322.kt b/compiler/testData/diagnostics/tests/controlStructures/kt10322.kt index 8d255c0e911..68bd3148541 100644 --- a/compiler/testData/diagnostics/tests/controlStructures/kt10322.kt +++ b/compiler/testData/diagnostics/tests/controlStructures/kt10322.kt @@ -1,10 +1,10 @@ fun test1() { run { if (true) { - if (true) {} + if (true) {} } else { - 1 + 1 } } } diff --git a/compiler/testData/diagnostics/tests/controlStructures/kt770.kt351.kt735_StatementType.kt b/compiler/testData/diagnostics/tests/controlStructures/kt770.kt351.kt735_StatementType.kt index 4cb36becfa1..447b413ade9 100644 --- a/compiler/testData/diagnostics/tests/controlStructures/kt770.kt351.kt735_StatementType.kt +++ b/compiler/testData/diagnostics/tests/controlStructures/kt770.kt351.kt735_StatementType.kt @@ -20,10 +20,10 @@ fun foo() { var z = 2 val r = { // type fun(): Any is inferred if (true) { - 2 + 2 } else { - z = 34 + z = 34 } } val f: ()-> Int = r @@ -74,9 +74,9 @@ fun testCoercionToUnit() { var x = 43 val checkType = { if (true) { - x = 4 + x = 4 } else { - 45 + 45 } } val f : () -> String = checkType diff --git a/compiler/testData/diagnostics/tests/controlStructures/whenToAnyDiscriminatingUsages.kt b/compiler/testData/diagnostics/tests/controlStructures/whenToAnyDiscriminatingUsages.kt new file mode 100644 index 00000000000..fc2e88283be --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlStructures/whenToAnyDiscriminatingUsages.kt @@ -0,0 +1,83 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_VARIABLE + +fun println() {} +fun foo(x: Any) {} +fun fooGeneric(x: T) {} + +fun testResultOfLambda1() = + run { + when { + true -> 42 + else -> println() + } + } + +fun testResultOfLambda2() = + run { + when { + true -> 42 + else -> + when { + true -> 42 + else -> println() + } + } + } + +fun testReturn1() = + run { + return when { + true -> 42 + else -> println() + } + } + +fun testReturn2() = + run { + return when { + true -> 42 + else -> + when { + true -> 42 + else -> println() + } + } + } + +fun testUsage1() = + when { + true -> 42 + else -> println() + } + +fun testUsage2() = + foo(when { + true -> 42 + else -> println() + }) + +fun testUsage2Generic() = + fooGeneric(when { + true -> 42 + else -> println() + }) + +val testUsage3 = + when { + true -> 42 + else -> println() + } + +val testUsage4 = + when { + true -> 42 + true -> 42 + true -> 42 + else -> println() + } + +val testUsage5: Any get() = + when { + true -> 42 + else -> println() + } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/controlStructures/whenToAnyDiscriminatingUsages.txt b/compiler/testData/diagnostics/tests/controlStructures/whenToAnyDiscriminatingUsages.txt new file mode 100644 index 00000000000..aab3381ceea --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlStructures/whenToAnyDiscriminatingUsages.txt @@ -0,0 +1,15 @@ +package + +public val testUsage3: kotlin.Any +public val testUsage4: kotlin.Any +public val testUsage5: kotlin.Any +public fun foo(/*0*/ x: kotlin.Any): kotlin.Unit +public fun fooGeneric(/*0*/ x: T): kotlin.Unit +public fun println(): kotlin.Unit +public fun testResultOfLambda1(): kotlin.Any +public fun testResultOfLambda2(): kotlin.Any +public fun testReturn1(): kotlin.Nothing +public fun testReturn2(): kotlin.Nothing +public fun testUsage1(): kotlin.Any +public fun testUsage2(): kotlin.Unit +public fun testUsage2Generic(): kotlin.Unit diff --git a/compiler/testData/diagnostics/testsWithStdLib/implicitCastToAny.kt b/compiler/testData/diagnostics/testsWithStdLib/implicitCastToAny.kt index b5614aff8ba..f684795dbf8 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/implicitCastToAny.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/implicitCastToAny.kt @@ -14,9 +14,19 @@ fun test1(word: String) = fun test2(word: String) = run { if (word.length > 4) { - if (word.startsWith("a")) longWords++ + if (word.startsWith("a")) longWords++ } else { smallWords.add(word) } + } + +fun test3(word: String) = + run { + if (word.length > 4) { + longWords++ + } + else { + System.out?.println(word) // Unit? + } } \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithStdLib/implicitCastToAny.txt b/compiler/testData/diagnostics/testsWithStdLib/implicitCastToAny.txt index 7073d05935c..27a51e14b91 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/implicitCastToAny.txt +++ b/compiler/testData/diagnostics/testsWithStdLib/implicitCastToAny.txt @@ -4,3 +4,4 @@ public var longWords: kotlin.Int public val smallWords: java.util.HashSet public fun test1(/*0*/ word: kotlin.String): kotlin.Any public fun test2(/*0*/ word: kotlin.String): kotlin.Any +public fun test3(/*0*/ word: kotlin.String): kotlin.Any? diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index b22f7a19bd0..c1e610882f9 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -3390,6 +3390,18 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("ifToAnyDiscriminatingUsages.kt") + public void testIfToAnyDiscriminatingUsages() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlStructures/ifToAnyDiscriminatingUsages.kt"); + doTest(fileName); + } + + @TestMetadata("ifWhenToAnyComplexExpressions.kt") + public void testIfWhenToAnyComplexExpressions() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlStructures/ifWhenToAnyComplexExpressions.kt"); + doTest(fileName); + } + @TestMetadata("ifWhenWithoutElse.kt") public void testIfWhenWithoutElse() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlStructures/ifWhenWithoutElse.kt"); @@ -3504,6 +3516,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("whenToAnyDiscriminatingUsages.kt") + public void testWhenToAnyDiscriminatingUsages() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlStructures/whenToAnyDiscriminatingUsages.kt"); + doTest(fileName); + } + @TestMetadata("when.kt234.kt973.kt") public void testWhen_kt234_kt973() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlStructures/when.kt234.kt973.kt");