DFA: take into account if / else and when expression assignments #KT-10002 Fixed

Also #KT-7479 Fixed
This commit is contained in:
Mikhail Glukhikh
2015-11-17 13:50:22 +03:00
parent e7fc6bcc6e
commit 7cd867b936
10 changed files with 102 additions and 4 deletions
@@ -31,6 +31,8 @@ class DataFlowValue(val id: Any?, val type: KotlinType, val kind: DataFlowValue.
// or protected / public member value from the same module without open / custom getter // or protected / public member value from the same module without open / custom getter
// Smart casts are completely safe // Smart casts are completely safe
STABLE_VALUE("stable"), STABLE_VALUE("stable"),
// Block, or if / else, or when, or (in future) some other complex expression
STABLE_COMPLEX_EXPRESSION("complex expression", ""),
// Member value with open / custom getter // Member value with open / custom getter
// Smart casts are not safe // Smart casts are not safe
PROPERTY_WITH_GETTER("custom getter", "property that has open or custom getter"), PROPERTY_WITH_GETTER("custom getter", "property that has open or custom getter"),
@@ -60,7 +62,7 @@ class DataFlowValue(val id: Any?, val type: KotlinType, val kind: DataFlowValue.
* Predictable means here we do not expect some sudden change of their values, * Predictable means here we do not expect some sudden change of their values,
* like accessing mutable properties in another thread, so smart casts can be used safely. * like accessing mutable properties in another thread, so smart casts can be used safely.
*/ */
val isPredictable = (kind == Kind.STABLE_VALUE || kind == Kind.PREDICTABLE_VARIABLE) val isPredictable = (kind == Kind.STABLE_VALUE || kind == Kind.STABLE_COMPLEX_EXPRESSION || kind == Kind.PREDICTABLE_VARIABLE)
@JvmName("isPredictable") get @JvmName("isPredictable") get
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@@ -96,6 +96,10 @@ public class DataFlowValueFactory {
Nullability.NOT_NULL); Nullability.NOT_NULL);
} }
if (expression instanceof KtBlockExpression || expression instanceof KtIfExpression || expression instanceof KtWhenExpression) {
return createDataFlowValueForComplexExpression(expression, type);
}
IdentifierInfo result = getIdForStableIdentifier(expression, bindingContext, containingDeclarationOrModule); IdentifierInfo result = getIdForStableIdentifier(expression, bindingContext, containingDeclarationOrModule);
return new DataFlowValue(result == NO_IDENTIFIER_INFO ? expression : result.id, return new DataFlowValue(result == NO_IDENTIFIER_INFO ? expression : result.id,
type, type,
@@ -155,6 +159,14 @@ public class DataFlowValueFactory {
getImmanentNullability(type)); getImmanentNullability(type));
} }
@NotNull
private static DataFlowValue createDataFlowValueForComplexExpression(
@NotNull KtExpression expression,
@NotNull KotlinType type
) {
return new DataFlowValue(expression, type, Kind.STABLE_COMPLEX_EXPRESSION, getImmanentNullability(type));
}
@NotNull @NotNull
private static Nullability getImmanentNullability(@NotNull KotlinType type) { private static Nullability getImmanentNullability(@NotNull KotlinType type) {
return TypeUtils.isNullableType(type) ? Nullability.UNKNOWN : Nullability.NOT_NULL; return TypeUtils.isNullableType(type) ? Nullability.UNKNOWN : Nullability.NOT_NULL;
@@ -32,6 +32,8 @@ import org.jetbrains.kotlin.resolve.ModifiersChecker;
import org.jetbrains.kotlin.resolve.calls.model.MutableDataFlowInfoForArguments; import org.jetbrains.kotlin.resolve.calls.model.MutableDataFlowInfoForArguments;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo; import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo;
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue;
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt; import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.resolve.inline.InlineUtil; import org.jetbrains.kotlin.resolve.inline.InlineUtil;
import org.jetbrains.kotlin.resolve.scopes.LexicalScope; import org.jetbrains.kotlin.resolve.scopes.LexicalScope;
@@ -137,12 +139,21 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
KotlinTypeInfo elseTypeInfo = BindingContextUtils.getRecordedTypeInfo(elseBranch, bindingContext); KotlinTypeInfo elseTypeInfo = BindingContextUtils.getRecordedTypeInfo(elseBranch, bindingContext);
assert thenTypeInfo != null : "'Then' branch of if expression was not processed: " + ifExpression; assert thenTypeInfo != null : "'Then' branch of if expression was not processed: " + ifExpression;
assert elseTypeInfo != null : "'Else' branch of if expression was not processed: " + ifExpression; assert elseTypeInfo != null : "'Else' branch of if expression was not processed: " + ifExpression;
boolean loopBreakContinuePossible = thenTypeInfo.getJumpOutPossible() || elseTypeInfo.getJumpOutPossible();
KotlinType resultType = resolvedCall.getResultingDescriptor().getReturnType();
KotlinType thenType = thenTypeInfo.getType(); KotlinType thenType = thenTypeInfo.getType();
KotlinType elseType = elseTypeInfo.getType(); KotlinType elseType = elseTypeInfo.getType();
DataFlowInfo thenDataFlowInfo = thenTypeInfo.getDataFlowInfo(); DataFlowInfo thenDataFlowInfo = thenTypeInfo.getDataFlowInfo();
DataFlowInfo elseDataFlowInfo = elseTypeInfo.getDataFlowInfo(); DataFlowInfo elseDataFlowInfo = elseTypeInfo.getDataFlowInfo();
if (resultType != null && thenType != null && elseType != null) {
DataFlowValue resultValue = DataFlowValueFactory.createDataFlowValue(ifExpression, resultType, context);
DataFlowValue thenValue = DataFlowValueFactory.createDataFlowValue(thenBranch, thenType, context);
thenDataFlowInfo = thenDataFlowInfo.assign(resultValue, thenValue);
DataFlowValue elseValue = DataFlowValueFactory.createDataFlowValue(elseBranch, elseType, context);
elseDataFlowInfo = elseDataFlowInfo.assign(resultValue, elseValue);
}
boolean loopBreakContinuePossible = thenTypeInfo.getJumpOutPossible() || elseTypeInfo.getJumpOutPossible();
boolean jumpInThen = thenType != null && KotlinBuiltIns.isNothing(thenType); boolean jumpInThen = thenType != null && KotlinBuiltIns.isNothing(thenType);
boolean jumpInElse = elseType != null && KotlinBuiltIns.isNothing(elseType); boolean jumpInElse = elseType != null && KotlinBuiltIns.isNothing(elseType);
@@ -161,7 +172,6 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
resultDataFlowInfo = thenDataFlowInfo.or(elseDataFlowInfo); resultDataFlowInfo = thenDataFlowInfo.or(elseDataFlowInfo);
} }
KotlinType resultType = resolvedCall.getResultingDescriptor().getReturnType();
// If break or continue was possible, take condition check info as the jump info // If break or continue was possible, take condition check info as the jump info
return TypeInfoFactoryKt return TypeInfoFactoryKt
.createTypeInfo(components.dataFlowAnalyzer.checkImplicitCast(resultType, ifExpression, contextWithExpectedType, isStatement), .createTypeInfo(components.dataFlowAnalyzer.checkImplicitCast(resultType, ifExpression, contextWithExpectedType, isStatement),
@@ -29,6 +29,8 @@ import org.jetbrains.kotlin.resolve.*;
import org.jetbrains.kotlin.resolve.calls.context.ContextDependency; import org.jetbrains.kotlin.resolve.calls.context.ContextDependency;
import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext; import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext;
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo; import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo;
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue;
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory;
import org.jetbrains.kotlin.resolve.scopes.LexicalScope; import org.jetbrains.kotlin.resolve.scopes.LexicalScope;
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind; import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind;
import org.jetbrains.kotlin.resolve.scopes.LexicalWritableScope; import org.jetbrains.kotlin.resolve.scopes.LexicalWritableScope;
@@ -241,6 +243,13 @@ public class ExpressionTypingServices {
result = getTypeOfLastExpressionInBlock( result = getTypeOfLastExpressionInBlock(
statementExpression, newContext.replaceExpectedType(context.expectedType), coercionStrategyForLastExpression, statementExpression, newContext.replaceExpectedType(context.expectedType), coercionStrategyForLastExpression,
blockLevelVisitor); blockLevelVisitor);
if (result.getType() != null && statementExpression.getParent() instanceof KtBlockExpression) {
DataFlowValue lastExpressionValue = DataFlowValueFactory.createDataFlowValue(
statementExpression, result.getType(), context);
DataFlowValue blockExpressionValue = DataFlowValueFactory.createDataFlowValue(
(KtBlockExpression) statementExpression.getParent(), result.getType(), context);
result = result.replaceDataFlowInfo(result.getDataFlowInfo().assign(blockExpressionValue, lastExpressionValue));
}
} }
else { else {
result = blockLevelVisitor result = blockLevelVisitor
@@ -114,6 +114,7 @@ public class PatternMatchingTypingVisitor extends ExpressionTypingVisitor {
Set<KotlinType> expressionTypes = Sets.newHashSet(); Set<KotlinType> expressionTypes = Sets.newHashSet();
DataFlowInfo commonDataFlowInfo = null; DataFlowInfo commonDataFlowInfo = null;
DataFlowInfo elseDataFlowInfo = context.dataFlowInfo; DataFlowInfo elseDataFlowInfo = context.dataFlowInfo;
DataFlowValue whenValue = DataFlowValueFactory.createDataFlowValue(expression, components.builtIns.getNullableAnyType(), context);
for (KtWhenEntry whenEntry : expression.getEntries()) { for (KtWhenEntry whenEntry : expression.getEntries()) {
DataFlowInfos infosForCondition = getDataFlowInfosForEntryCondition( DataFlowInfos infosForCondition = getDataFlowInfosForEntryCondition(
whenEntry, context.replaceDataFlowInfo(elseDataFlowInfo), subjectExpression, subjectType, subjectDataFlowValue); whenEntry, context.replaceDataFlowInfo(elseDataFlowInfo), subjectExpression, subjectType, subjectDataFlowValue);
@@ -131,6 +132,8 @@ public class PatternMatchingTypingVisitor extends ExpressionTypingVisitor {
KotlinType type = typeInfo.getType(); KotlinType type = typeInfo.getType();
if (type != null) { if (type != null) {
expressionTypes.add(type); expressionTypes.add(type);
DataFlowValue entryValue = DataFlowValueFactory.createDataFlowValue(bodyExpression, type, context);
typeInfo = typeInfo.replaceDataFlowInfo(typeInfo.getDataFlowInfo().assign(whenValue, entryValue));
} }
if (commonDataFlowInfo == null) { if (commonDataFlowInfo == null) {
commonDataFlowInfo = typeInfo.getDataFlowInfo(); commonDataFlowInfo = typeInfo.getDataFlowInfo();
@@ -150,9 +153,14 @@ public class PatternMatchingTypingVisitor extends ExpressionTypingVisitor {
commonDataFlowInfo = commonDataFlowInfo.or(context.dataFlowInfo); commonDataFlowInfo = commonDataFlowInfo.or(context.dataFlowInfo);
} }
KotlinType resultType = expressionTypes.isEmpty() ? null : CommonSupertypes.commonSupertype(expressionTypes);
if (resultType != null) {
DataFlowValue resultValue = DataFlowValueFactory.createDataFlowValue(expression, resultType, context);
commonDataFlowInfo = commonDataFlowInfo.assign(resultValue, whenValue);
}
return TypeInfoFactoryKt.createTypeInfo(expressionTypes.isEmpty() ? null : components.dataFlowAnalyzer.checkType( return TypeInfoFactoryKt.createTypeInfo(expressionTypes.isEmpty() ? null : components.dataFlowAnalyzer.checkType(
components.dataFlowAnalyzer.checkImplicitCast( components.dataFlowAnalyzer.checkImplicitCast(
CommonSupertypes.commonSupertype(expressionTypes), expression, resultType, expression,
contextWithExpectedType, isStatement), contextWithExpectedType, isStatement),
expression, contextWithExpectedType), expression, contextWithExpectedType),
commonDataFlowInfo, commonDataFlowInfo,
@@ -0,0 +1,19 @@
fun baz(s: String?): String {
// If String type is given explicitly, problem disappears
val t = if (s == null) {
""
}
else {
val u: String? = null
if (u == null) return ""
// !! is detected as unnecessary here
u
}
return <!DEBUG_INFO_SMARTCAST!>t<!>
}
fun foo(s: String?): String {
if (s == null) return ""
val t = if (s == "abc") <!DEBUG_INFO_SMARTCAST!>s<!> else "xyz"
return t
}
@@ -0,0 +1,4 @@
package
public fun baz(/*0*/ s: kotlin.String?): kotlin.String
public fun foo(/*0*/ s: kotlin.String?): kotlin.String
@@ -0,0 +1,18 @@
fun baz(s: String?): String {
if (s == null) return ""
// if explicit type String is given for t, problem disappears
val t = when(<!DEBUG_INFO_SMARTCAST!>s<!>) {
// !! is detected as unnecessary here
"abc" -> s
else -> "xyz"
}
return <!DEBUG_INFO_SMARTCAST!>t<!>
}
fun foo(s: String?): String {
val t = when {
s != null -> s
else -> ""
}
return <!DEBUG_INFO_SMARTCAST!>t<!>
}
@@ -0,0 +1,4 @@
package
public fun baz(/*0*/ s: kotlin.String?): kotlin.String
public fun foo(/*0*/ s: kotlin.String?): kotlin.String
@@ -14961,6 +14961,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("ifExprNonNull.kt")
public void testIfExprNonNull() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/ifExprNonNull.kt");
doTest(fileName);
}
@TestMetadata("implicitReceiver.kt") @TestMetadata("implicitReceiver.kt")
public void testImplicitReceiver() throws Exception { public void testImplicitReceiver() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/implicitReceiver.kt");
@@ -15291,6 +15297,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("whenExprNonNull.kt")
public void testWhenExprNonNull() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/whenExprNonNull.kt");
doTest(fileName);
}
@TestMetadata("whenSubjectImpossible.kt") @TestMetadata("whenSubjectImpossible.kt")
public void testWhenSubjectImpossible() throws Exception { public void testWhenSubjectImpossible() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/whenSubjectImpossible.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/whenSubjectImpossible.kt");