Refactoring: context instead of trace in WhenChecker and around

This commit is contained in:
Mikhail Glukhikh
2016-01-11 18:32:27 +03:00
parent 936fee0afa
commit 44b07d8dfa
7 changed files with 46 additions and 47 deletions
@@ -104,9 +104,9 @@ class JavaNullabilityWarningsChecker : AdditionalTypeChecker {
val type = expression.subjectExpression?.let { c.trace.getType(it) } ?: return val type = expression.subjectExpression?.let { c.trace.getType(it) } ?: return
if (type.isFlexible() && TypeUtils.isNullableType(type.flexibility().upperBound) && !type.annotations.isMarkedNotNull()) { if (type.isFlexible() && TypeUtils.isNullableType(type.flexibility().upperBound) && !type.annotations.isMarkedNotNull()) {
val enumClassDescriptor = WhenChecker.getClassDescriptorOfTypeIfEnum(type) ?: return val enumClassDescriptor = WhenChecker.getClassDescriptorOfTypeIfEnum(type) ?: return
val context = c.trace.bindingContext
if (WhenChecker.getEnumMissingCases(expression, c.trace, enumClassDescriptor).isEmpty() if (WhenChecker.getEnumMissingCases(expression, context, enumClassDescriptor).isEmpty()
&& !WhenChecker.containsNullCase(expression, c.trace)) { && !WhenChecker.containsNullCase(expression, context)) {
c.trace.report(ErrorsJvm.WHEN_ENUM_CAN_BE_NULL_IN_JAVA.on(expression.getSubjectExpression())) c.trace.report(ErrorsJvm.WHEN_ENUM_CAN_BE_NULL_IN_JAVA.on(expression.getSubjectExpression()))
} }
@@ -743,7 +743,8 @@ public class ControlFlowInformationProvider {
KtWhenExpression whenExpression = (KtWhenExpression) element; KtWhenExpression whenExpression = (KtWhenExpression) element;
if (whenExpression.getElseExpression() != null) continue; if (whenExpression.getElseExpression() != null) continue;
List<WhenMissingCase> necessaryCases = WhenChecker.getNecessaryCases(whenExpression, trace); BindingContext context = trace.getBindingContext();
List<WhenMissingCase> necessaryCases = WhenChecker.getNecessaryCases(whenExpression, context);
if (!necessaryCases.isEmpty()) { if (!necessaryCases.isEmpty()) {
trace.report(NO_ELSE_IN_WHEN.on(whenExpression, necessaryCases)); trace.report(NO_ELSE_IN_WHEN.on(whenExpression, necessaryCases));
} }
@@ -752,7 +753,7 @@ public class ControlFlowInformationProvider {
trace.getType(whenExpression.getSubjectExpression())); trace.getType(whenExpression.getSubjectExpression()));
if (enumClassDescriptor != null) { if (enumClassDescriptor != null) {
List<WhenMissingCase> missingCases = WhenChecker.getEnumMissingCases( List<WhenMissingCase> missingCases = WhenChecker.getEnumMissingCases(
whenExpression, trace, enumClassDescriptor whenExpression, context, enumClassDescriptor
); );
if (!missingCases.isEmpty()) { if (!missingCases.isEmpty()) {
trace.report(NON_EXHAUSTIVE_WHEN.on(whenExpression, missingCases)); trace.report(NON_EXHAUSTIVE_WHEN.on(whenExpression, missingCases));
@@ -802,8 +802,7 @@ public class ControlFlowProcessor {
generateInstructions(condition); generateInstructions(condition);
} }
mark(expression); mark(expression);
boolean conditionIsTrueConstant = CompileTimeConstantUtils.canBeReducedToBooleanConstant(condition, trace, true); if (!CompileTimeConstantUtils.canBeReducedToBooleanConstant(condition, trace.getBindingContext(), true)) {
if (!conditionIsTrueConstant) {
builder.jumpOnFalse(loopInfo.getExitPoint(), expression, builder.getBoundValue(condition)); builder.jumpOnFalse(loopInfo.getExitPoint(), expression, builder.getBoundValue(condition));
} }
else { else {
@@ -52,12 +52,12 @@ val List<WhenMissingCase>.hasUnknown: Boolean
private interface WhenExhaustivenessChecker { private interface WhenExhaustivenessChecker {
fun getMissingCases( fun getMissingCases(
expression: KtWhenExpression, expression: KtWhenExpression,
trace: BindingTrace, context: BindingContext,
subjectDescriptor: ClassDescriptor?, subjectDescriptor: ClassDescriptor?,
nullable: Boolean nullable: Boolean
): List<WhenMissingCase> ): List<WhenMissingCase>
fun isApplicable(subjectType: KotlinType, trace: BindingTrace): Boolean = false fun isApplicable(subjectType: KotlinType): Boolean = false
} }
private object NullMissingCase : WhenMissingCase { private object NullMissingCase : WhenMissingCase {
@@ -65,17 +65,17 @@ private object NullMissingCase : WhenMissingCase {
} }
private object WhenOnNullableExhaustivenessChecker : WhenExhaustivenessChecker { private object WhenOnNullableExhaustivenessChecker : WhenExhaustivenessChecker {
override fun getMissingCases(expression: KtWhenExpression, trace: BindingTrace, subjectDescriptor: ClassDescriptor?, nullable: Boolean) = override fun getMissingCases(expression: KtWhenExpression, context: BindingContext, subjectDescriptor: ClassDescriptor?, nullable: Boolean) =
if (nullable) getNullCaseIfMissing(expression, trace) else listOf() if (nullable) getNullCaseIfMissing(expression, context) else listOf()
override fun isApplicable(subjectType: KotlinType, trace: BindingTrace): Boolean = TypeUtils.isNullableType(subjectType) override fun isApplicable(subjectType: KotlinType): Boolean = TypeUtils.isNullableType(subjectType)
private fun getNullCaseIfMissing(expression: KtWhenExpression, trace: BindingTrace): List<WhenMissingCase> { private fun getNullCaseIfMissing(expression: KtWhenExpression, context: BindingContext): List<WhenMissingCase> {
for (entry in expression.entries) { for (entry in expression.entries) {
for (condition in entry.conditions) { for (condition in entry.conditions) {
if (condition is KtWhenConditionWithExpression) { if (condition is KtWhenConditionWithExpression) {
condition.expression?.let { condition.expression?.let {
val type = trace.bindingContext.getType(it) val type = context.getType(it)
if (type != null && KotlinBuiltIns.isNullableNothing(type)) { if (type != null && KotlinBuiltIns.isNullableNothing(type)) {
return listOf() return listOf()
} }
@@ -94,7 +94,7 @@ private class BooleanMissingCase(val b: Boolean) : WhenMissingCase {
private object WhenOnBooleanExhaustivenessChecker : WhenExhaustivenessChecker { private object WhenOnBooleanExhaustivenessChecker : WhenExhaustivenessChecker {
override fun getMissingCases( override fun getMissingCases(
expression: KtWhenExpression, expression: KtWhenExpression,
trace: BindingTrace, context: BindingContext,
subjectDescriptor: ClassDescriptor?, subjectDescriptor: ClassDescriptor?,
nullable: Boolean nullable: Boolean
): List<WhenMissingCase> { ): List<WhenMissingCase> {
@@ -105,8 +105,8 @@ private object WhenOnBooleanExhaustivenessChecker : WhenExhaustivenessChecker {
for (whenCondition in whenEntry.conditions) { for (whenCondition in whenEntry.conditions) {
if (whenCondition is KtWhenConditionWithExpression) { if (whenCondition is KtWhenConditionWithExpression) {
val whenExpression = whenCondition.expression val whenExpression = whenCondition.expression
if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, trace, true)) containsTrue = true if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, context, true)) containsTrue = true
if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, trace, false)) containsFalse = true if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, context, false)) containsFalse = true
} }
} }
} }
@@ -114,7 +114,7 @@ private object WhenOnBooleanExhaustivenessChecker : WhenExhaustivenessChecker {
(if (!containsFalse) listOf(BooleanMissingCase(false)) else listOf()) (if (!containsFalse) listOf(BooleanMissingCase(false)) else listOf())
} }
override fun isApplicable(subjectType: KotlinType, trace: BindingTrace): Boolean { override fun isApplicable(subjectType: KotlinType): Boolean {
return KotlinBuiltIns.isBoolean(TypeUtils.makeNotNullable(subjectType)) return KotlinBuiltIns.isBoolean(TypeUtils.makeNotNullable(subjectType))
} }
} }
@@ -134,7 +134,7 @@ private abstract class WhenOnClassExhaustivenessChecker : WhenExhaustivenessChec
protected fun getMissingClassCases( protected fun getMissingClassCases(
whenExpression: KtWhenExpression, whenExpression: KtWhenExpression,
memberDescriptors: Set<ClassDescriptor>, memberDescriptors: Set<ClassDescriptor>,
trace: BindingTrace context: BindingContext
): List<WhenMissingCase> { ): List<WhenMissingCase> {
// when on empty enum / sealed is considered non-exhaustive, see test whenOnEmptySealed // when on empty enum / sealed is considered non-exhaustive, see test whenOnEmptySealed
if (memberDescriptors.isEmpty()) return listOf(UnknownMissingCase) if (memberDescriptors.isEmpty()) return listOf(UnknownMissingCase)
@@ -144,7 +144,7 @@ private abstract class WhenOnClassExhaustivenessChecker : WhenExhaustivenessChec
var negated = false var negated = false
var checkedDescriptor: ClassDescriptor? = null var checkedDescriptor: ClassDescriptor? = null
if (condition is KtWhenConditionIsPattern) { if (condition is KtWhenConditionIsPattern) {
val checkedType = trace.get(BindingContext.TYPE, condition.typeReference) val checkedType = context.get(BindingContext.TYPE, condition.typeReference)
if (checkedType != null) { if (checkedType != null) {
checkedDescriptor = TypeUtils.getClassDescriptor(checkedType) checkedDescriptor = TypeUtils.getClassDescriptor(checkedType)
} }
@@ -154,7 +154,7 @@ private abstract class WhenOnClassExhaustivenessChecker : WhenExhaustivenessChec
if (condition.expression != null) { if (condition.expression != null) {
val reference = getReference(condition.expression) val reference = getReference(condition.expression)
if (reference != null) { if (reference != null) {
val target = trace.get(BindingContext.REFERENCE_TARGET, reference) val target = context.get(BindingContext.REFERENCE_TARGET, reference)
if (target is ClassDescriptor) { if (target is ClassDescriptor) {
checkedDescriptor = target checkedDescriptor = target
} }
@@ -188,7 +188,7 @@ private abstract class WhenOnClassExhaustivenessChecker : WhenExhaustivenessChec
private object WhenOnEnumExhaustivenessChecker : WhenOnClassExhaustivenessChecker() { private object WhenOnEnumExhaustivenessChecker : WhenOnClassExhaustivenessChecker() {
override fun getMissingCases( override fun getMissingCases(
expression: KtWhenExpression, expression: KtWhenExpression,
trace: BindingTrace, context: BindingContext,
subjectDescriptor: ClassDescriptor?, subjectDescriptor: ClassDescriptor?,
nullable: Boolean nullable: Boolean
): List<WhenMissingCase> { ): List<WhenMissingCase> {
@@ -198,10 +198,10 @@ private object WhenOnEnumExhaustivenessChecker : WhenOnClassExhaustivenessChecke
.filter { isEnumEntry(it) } .filter { isEnumEntry(it) }
.filterIsInstance<ClassDescriptor>() .filterIsInstance<ClassDescriptor>()
.toSet() .toSet()
return getMissingClassCases(expression, entryDescriptors, trace) return getMissingClassCases(expression, entryDescriptors, context)
} }
override fun isApplicable(subjectType: KotlinType, trace: BindingTrace): Boolean { override fun isApplicable(subjectType: KotlinType): Boolean {
return WhenChecker.getClassDescriptorOfTypeIfEnum(subjectType) != null return WhenChecker.getClassDescriptorOfTypeIfEnum(subjectType) != null
} }
} }
@@ -209,7 +209,7 @@ private object WhenOnEnumExhaustivenessChecker : WhenOnClassExhaustivenessChecke
private object WhenOnSealedExhaustivenessChecker : WhenOnClassExhaustivenessChecker() { private object WhenOnSealedExhaustivenessChecker : WhenOnClassExhaustivenessChecker() {
override fun getMissingCases( override fun getMissingCases(
expression: KtWhenExpression, expression: KtWhenExpression,
trace: BindingTrace, context: BindingContext,
subjectDescriptor: ClassDescriptor?, subjectDescriptor: ClassDescriptor?,
nullable: Boolean nullable: Boolean
): List<WhenMissingCase> { ): List<WhenMissingCase> {
@@ -220,10 +220,10 @@ private object WhenOnSealedExhaustivenessChecker : WhenOnClassExhaustivenessChec
val memberClassDescriptors = LinkedHashSet<ClassDescriptor>() val memberClassDescriptors = LinkedHashSet<ClassDescriptor>()
collectNestedSubclasses(subjectDescriptor!!, subjectDescriptor, memberClassDescriptors) collectNestedSubclasses(subjectDescriptor!!, subjectDescriptor, memberClassDescriptors)
// When on a sealed class without derived members is considered non-exhaustive (see test WhenOnEmptySealed) // When on a sealed class without derived members is considered non-exhaustive (see test WhenOnEmptySealed)
return getMissingClassCases(expression, memberClassDescriptors, trace) return getMissingClassCases(expression, memberClassDescriptors, context)
} }
override fun isApplicable(subjectType: KotlinType, trace: BindingTrace): Boolean { override fun isApplicable(subjectType: KotlinType): Boolean {
return TypeUtils.getClassDescriptor(subjectType)?.modality == Modality.SEALED return TypeUtils.getClassDescriptor(subjectType)?.modality == Modality.SEALED
} }
@@ -251,8 +251,8 @@ object WhenChecker {
WhenOnNullableExhaustivenessChecker) WhenOnNullableExhaustivenessChecker)
@JvmStatic @JvmStatic
fun getNecessaryCases(expression: KtWhenExpression, trace: BindingTrace) = fun getNecessaryCases(expression: KtWhenExpression, context: BindingContext) =
if (expression.isUsedAsExpression(trace.bindingContext)) getMissingCases(expression, trace) if (expression.isUsedAsExpression(context)) getMissingCases(expression, context)
else listOf() else listOf()
@JvmStatic @JvmStatic
@@ -274,9 +274,9 @@ object WhenChecker {
@JvmStatic @JvmStatic
fun getEnumMissingCases( fun getEnumMissingCases(
expression: KtWhenExpression, expression: KtWhenExpression,
trace: BindingTrace, context: BindingContext,
enumClassDescriptor: ClassDescriptor enumClassDescriptor: ClassDescriptor
) = WhenOnEnumExhaustivenessChecker.getMissingCases(expression, trace, enumClassDescriptor, false) ) = WhenOnEnumExhaustivenessChecker.getMissingCases(expression, context, enumClassDescriptor, false)
/** /**
* It's assumed that function is called for a final type. In this case the only possible smart cast is to not nullable type. * It's assumed that function is called for a final type. In this case the only possible smart cast is to not nullable type.
@@ -285,11 +285,12 @@ object WhenChecker {
private fun isNullableTypeWithoutPossibleSmartCast( private fun isNullableTypeWithoutPossibleSmartCast(
expression: KtExpression?, expression: KtExpression?,
type: KotlinType, type: KotlinType,
trace: BindingTrace): Boolean { context: BindingContext
): Boolean {
if (expression == null) return false // Normally should not happen if (expression == null) return false // Normally should not happen
if (!TypeUtils.isNullableType(type)) return false if (!TypeUtils.isNullableType(type)) return false
// We cannot read data flow information here due to lack of inputs (module descriptor is necessary) // We cannot read data flow information here due to lack of inputs (module descriptor is necessary)
if (trace.get(BindingContext.SMARTCAST, expression) != null) { if (context.get(BindingContext.SMARTCAST, expression) != null) {
// We have smart cast from enum or boolean to something // We have smart cast from enum or boolean to something
// Not very nice but we *can* decide it was smart cast to not-null // Not very nice but we *can* decide it was smart cast to not-null
// because both enum and boolean are final // because both enum and boolean are final
@@ -298,17 +299,17 @@ object WhenChecker {
return true return true
} }
private fun getMissingCases(expression: KtWhenExpression, trace: BindingTrace): List<WhenMissingCase> { private fun getMissingCases(expression: KtWhenExpression, context: BindingContext): List<WhenMissingCase> {
val type = whenSubjectType(expression, trace.bindingContext) ?: return listOf(UnknownMissingCase) val type = whenSubjectType(expression, context) ?: return listOf(UnknownMissingCase)
val nullable = !type.isFlexible() && isNullableTypeWithoutPossibleSmartCast(expression.subjectExpression, type, trace) val nullable = !type.isFlexible() && isNullableTypeWithoutPossibleSmartCast(expression.subjectExpression, type, context)
val checkers = exhaustivenessCheckers.filter { it.isApplicable(type, trace) } val checkers = exhaustivenessCheckers.filter { it.isApplicable(type) }
if (checkers.isEmpty()) return listOf(UnknownMissingCase) if (checkers.isEmpty()) return listOf(UnknownMissingCase)
return checkers.map { it.getMissingCases(expression, trace, TypeUtils.getClassDescriptor(type), nullable) }.flatten() return checkers.map { it.getMissingCases(expression, context, TypeUtils.getClassDescriptor(type), nullable) }.flatten()
} }
@JvmStatic @JvmStatic
fun isWhenExhaustive(expression: KtWhenExpression, trace: BindingTrace) = fun isWhenExhaustive(expression: KtWhenExpression, trace: BindingTrace) =
if (getMissingCases(expression, trace).isEmpty()) { if (getMissingCases(expression, trace.bindingContext).isEmpty()) {
trace.record(BindingContext.EXHAUSTIVE_WHEN, expression) trace.record(BindingContext.EXHAUSTIVE_WHEN, expression)
true true
} else { } else {
@@ -316,8 +317,8 @@ object WhenChecker {
} }
@JvmStatic @JvmStatic
fun containsNullCase(expression: KtWhenExpression, trace: BindingTrace) = fun containsNullCase(expression: KtWhenExpression, context: BindingContext) =
WhenOnNullableExhaustivenessChecker.getMissingCases(expression, trace, null, true).isEmpty() WhenOnNullableExhaustivenessChecker.getMissingCases(expression, context, null, true).isEmpty()
@JvmStatic @JvmStatic
fun checkDeprecatedWhenSyntax(trace: BindingTrace, expression: KtWhenExpression) { fun checkDeprecatedWhenSyntax(trace: BindingTrace, expression: KtWhenExpression) {
@@ -109,14 +109,14 @@ public class CompileTimeConstantUtils {
public static boolean canBeReducedToBooleanConstant( public static boolean canBeReducedToBooleanConstant(
@Nullable KtExpression expression, @Nullable KtExpression expression,
@NotNull BindingTrace trace, @NotNull BindingContext context,
@Nullable Boolean expectedValue @Nullable Boolean expectedValue
) { ) {
KtExpression effectiveExpression = KtPsiUtil.deparenthesize(expression); KtExpression effectiveExpression = KtPsiUtil.deparenthesize(expression);
if (effectiveExpression == null) return false; if (effectiveExpression == null) return false;
CompileTimeConstant<?> compileTimeConstant = ConstantExpressionEvaluator.getConstant(effectiveExpression, trace.getBindingContext()); CompileTimeConstant<?> compileTimeConstant = ConstantExpressionEvaluator.getConstant(effectiveExpression, context);
if (!(compileTimeConstant instanceof TypedCompileTimeConstant) || compileTimeConstant.getUsesVariableAsConstant()) return false; if (!(compileTimeConstant instanceof TypedCompileTimeConstant) || compileTimeConstant.getUsesVariableAsConstant()) return false;
ConstantValue constantValue = ((TypedCompileTimeConstant) compileTimeConstant).getConstantValue(); ConstantValue constantValue = ((TypedCompileTimeConstant) compileTimeConstant).getConstantValue();
@@ -93,7 +93,7 @@ public class PatternMatchingTypingVisitor extends ExpressionTypingVisitor {
loopBreakContinuePossible = typeInfo.getJumpOutPossible(); loopBreakContinuePossible = typeInfo.getJumpOutPossible();
subjectType = typeInfo.getType(); subjectType = typeInfo.getType();
assert subjectType != null; assert subjectType != null;
if (TypeUtils.isNullableType(subjectType) && !WhenChecker.containsNullCase(expression, context.trace)) { if (TypeUtils.isNullableType(subjectType) && !WhenChecker.containsNullCase(expression, context.trace.getBindingContext())) {
TemporaryBindingTrace trace = TemporaryBindingTrace.create(context.trace, "Temporary trace for when subject nullability"); TemporaryBindingTrace trace = TemporaryBindingTrace.create(context.trace, "Temporary trace for when subject nullability");
ExpressionTypingContext subjectContext = ExpressionTypingContext subjectContext =
context.replaceExpectedType(TypeUtils.makeNotNullable(subjectType)).replaceBindingTrace(trace); context.replaceExpectedType(TypeUtils.makeNotNullable(subjectType)).replaceBindingTrace(trace);
@@ -124,9 +124,7 @@ class SimplifyBooleanWithConstantsIntention : SelfTargetingOffsetIndependentInte
private fun simplifyExpression(expression: KtExpression) = expression.replaced(toSimplifiedExpression(expression)) private fun simplifyExpression(expression: KtExpression) = expression.replaced(toSimplifiedExpression(expression))
private fun KtExpression.canBeReducedToBooleanConstant(constant: Boolean?): Boolean { private fun KtExpression.canBeReducedToBooleanConstant(constant: Boolean?): Boolean {
val bindingContext = this.analyze() return CompileTimeConstantUtils.canBeReducedToBooleanConstant(this, this.analyze(), constant)
val trace = DelegatingBindingTrace(bindingContext, "trace for constant check")
return CompileTimeConstantUtils.canBeReducedToBooleanConstant(this, trace, constant)
} }
private fun KtExpression.canBeReducedToTrue() = canBeReducedToBooleanConstant(true) private fun KtExpression.canBeReducedToTrue() = canBeReducedToBooleanConstant(true)