Do not report IMPLICIT_CAST_TO_ANY on last statement of lambda with Unit(?) in at least one branch.

This commit is contained in:
Dmitry Petrov
2016-02-04 15:57:39 +03:00
parent fa8706b46a
commit e4583fd275
15 changed files with 314 additions and 26 deletions
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.cfg; package org.jetbrains.kotlin.cfg;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Sets; 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.idea.MainFunctionDetector;
import org.jetbrains.kotlin.lexer.KtTokens; import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
import org.jetbrains.kotlin.resolve.*; import org.jetbrains.kotlin.resolve.*;
import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt; import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt;
import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt; import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
@@ -65,14 +63,12 @@ import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils;
import java.util.*; import java.util.*;
import static org.jetbrains.kotlin.cfg.VariableUseState.*;
import static org.jetbrains.kotlin.cfg.TailRecursionKind.*; 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.*;
import static org.jetbrains.kotlin.diagnostics.Errors.UNREACHABLE_CODE; import static org.jetbrains.kotlin.diagnostics.Errors.UNREACHABLE_CODE;
import static org.jetbrains.kotlin.resolve.BindingContext.*; import static org.jetbrains.kotlin.resolve.BindingContext.*;
import static org.jetbrains.kotlin.types.TypeUtils.DONT_CARE; import static org.jetbrains.kotlin.types.TypeUtils.*;
import static org.jetbrains.kotlin.types.TypeUtils.NO_EXPECTED_TYPE;
import static org.jetbrains.kotlin.types.TypeUtils.noExpectedType;
public class ControlFlowInformationProvider { public class ControlFlowInformationProvider {
@@ -689,15 +685,32 @@ public class ControlFlowInformationProvider {
? ((InstructionWithValue) instruction).getOutputValue() ? ((InstructionWithValue) instruction).getOutputValue()
: null; : null;
Pseudocode pseudocode = instruction.getOwner(); Pseudocode pseudocode = instruction.getOwner();
boolean isUsedAsExpression = !pseudocode.getUsages(value).isEmpty(); List<Instruction> usages = pseudocode.getUsages(value);
boolean isUsedAsExpression = !usages.isEmpty();
boolean isUsedAsResultOfLambda = isUsedAsResultOfLambda(usages);
for (KtElement element : pseudocode.getValueElements(value)) { for (KtElement element : pseudocode.getValueElements(value)) {
trace.record(BindingContext.USED_AS_EXPRESSION, element, isUsedAsExpression); trace.record(BindingContext.USED_AS_EXPRESSION, element, isUsedAsExpression);
trace.record(BindingContext.USED_AS_RESULT_OF_LAMBDA, element, isUsedAsResultOfLambda);
} }
} }
} }
); );
} }
private static boolean isUsedAsResultOfLambda(List<Instruction> 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() { public void checkIfExpressions() {
PseudocodeTraverserKt.traverse( PseudocodeTraverserKt.traverse(
pseudocode, TraversalOrder.FORWARD, new ControlFlowInformationProvider.FunctionVoid1<Instruction>() { pseudocode, TraversalOrder.FORWARD, new ControlFlowInformationProvider.FunctionVoid1<Instruction>() {
@@ -718,7 +731,7 @@ public class ControlFlowInformationProvider {
trace.report(INVALID_IF_AS_EXPRESSION.on(ifExpression)); trace.report(INVALID_IF_AS_EXPRESSION.on(ifExpression));
} }
else { else {
checkImplicitCastOnConditionalExpression(ifExpression, ImmutableList.of(thenExpression, elseExpression)); checkImplicitCastOnConditionalExpression(ifExpression);
} }
} }
} }
@@ -727,10 +740,41 @@ public class ControlFlowInformationProvider {
); );
} }
private void checkImplicitCastOnConditionalExpression( private static List<KtExpression> collectResultingExpressionsOfConditionalExpression(KtExpression expression) {
@NotNull KtExpression expression, List<KtExpression> leafBranches = new ArrayList<KtExpression>();
@NotNull Collection<KtExpression> branchExpressions collectResultingExpressionsOfConditionalExpressionRec(expression, leafBranches);
return leafBranches;
}
private static void collectResultingExpressionsOfConditionalExpressionRec(
@Nullable KtExpression expression,
@NotNull List<KtExpression> 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<KtExpression> branchExpressions = collectResultingExpressionsOfConditionalExpression(expression);
KotlinType expectedExpressionType = trace.get(EXPECTED_EXPRESSION_TYPE, expression); KotlinType expectedExpressionType = trace.get(EXPECTED_EXPRESSION_TYPE, expression);
if (expectedExpressionType != null && expectedExpressionType != DONT_CARE) return; if (expectedExpressionType != null && expectedExpressionType != DONT_CARE) return;
@@ -739,10 +783,13 @@ public class ControlFlowInformationProvider {
return; return;
} }
if (KotlinBuiltIns.isAnyOrNullableAny(expressionType)) { if (KotlinBuiltIns.isAnyOrNullableAny(expressionType)) {
boolean isUsedAsResultOfLambda = BindingContextUtilsKt.isUsedAsResultOfLambda(expression, trace.getBindingContext());
for (KtExpression branchExpression : branchExpressions) { for (KtExpression branchExpression : branchExpressions) {
if (branchExpression == null) continue; if (branchExpression == null) continue;
KotlinType branchType = trace.getType(branchExpression); KotlinType branchType = trace.getType(branchExpression);
if (branchType == null || KotlinBuiltIns.isAnyOrNullableAny(branchType)) { if (branchType == null
|| KotlinBuiltIns.isAnyOrNullableAny(branchType)
|| (isUsedAsResultOfLambda && KotlinBuiltIns.isUnitOrNullableUnit(branchType))) {
return; return;
} }
} }
@@ -798,11 +845,7 @@ public class ControlFlowInformationProvider {
KtWhenExpression whenExpression = (KtWhenExpression) element; KtWhenExpression whenExpression = (KtWhenExpression) element;
if (BindingContextUtilsKt.isUsedAsExpression(whenExpression, trace.getBindingContext())) { if (BindingContextUtilsKt.isUsedAsExpression(whenExpression, trace.getBindingContext())) {
List<KtExpression> branchExpressions = new ArrayList<KtExpression>(whenExpression.getEntries().size()); checkImplicitCastOnConditionalExpression(whenExpression);
for (KtWhenEntry whenEntry : whenExpression.getEntries()) {
branchExpressions.add(whenEntry.getExpression());
}
checkImplicitCastOnConditionalExpression(whenExpression, branchExpressions);
} }
if (whenExpression.getElseExpression() != null) continue; if (whenExpression.getElseExpression() != null) continue;
@@ -149,6 +149,7 @@ public interface BindingContext {
*/ */
WritableSlice<KtExpression, Boolean> PROCESSED = Slices.createSimpleSetSlice(); WritableSlice<KtExpression, Boolean> PROCESSED = Slices.createSimpleSetSlice();
WritableSlice<KtElement, Boolean> USED_AS_EXPRESSION = Slices.createSimpleSetSlice(); WritableSlice<KtElement, Boolean> USED_AS_EXPRESSION = Slices.createSimpleSetSlice();
WritableSlice<KtElement, Boolean> USED_AS_RESULT_OF_LAMBDA = Slices.createSimpleSetSlice();
WritableSlice<KtElement, Boolean> UNREACHABLE_CODE = Slices.createSimpleSetSlice(); WritableSlice<KtElement, Boolean> UNREACHABLE_CODE = Slices.createSimpleSetSlice();
WritableSlice<VariableDescriptor, CaptureKind> CAPTURED_IN_CLOSURE = new BasicWritableSlice<VariableDescriptor, CaptureKind>(DO_NOTHING); WritableSlice<VariableDescriptor, CaptureKind> CAPTURED_IN_CLOSURE = new BasicWritableSlice<VariableDescriptor, CaptureKind>(DO_NOTHING);
@@ -55,6 +55,7 @@ fun KtReturnExpression.getTargetFunction(context: BindingContext): KtCallableDec
} }
fun KtExpression.isUsedAsExpression(context: BindingContext): Boolean = context[BindingContext.USED_AS_EXPRESSION, this]!! 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) fun KtExpression.isUsedAsStatement(context: BindingContext): Boolean = !isUsedAsExpression(context)
@@ -0,0 +1,61 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_VARIABLE
fun println() {}
fun foo(x: Any) {}
fun <T> 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) <!IMPLICIT_CAST_TO_ANY!>42<!>
else <!IMPLICIT_CAST_TO_ANY!>println()<!>
)
fun testResultOfAnonFun2() =
run(fun () {
if (true) <!UNUSED_EXPRESSION!>42<!> else println()
})
fun testReturnFromAnonFun() =
run(fun () {
return if (true) <!CONSTANT_EXPECTED_TYPE_MISMATCH!>42<!> else println()
})
fun <!IMPLICIT_NOTHING_RETURN_TYPE!>testReturn1<!>() =
run {
return if (true) <!IMPLICIT_CAST_TO_ANY!>42<!>
else <!IMPLICIT_CAST_TO_ANY!>println()<!>
}
fun <!IMPLICIT_NOTHING_RETURN_TYPE!>testReturn2<!>() =
run {
return if (true) <!IMPLICIT_CAST_TO_ANY!>42<!>
else if (true) <!IMPLICIT_CAST_TO_ANY!>42<!>
else <!IMPLICIT_CAST_TO_ANY!>println()<!>
}
fun testUsage1() =
if (true) <!IMPLICIT_CAST_TO_ANY!>42<!>
else <!IMPLICIT_CAST_TO_ANY!>println()<!>
fun testUsage2() =
foo(if (true) 42 else println())
fun testUsage2Generic() =
fooGeneric(if (true) 42 else println())
val testUsage3 =
if (true) <!IMPLICIT_CAST_TO_ANY!>42<!>
else <!IMPLICIT_CAST_TO_ANY!>println()<!>
val testUsage4: Any get() =
if (true) 42 else println()
@@ -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 </*0*/ T> 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
@@ -0,0 +1,31 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_VARIABLE
fun println() {}
fun foo(x: Any) {}
fun <T> fooGeneric(x: T) {}
fun testMixedIfAndWhen() =
if (true)
when {
true -> if (true) <!IMPLICIT_CAST_TO_ANY!>42<!>
else <!IMPLICIT_CAST_TO_ANY!>1<!>
true -> if (true) <!IMPLICIT_CAST_TO_ANY!>42<!>
else <!IMPLICIT_CAST_TO_ANY!>println()<!>
else -> <!INVALID_IF_AS_EXPRESSION!>if (true) <!IMPLICIT_CAST_TO_ANY!>println()<!><!>
}
else <!IMPLICIT_CAST_TO_ANY!>println()<!>
fun testWrappedExpressions() =
if (true) {
println()
<!INVALID_IF_AS_EXPRESSION!>if (true) {
println()
if (true) {
<!IMPLICIT_CAST_TO_ANY!>println()<!>
}
else <!IMPLICIT_CAST_TO_ANY!>{}<!>
}<!>
}
else {
(((<!IMPLICIT_CAST_TO_ANY!>((42)) + 1<!>)))
}
@@ -0,0 +1,7 @@
package
public fun foo(/*0*/ x: kotlin.Any): kotlin.Unit
public fun </*0*/ T> fooGeneric(/*0*/ x: T): kotlin.Unit
public fun println(): kotlin.Unit
public fun testMixedIfAndWhen(): kotlin.Any
public fun testWrappedExpressions(): kotlin.Any
@@ -17,12 +17,12 @@ fun example() {
}(); }();
{ {
if (true) <!IMPLICIT_CAST_TO_ANY!>{}<!> else <!IMPLICIT_CAST_TO_ANY!>false<!> if (true) {} else false
}(); }();
{ {
if (true) <!IMPLICIT_CAST_TO_ANY!>true<!> else <!IMPLICIT_CAST_TO_ANY!>{}<!> if (true) true else {}
}() }()
fun t(): Boolean { fun t(): Boolean {
@@ -1,10 +1,10 @@
fun test1() { fun test1() {
run { run {
if (true) { if (true) {
<!INVALID_IF_AS_EXPRESSION, IMPLICIT_CAST_TO_ANY!>if (true) {}<!> <!INVALID_IF_AS_EXPRESSION!>if (true) {}<!>
} }
else { else {
<!IMPLICIT_CAST_TO_ANY!>1<!> 1
} }
} }
} }
@@ -20,10 +20,10 @@ fun foo() {
var <!ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE!>z<!> = 2 var <!ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE!>z<!> = 2
val r = { // type fun(): Any is inferred val r = { // type fun(): Any is inferred
if (true) { if (true) {
<!IMPLICIT_CAST_TO_ANY!>2<!> 2
} }
else { else {
<!IMPLICIT_CAST_TO_ANY!>z = 34<!> z = 34
} }
} }
val <!UNUSED_VARIABLE!>f<!>: ()-> Int = <!TYPE_MISMATCH!>r<!> val <!UNUSED_VARIABLE!>f<!>: ()-> Int = <!TYPE_MISMATCH!>r<!>
@@ -74,9 +74,9 @@ fun testCoercionToUnit() {
var <!ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE!>x<!> = 43 var <!ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE!>x<!> = 43
val checkType = { val checkType = {
if (true) { if (true) {
<!IMPLICIT_CAST_TO_ANY!>x = 4<!> x = 4
} else { } else {
<!IMPLICIT_CAST_TO_ANY!>45<!> 45
} }
} }
val <!UNUSED_VARIABLE!>f<!> : () -> String = <!TYPE_MISMATCH!>checkType<!> val <!UNUSED_VARIABLE!>f<!> : () -> String = <!TYPE_MISMATCH!>checkType<!>
@@ -0,0 +1,83 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_VARIABLE
fun println() {}
fun foo(x: Any) {}
fun <T> fooGeneric(x: T) {}
fun testResultOfLambda1() =
run {
when {
true -> 42
else -> println()
}
}
fun testResultOfLambda2() =
run {
when {
true -> 42
else ->
when {
true -> 42
else -> println()
}
}
}
fun <!IMPLICIT_NOTHING_RETURN_TYPE!>testReturn1<!>() =
run {
return when {
true -> <!IMPLICIT_CAST_TO_ANY!>42<!>
else -> <!IMPLICIT_CAST_TO_ANY!>println()<!>
}
}
fun <!IMPLICIT_NOTHING_RETURN_TYPE!>testReturn2<!>() =
run {
return when {
true -> <!IMPLICIT_CAST_TO_ANY!>42<!>
else ->
when {
true -> <!IMPLICIT_CAST_TO_ANY!>42<!>
else -> <!IMPLICIT_CAST_TO_ANY!>println()<!>
}
}
}
fun testUsage1() =
when {
true -> <!IMPLICIT_CAST_TO_ANY!>42<!>
else -> <!IMPLICIT_CAST_TO_ANY!>println()<!>
}
fun testUsage2() =
foo(when {
true -> 42
else -> println()
})
fun testUsage2Generic() =
fooGeneric(when {
true -> 42
else -> println()
})
val testUsage3 =
when {
true -> <!IMPLICIT_CAST_TO_ANY!>42<!>
else -> <!IMPLICIT_CAST_TO_ANY!>println()<!>
}
val testUsage4 =
when {
true -> <!IMPLICIT_CAST_TO_ANY!>42<!>
true -> <!IMPLICIT_CAST_TO_ANY!>42<!>
true -> <!IMPLICIT_CAST_TO_ANY!>42<!>
else -> <!IMPLICIT_CAST_TO_ANY!>println()<!>
}
val testUsage5: Any get() =
when {
true -> 42
else -> println()
}
@@ -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 </*0*/ T> 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
@@ -14,9 +14,19 @@ fun test1(word: String) =
fun test2(word: String) = fun test2(word: String) =
run { run {
if (word.length > 4) { if (word.length > 4) {
<!INVALID_IF_AS_EXPRESSION, IMPLICIT_CAST_TO_ANY!>if (word.startsWith("a")) longWords++<!> <!INVALID_IF_AS_EXPRESSION!>if (word.startsWith("a")) <!IMPLICIT_CAST_TO_ANY!>longWords++<!><!>
} }
else { else {
<!IMPLICIT_CAST_TO_ANY!>smallWords.add(word)<!> <!IMPLICIT_CAST_TO_ANY!>smallWords.add(word)<!>
} }
}
fun test3(word: String) =
run {
if (word.length > 4) {
longWords++
}
else {
System.out?.println(word) // Unit?
}
} }
@@ -4,3 +4,4 @@ public var longWords: kotlin.Int
public val smallWords: java.util.HashSet<kotlin.String> public val smallWords: java.util.HashSet<kotlin.String>
public fun test1(/*0*/ word: kotlin.String): kotlin.Any public fun test1(/*0*/ word: kotlin.String): kotlin.Any
public fun test2(/*0*/ word: kotlin.String): kotlin.Any public fun test2(/*0*/ word: kotlin.String): kotlin.Any
public fun test3(/*0*/ word: kotlin.String): kotlin.Any?
@@ -3390,6 +3390,18 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName); 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") @TestMetadata("ifWhenWithoutElse.kt")
public void testIfWhenWithoutElse() throws Exception { public void testIfWhenWithoutElse() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlStructures/ifWhenWithoutElse.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlStructures/ifWhenWithoutElse.kt");
@@ -3504,6 +3516,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName); 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") @TestMetadata("when.kt234.kt973.kt")
public void testWhen_kt234_kt973() throws Exception { public void testWhen_kt234_kt973() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlStructures/when.kt234.kt973.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlStructures/when.kt234.kt973.kt");