Handle equality checks for 'when' and data classes

This commit is contained in:
Dmitry Petrov
2018-02-06 17:51:12 +03:00
parent 299eb24ca9
commit 00325ae539
19 changed files with 631 additions and 89 deletions
@@ -40,6 +40,7 @@ import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue;
import org.jetbrains.kotlin.resolve.calls.smartcasts.ExplicitSmartCasts;
import org.jetbrains.kotlin.resolve.calls.smartcasts.ImplicitSmartCasts;
import org.jetbrains.kotlin.resolve.calls.tower.KotlinResolutionCallbacksImpl;
import org.jetbrains.kotlin.resolve.checkers.PrimitiveNumericComparisonInfo;
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant;
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics;
import org.jetbrains.kotlin.resolve.scopes.LexicalScope;
@@ -267,8 +268,7 @@ public interface BindingContext {
WritableSlice<KtFunction, KotlinResolutionCallbacksImpl.LambdaInfo> NEW_INFERENCE_LAMBDA_INFO = new BasicWritableSlice<>(DO_NOTHING);
WritableSlice<KtBinaryExpression, KotlinType> PRIMITIVE_NUMERIC_COMPARISON_TYPE = Slices.createSimpleSlice();
WritableSlice<KtExpression, KotlinType> PRIMITIVE_NUMERIC_COMPARISON_OPERAND_TYPE = Slices.createSimpleSlice();
WritableSlice<KtExpression, PrimitiveNumericComparisonInfo> PRIMITIVE_NUMERIC_COMPARISON_INFO = Slices.createSimpleSlice();
@SuppressWarnings("UnusedDeclaration")
@Deprecated // This field is needed only for the side effects of its initializer
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
@@ -17,6 +18,12 @@ import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.*
class PrimitiveNumericComparisonInfo(
val comparisonType: KotlinType,
val leftType: KotlinType,
val rightType: KotlinType
)
object PrimitiveNumericComparisonCallChecker : CallChecker {
private val comparisonOperatorTokens = setOf(KtTokens.EQEQ, KtTokens.EXCLEQ, KtTokens.LT, KtTokens.LTEQ, KtTokens.GT, KtTokens.GTEQ)
@@ -29,15 +36,27 @@ object PrimitiveNumericComparisonCallChecker : CallChecker {
val leftExpr = binaryExpression.left ?: return
val rightExpr = binaryExpression.right ?: return
val leftType = context.getInferredPrimitiveNumericType(leftExpr) ?: return
val rightType = context.getInferredPrimitiveNumericType(rightExpr) ?: return
val leftTypes = context.getStableTypesForExpression(leftExpr)
val rightTypes = context.getStableTypesForExpression(rightExpr)
context.trace.record(BindingContext.PRIMITIVE_NUMERIC_COMPARISON_OPERAND_TYPE, leftExpr, leftType)
context.trace.record(BindingContext.PRIMITIVE_NUMERIC_COMPARISON_OPERAND_TYPE, rightExpr, rightType)
inferPrimitiveNumericComparisonType(context.trace, leftTypes, rightTypes, binaryExpression)
}
val leastCommonType = leastCommonPrimitiveNumericType(leftType, rightType)
fun inferPrimitiveNumericComparisonType(
trace: BindingTrace,
leftTypes: List<KotlinType>,
rightTypes: List<KotlinType>,
comparison: KtExpression
) {
val leftPrimitiveType = leftTypes.findPrimitiveType() ?: return
val rightPrimitiveType = rightTypes.findPrimitiveType() ?: return
val leastCommonType = leastCommonPrimitiveNumericType(leftPrimitiveType, rightPrimitiveType)
context.trace.record(BindingContext.PRIMITIVE_NUMERIC_COMPARISON_TYPE, binaryExpression, leastCommonType)
trace.record(
BindingContext.PRIMITIVE_NUMERIC_COMPARISON_INFO,
comparison,
PrimitiveNumericComparisonInfo(leastCommonType, leftPrimitiveType, rightPrimitiveType)
)
}
private fun leastCommonPrimitiveNumericType(t1: KotlinType, t2: KotlinType): KotlinType {
@@ -60,14 +79,14 @@ object PrimitiveNumericComparisonCallChecker : CallChecker {
else -> this
}
private fun CallCheckerContext.getInferredPrimitiveNumericType(expression: KtExpression): KotlinType? {
val type = trace.bindingContext.getType(expression) ?: return null
private fun CallCheckerContext.getStableTypesForExpression(expression: KtExpression): List<KotlinType> {
val type = trace.bindingContext.getType(expression) ?: return emptyList()
val dataFlowValue = DataFlowValueFactory.createDataFlowValue(
expression, type, trace.bindingContext, resolutionContext.scope.ownerDescriptor
)
val dataFlowInfo = trace.get(BindingContext.EXPRESSION_TYPE_INFO, expression)?.dataFlowInfo ?: return null
val dataFlowInfo = trace.get(BindingContext.EXPRESSION_TYPE_INFO, expression)?.dataFlowInfo ?: return emptyList()
val stableTypes = dataFlowInfo.getStableTypes(dataFlowValue, languageVersionSettings)
return (listOf(type) + stableTypes).findPrimitiveType()
return listOf(type) + stableTypes
}
private fun List<KotlinType>.findPrimitiveType() =
@@ -34,6 +34,7 @@ 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.calls.util.CallMaker
import org.jetbrains.kotlin.resolve.checkers.PrimitiveNumericComparisonCallChecker
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.TypeUtils.NO_EXPECTED_TYPE
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
@@ -259,7 +260,8 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
if (checkSmartCastToExpectedTypeInSubject(
contextBeforeSubject, subjectExpression, subjectType,
possibleCastType
)) {
)
) {
return
}
}
@@ -271,7 +273,8 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
if (checkSmartCastToExpectedTypeInSubject(
contextBeforeSubject, subjectExpression, subjectType,
notNullableType
)) {
)
) {
return
}
}
@@ -384,7 +387,7 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
val expression = condition.expression
if (expression != null) {
val basicDataFlowInfo = checkTypeForExpressionCondition(
context, expression, subjectType, subjectExpression == null, subjectDataFlowValue
context, expression, subjectType, subjectExpression, subjectDataFlowValue
)
val moduleDescriptor = DescriptorUtils.getContainingModule(context.scope.ownerDescriptor)
val dataFlowInfoFromES =
@@ -404,14 +407,16 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
context: ExpressionTypingContext,
expression: KtExpression,
subjectType: KotlinType,
conditionExpected: Boolean,
subjectExpression: KtExpression?,
subjectDataFlowValue: DataFlowValue
): ConditionalDataFlowInfo {
var newContext = context
val typeInfo = facade.getTypeInfo(expression, newContext)
val type = typeInfo.type ?: return noChange(newContext)
newContext = newContext.replaceDataFlowInfo(typeInfo.dataFlowInfo)
if (conditionExpected) {
if (subjectExpression == null) { // condition expected
val booleanType = components.builtIns.booleanType
val checkedTypeInfo = components.dataFlowAnalyzer.checkType(typeInfo, expression, newContext.replaceExpectedType(booleanType))
if (KotlinTypeChecker.DEFAULT.equalTypes(booleanType, checkedTypeInfo.type ?: type)) {
@@ -421,8 +426,21 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping
}
return noChange(newContext)
}
checkTypeCompatibility(newContext, type, subjectType, expression)
val expressionDataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, type, newContext)
val subjectStableTypes =
listOf(subjectType) + context.dataFlowInfo.getStableTypes(subjectDataFlowValue, components.languageVersionSettings)
val expressionStableTypes =
listOf(type) + newContext.dataFlowInfo.getStableTypes(expressionDataFlowValue, components.languageVersionSettings)
PrimitiveNumericComparisonCallChecker.inferPrimitiveNumericComparisonType(
context.trace,
subjectStableTypes,
expressionStableTypes,
expression
)
val result = noChange(newContext)
return ConditionalDataFlowInfo(
result.thenInfo.equate(