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;
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<Instruction> 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<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() {
PseudocodeTraverserKt.traverse(
pseudocode, TraversalOrder.FORWARD, new ControlFlowInformationProvider.FunctionVoid1<Instruction>() {
@@ -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<KtExpression> branchExpressions
private static List<KtExpression> collectResultingExpressionsOfConditionalExpression(KtExpression expression) {
List<KtExpression> leafBranches = new ArrayList<KtExpression>();
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);
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<KtExpression> branchExpressions = new ArrayList<KtExpression>(whenExpression.getEntries().size());
for (KtWhenEntry whenEntry : whenExpression.getEntries()) {
branchExpressions.add(whenEntry.getExpression());
}
checkImplicitCastOnConditionalExpression(whenExpression, branchExpressions);
checkImplicitCastOnConditionalExpression(whenExpression);
}
if (whenExpression.getElseExpression() != null) continue;
@@ -149,6 +149,7 @@ public interface BindingContext {
*/
WritableSlice<KtExpression, Boolean> PROCESSED = 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<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.isUsedAsResultOfLambda(context: BindingContext): Boolean = context[BindingContext.USED_AS_RESULT_OF_LAMBDA, this]!!
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 {
@@ -1,10 +1,10 @@
fun test1() {
run {
if (true) {
<!INVALID_IF_AS_EXPRESSION, IMPLICIT_CAST_TO_ANY!>if (true) {}<!>
<!INVALID_IF_AS_EXPRESSION!>if (true) {}<!>
}
else {
<!IMPLICIT_CAST_TO_ANY!>1<!>
1
}
}
}
@@ -20,10 +20,10 @@ fun foo() {
var <!ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE!>z<!> = 2
val r = { // type fun(): Any is inferred
if (true) {
<!IMPLICIT_CAST_TO_ANY!>2<!>
2
}
else {
<!IMPLICIT_CAST_TO_ANY!>z = 34<!>
z = 34
}
}
val <!UNUSED_VARIABLE!>f<!>: ()-> Int = <!TYPE_MISMATCH!>r<!>
@@ -74,9 +74,9 @@ fun testCoercionToUnit() {
var <!ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE!>x<!> = 43
val checkType = {
if (true) {
<!IMPLICIT_CAST_TO_ANY!>x = 4<!>
x = 4
} else {
<!IMPLICIT_CAST_TO_ANY!>45<!>
45
}
}
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) =
run {
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 {
<!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 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?
@@ -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");