KT-10646, KT-10647:
Move IMPLICIT_CAST_TO_UNIT_OR_ANY to ControlFlowInformationProvider (where checks for 'if' and 'when' used as expressions are performed).
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
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;
|
||||
@@ -701,10 +702,17 @@ public class ControlFlowInformationProvider {
|
||||
for (KtElement element : instruction.getOwner().getValueElements(value)) {
|
||||
if (!(element instanceof KtIfExpression)) continue;
|
||||
KtIfExpression ifExpression = (KtIfExpression) element;
|
||||
if (ifExpression.getThen() != null && ifExpression.getElse() != null) continue;
|
||||
|
||||
if (BindingContextUtilsKt.isUsedAsExpression(ifExpression, trace.getBindingContext())) {
|
||||
trace.report(INVALID_IF_AS_EXPRESSION.on(ifExpression));
|
||||
KtExpression thenExpression = ifExpression.getThen();
|
||||
KtExpression elseExpression = ifExpression.getElse();
|
||||
|
||||
if (thenExpression == null || elseExpression == null) {
|
||||
trace.report(INVALID_IF_AS_EXPRESSION.on(ifExpression));
|
||||
}
|
||||
else {
|
||||
checkImplicitCastOnConditionalExpression(ifExpression, ImmutableList.of(thenExpression, elseExpression));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -712,6 +720,31 @@ public class ControlFlowInformationProvider {
|
||||
);
|
||||
}
|
||||
|
||||
private void checkImplicitCastOnConditionalExpression(
|
||||
@NotNull KtExpression expression,
|
||||
@NotNull Collection<KtExpression> branchExpressions
|
||||
) {
|
||||
KotlinType expectedExpressionType = trace.get(EXPECTED_EXPRESSION_TYPE, expression);
|
||||
if (expectedExpressionType != null) return;
|
||||
|
||||
KotlinType expressionType = trace.getType(expression);
|
||||
if (expressionType == null) {
|
||||
return;
|
||||
}
|
||||
if (KotlinBuiltIns.isAnyOrNullableAny(expressionType)) {
|
||||
for (KtExpression branchExpression : branchExpressions) {
|
||||
KotlinType branchType = trace.getType(branchExpression);
|
||||
if (branchType == null || KotlinBuiltIns.isAnyOrNullableAny(branchType)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
trace.report(IMPLICIT_CAST_TO_UNIT_OR_ANY.on(expression, expressionType));
|
||||
}
|
||||
else if (KotlinBuiltIns.isUnit(expressionType)) {
|
||||
trace.report(IMPLICIT_CAST_TO_UNIT_OR_ANY.on(expression, expressionType));
|
||||
}
|
||||
}
|
||||
|
||||
public void markWhenWithoutElse() {
|
||||
final Map<Instruction, Edges<InitControlFlowInfo>> initializers = pseudocodeVariablesData.getVariableInitializers();
|
||||
PseudocodeTraverserKt.traverse(
|
||||
@@ -741,6 +774,18 @@ public class ControlFlowInformationProvider {
|
||||
for (KtElement element : instruction.getOwner().getValueElements(value)) {
|
||||
if (!(element instanceof KtWhenExpression)) continue;
|
||||
KtWhenExpression whenExpression = (KtWhenExpression) element;
|
||||
|
||||
if (BindingContextUtilsKt.isUsedAsExpression(whenExpression, trace.getBindingContext())) {
|
||||
List<KtExpression> branchExpressions = new ArrayList<KtExpression>(whenExpression.getEntries().size() + 1);
|
||||
for (KtWhenEntry whenEntry : whenExpression.getEntries()) {
|
||||
branchExpressions.add(whenEntry.getExpression());
|
||||
}
|
||||
if (whenExpression.getElseExpression() != null) {
|
||||
branchExpressions.add(whenExpression.getElseExpression());
|
||||
}
|
||||
checkImplicitCastOnConditionalExpression(whenExpression, branchExpressions);
|
||||
}
|
||||
|
||||
if (whenExpression.getElseExpression() != null) continue;
|
||||
|
||||
BindingContext context = trace.getBindingContext();
|
||||
@@ -761,6 +806,8 @@ public class ControlFlowInformationProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
+7
-17
@@ -91,6 +91,8 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
|
||||
}
|
||||
|
||||
public KotlinTypeInfo visitIfExpression(KtIfExpression ifExpression, ExpressionTypingContext contextWithExpectedType, boolean isStatement) {
|
||||
components.dataFlowAnalyzer.recordExpectedType(contextWithExpectedType.trace, ifExpression, contextWithExpectedType.expectedType);
|
||||
|
||||
ExpressionTypingContext context = contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE);
|
||||
KtExpression condition = ifExpression.getCondition();
|
||||
DataFlowInfo conditionDataFlowInfo = checkCondition(context.scope, condition, context);
|
||||
@@ -112,12 +114,7 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
|
||||
? result.replaceJumpOutPossible(true).replaceJumpFlowInfo(conditionDataFlowInfo)
|
||||
: result;
|
||||
}
|
||||
return TypeInfoFactoryKt.createTypeInfo(components.dataFlowAnalyzer.checkImplicitCast(
|
||||
components.builtIns.getUnitType(), ifExpression,
|
||||
contextWithExpectedType, isStatement
|
||||
),
|
||||
thenInfo.or(elseInfo)
|
||||
);
|
||||
return TypeInfoFactoryKt.createTypeInfo(components.builtIns.getUnitType(), thenInfo.or(elseInfo));
|
||||
}
|
||||
if (thenBranch == null) {
|
||||
return getTypeInfoWhenOnlyOneBranchIsPresent(
|
||||
@@ -173,9 +170,7 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
|
||||
}
|
||||
|
||||
// If break or continue was possible, take condition check info as the jump info
|
||||
return TypeInfoFactoryKt
|
||||
.createTypeInfo(components.dataFlowAnalyzer.checkImplicitCast(resultType, ifExpression, contextWithExpectedType, isStatement),
|
||||
resultDataFlowInfo, loopBreakContinuePossible, conditionDataFlowInfo);
|
||||
return TypeInfoFactoryKt.createTypeInfo(resultType, resultDataFlowInfo, loopBreakContinuePossible, conditionDataFlowInfo);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -199,15 +194,10 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
|
||||
} else {
|
||||
dataFlowInfo = typeInfo.getDataFlowInfo().or(otherInfo);
|
||||
}
|
||||
return components.dataFlowAnalyzer.checkImplicitCast(
|
||||
components.dataFlowAnalyzer.checkType(
|
||||
typeInfo.replaceType(components.builtIns.getUnitType()),
|
||||
ifExpression,
|
||||
context
|
||||
),
|
||||
return components.dataFlowAnalyzer.checkType(
|
||||
typeInfo.replaceType(components.builtIns.getUnitType()),
|
||||
ifExpression,
|
||||
context,
|
||||
isStatement
|
||||
context
|
||||
).replaceDataFlowInfo(dataFlowInfo);
|
||||
}
|
||||
|
||||
|
||||
@@ -271,29 +271,6 @@ public class DataFlowAnalyzer {
|
||||
return builtIns.getUnitType();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public KotlinType checkImplicitCast(@Nullable KotlinType expressionType, @NotNull KtExpression expression, @NotNull ResolutionContext context, boolean isStatement) {
|
||||
boolean isIfExpression = expression instanceof KtIfExpression;
|
||||
if (expressionType != null
|
||||
&& (context.expectedType == NO_EXPECTED_TYPE || isIfExpression)
|
||||
&& context.contextDependency == INDEPENDENT && !isStatement
|
||||
&& (KotlinBuiltIns.isUnit(expressionType) || KotlinBuiltIns.isAnyOrNullableAny(expressionType))
|
||||
&& !DynamicTypesKt.isDynamic(expressionType)) {
|
||||
if (isIfExpression && KotlinBuiltIns.isUnit(expressionType) || isIfExpression && context.expectedType != NO_EXPECTED_TYPE) {
|
||||
return expressionType;
|
||||
}
|
||||
else {
|
||||
context.trace.report(IMPLICIT_CAST_TO_UNIT_OR_ANY.on(expression, expressionType));
|
||||
}
|
||||
}
|
||||
return expressionType;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public KotlinTypeInfo checkImplicitCast(@NotNull KotlinTypeInfo typeInfo, @NotNull KtExpression expression, @NotNull ResolutionContext context, boolean isStatement) {
|
||||
return typeInfo.replaceType(checkImplicitCast(typeInfo.getType(), expression, context, isStatement));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public KotlinTypeInfo illegalStatementType(@NotNull KtExpression expression, @NotNull ExpressionTypingContext context, @NotNull ExpressionTypingInternals facade) {
|
||||
facade.checkStatementType(
|
||||
|
||||
+2
-5
@@ -162,12 +162,9 @@ public class PatternMatchingTypingVisitor extends ExpressionTypingVisitor {
|
||||
if (isExhaustive && expression.getElseExpression() == null && KotlinBuiltIns.isNothing(resultType)) {
|
||||
context.trace.record(BindingContext.IMPLICIT_EXHAUSTIVE_WHEN, expression);
|
||||
}
|
||||
resultType = components.dataFlowAnalyzer.checkType(resultType, expression, contextWithExpectedType);
|
||||
}
|
||||
return TypeInfoFactoryKt.createTypeInfo(expressionTypes.isEmpty() ? null : components.dataFlowAnalyzer.checkType(
|
||||
components.dataFlowAnalyzer.checkImplicitCast(
|
||||
resultType, expression,
|
||||
contextWithExpectedType, isStatement),
|
||||
expression, contextWithExpectedType),
|
||||
return TypeInfoFactoryKt.createTypeInfo(resultType,
|
||||
commonDataFlowInfo,
|
||||
loopBreakContinuePossible,
|
||||
contextWithExpectedType.dataFlowInfo);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
val test1 = { <!IMPLICIT_CAST_TO_UNIT_OR_ANY!>if (true) 1 else ""<!> }
|
||||
|
||||
val test2 = { { <!IMPLICIT_CAST_TO_UNIT_OR_ANY!>if (true) 1 else ""<!> } }
|
||||
|
||||
val test3: (Boolean) -> Any = { if (it) 1 else "" }
|
||||
|
||||
val test4: (Boolean) -> Any? = { if (it) 1 else "" }
|
||||
@@ -0,0 +1,6 @@
|
||||
package
|
||||
|
||||
public val test1: () -> kotlin.Any
|
||||
public val test2: () -> () -> kotlin.Any
|
||||
public val test3: (kotlin.Boolean) -> kotlin.Any
|
||||
public val test4: (kotlin.Boolean) -> kotlin.Any?
|
||||
+2
-2
@@ -17,12 +17,12 @@ fun example() {
|
||||
}();
|
||||
|
||||
{
|
||||
if (true) {} else false
|
||||
<!IMPLICIT_CAST_TO_UNIT_OR_ANY!>if (true) {} else false<!>
|
||||
}();
|
||||
|
||||
|
||||
{
|
||||
if (true) true else {}
|
||||
<!IMPLICIT_CAST_TO_UNIT_OR_ANY!>if (true) true else {}<!>
|
||||
}()
|
||||
|
||||
fun t(): Boolean {
|
||||
|
||||
+5
-5
@@ -18,13 +18,13 @@ val w = <!EXPRESSION_EXPECTED!>while (true) {}<!>
|
||||
|
||||
fun foo() {
|
||||
var <!ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE!>z<!> = 2
|
||||
val r = { // type fun(): Int is inferred
|
||||
if (true) {
|
||||
val r = { // type fun(): Any is inferred
|
||||
<!IMPLICIT_CAST_TO_UNIT_OR_ANY!>if (true) {
|
||||
2
|
||||
}
|
||||
else {
|
||||
z = 34
|
||||
}
|
||||
}<!>
|
||||
}
|
||||
val <!UNUSED_VARIABLE!>f<!>: ()-> Int = <!TYPE_MISMATCH!>r<!>
|
||||
val <!UNUSED_VARIABLE!>g<!>: ()-> Any = r
|
||||
@@ -73,11 +73,11 @@ fun testCoercionToUnit() {
|
||||
|
||||
var <!ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE!>x<!> = 43
|
||||
val checkType = {
|
||||
if (true) {
|
||||
<!IMPLICIT_CAST_TO_UNIT_OR_ANY!>if (true) {
|
||||
x = 4
|
||||
} else {
|
||||
45
|
||||
}
|
||||
}<!>
|
||||
}
|
||||
val <!UNUSED_VARIABLE!>f<!> : () -> String = <!TYPE_MISMATCH!>checkType<!>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
val test1 = { <!IMPLICIT_CAST_TO_UNIT_OR_ANY!>when (true) { true -> 1; else -> "" }<!> }
|
||||
|
||||
val test2 = { { <!IMPLICIT_CAST_TO_UNIT_OR_ANY!>when (true) { true -> 1; else -> "" }<!> } }
|
||||
|
||||
val test3: (Boolean) -> Any = { when (true) { true -> 1; else -> "" } }
|
||||
|
||||
val test4: (Boolean) -> Any? = { when (true) { true -> 1; else -> "" } }
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package
|
||||
|
||||
public val test1: () -> kotlin.Any
|
||||
public val test2: () -> () -> kotlin.Any
|
||||
public val test3: (kotlin.Boolean) -> kotlin.Any
|
||||
public val test4: (kotlin.Boolean) -> kotlin.Any?
|
||||
@@ -3318,6 +3318,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("ifInResultOfLambda.kt")
|
||||
public void testIfInResultOfLambda() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlStructures/ifInResultOfLambda.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("ifWhenWithoutElse.kt")
|
||||
public void testIfWhenWithoutElse() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlStructures/ifWhenWithoutElse.kt");
|
||||
@@ -3402,6 +3408,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("whenInResultOfLambda.kt")
|
||||
public void testWhenInResultOfLambda() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlStructures/whenInResultOfLambda.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");
|
||||
|
||||
Reference in New Issue
Block a user