From a99b2a6799bea03f1448469bff2939730a8ba99a Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Mon, 14 Dec 2015 19:08:30 +0300 Subject: [PATCH] J2K: WhenChecker converted to Kotlin --- .../org/jetbrains/kotlin/cfg/WhenChecker.kt | 342 +++++++++--------- 1 file changed, 162 insertions(+), 180 deletions(-) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt index fe35ec8b0c9..f69ccc3e6da 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt @@ -14,205 +14,187 @@ * limitations under the License. */ -package org.jetbrains.kotlin.cfg; +package org.jetbrains.kotlin.cfg -import com.intellij.psi.PsiElement; -import com.intellij.psi.tree.TokenSet; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.kotlin.builtins.KotlinBuiltIns; -import org.jetbrains.kotlin.descriptors.ClassDescriptor; -import org.jetbrains.kotlin.descriptors.ClassKind; -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; -import org.jetbrains.kotlin.descriptors.Modality; -import org.jetbrains.kotlin.diagnostics.Errors; -import org.jetbrains.kotlin.lexer.KtTokens; import org.jetbrains.kotlin.psi.*; -import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt; -import org.jetbrains.kotlin.resolve.BindingContext; -import org.jetbrains.kotlin.resolve.BindingTrace; -import org.jetbrains.kotlin.resolve.CompileTimeConstantUtils; -import org.jetbrains.kotlin.resolve.DescriptorUtils; -import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt; -import org.jetbrains.kotlin.types.FlexibleTypesKt; -import org.jetbrains.kotlin.types.KotlinType; -import org.jetbrains.kotlin.types.TypeUtils; +import com.intellij.psi.PsiElement +import com.intellij.psi.tree.TokenSet +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.psiUtil.checkReservedPrefixWord +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.BindingTrace +import org.jetbrains.kotlin.resolve.CompileTimeConstantUtils +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.types.* +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeUtils -import java.util.HashSet; -import java.util.Set; +import java.util.HashSet -import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumClass; -import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry; +import org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumClass +import org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry +import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression -public final class WhenChecker { - private WhenChecker() { +object WhenChecker { + + @JvmStatic + fun mustHaveElse(expression: KtWhenExpression, trace: BindingTrace): Boolean { + return expression.isUsedAsExpression(trace.bindingContext) && !isWhenExhaustive(expression, trace) } - public static boolean mustHaveElse(@NotNull KtWhenExpression expression, @NotNull BindingTrace trace) { - return !BindingContextUtilsKt.isUsedAsStatement(expression, trace.getBindingContext()) && !isWhenExhaustive(expression, trace); + @JvmStatic + fun isWhenByEnum(expression: KtWhenExpression, context: BindingContext): Boolean { + return getClassDescriptorOfTypeIfEnum(whenSubjectType(expression, context)) != null } - public static boolean isWhenByEnum(@NotNull KtWhenExpression expression, @NotNull BindingContext context) { - return getClassDescriptorOfTypeIfEnum(whenSubjectType(expression, context)) != null; + @JvmStatic + fun getClassDescriptorOfTypeIfEnum(type: KotlinType?): ClassDescriptor? { + if (type == null) return null + val classDescriptor = TypeUtils.getClassDescriptor(type) ?: return null + if (classDescriptor.kind != ClassKind.ENUM_CLASS) return null + + return classDescriptor } - @Nullable - public static ClassDescriptor getClassDescriptorOfTypeIfEnum(@Nullable KotlinType type) { - if (type == null) return null; - ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(type); - if (classDescriptor == null) return null; - if (classDescriptor.getKind() != ClassKind.ENUM_CLASS) return null; + private fun whenSubjectType(expression: KtWhenExpression, context: BindingContext): KotlinType? = + expression.subjectExpression?.let { context.getType(it) } ?: null - return classDescriptor; - } - - @Nullable - private static KotlinType whenSubjectType(@NotNull KtWhenExpression expression, @NotNull BindingContext context) { - KtExpression subjectExpression = expression.getSubjectExpression(); - return subjectExpression == null ? null : context.getType(subjectExpression); - } - - private static boolean isWhenOnBooleanExhaustive(@NotNull KtWhenExpression expression, @NotNull BindingTrace trace) { + private fun isWhenOnBooleanExhaustive(expression: KtWhenExpression, trace: BindingTrace): Boolean { // It's assumed (and not checked) that expression is of the boolean type - boolean containsFalse = false; - boolean containsTrue = false; - for (KtWhenEntry whenEntry: expression.getEntries()) { - for (KtWhenCondition whenCondition : whenEntry.getConditions()) { - if (whenCondition instanceof KtWhenConditionWithExpression) { - KtExpression whenExpression = ((KtWhenConditionWithExpression) whenCondition).getExpression(); - if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, trace, true)) containsTrue = true; - if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, trace, false)) containsFalse = true; + var containsFalse = false + var containsTrue = false + for (whenEntry in expression.entries) { + for (whenCondition in whenEntry.conditions) { + if (whenCondition is KtWhenConditionWithExpression) { + val whenExpression = whenCondition.expression + if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, trace, true)) containsTrue = true + if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, trace, false)) containsFalse = true } } } - return containsFalse && containsTrue; + return containsFalse && containsTrue } - public static boolean isWhenOnEnumExhaustive( - @NotNull KtWhenExpression expression, - @NotNull BindingTrace trace, - @NotNull ClassDescriptor enumClassDescriptor - ) { - assert isEnumClass(enumClassDescriptor) : - "isWhenOnEnumExhaustive should be called with an enum class descriptor"; - Set entryDescriptors = new HashSet(); - for (DeclarationDescriptor descriptor : DescriptorUtils.getAllDescriptors(enumClassDescriptor.getUnsubstitutedInnerClassesScope())) { - if (isEnumEntry(descriptor)) { - entryDescriptors.add((ClassDescriptor) descriptor); - } - } - return !entryDescriptors.isEmpty() && containsAllClassCases(expression, entryDescriptors, trace); + @JvmStatic + fun isWhenOnEnumExhaustive( + expression: KtWhenExpression, + trace: BindingTrace, + enumClassDescriptor: ClassDescriptor): Boolean { + assert(isEnumClass(enumClassDescriptor)) { "isWhenOnEnumExhaustive should be called with an enum class descriptor" } + val entryDescriptors = + DescriptorUtils.getAllDescriptors(enumClassDescriptor.unsubstitutedInnerClassesScope) + .filter { isEnumEntry(it) } + .filterIsInstance() + .toSet() + return !entryDescriptors.isEmpty() && containsAllClassCases(expression, entryDescriptors, trace) } - private static void collectNestedSubclasses( - @NotNull ClassDescriptor baseDescriptor, - @NotNull ClassDescriptor currentDescriptor, - @NotNull Set subclasses - ) { - for (DeclarationDescriptor descriptor : DescriptorUtils.getAllDescriptors(currentDescriptor.getUnsubstitutedInnerClassesScope())) { - if (descriptor instanceof ClassDescriptor) { - ClassDescriptor memberClassDescriptor = (ClassDescriptor) descriptor; - if (DescriptorUtils.isDirectSubclass(memberClassDescriptor, baseDescriptor)) { - subclasses.add(memberClassDescriptor); + private fun collectNestedSubclasses( + baseDescriptor: ClassDescriptor, + currentDescriptor: ClassDescriptor, + subclasses: MutableSet) { + for (descriptor in DescriptorUtils.getAllDescriptors(currentDescriptor.unsubstitutedInnerClassesScope)) { + if (descriptor is ClassDescriptor) { + if (DescriptorUtils.isDirectSubclass(descriptor, baseDescriptor)) { + subclasses.add(descriptor) } - collectNestedSubclasses(baseDescriptor, memberClassDescriptor, subclasses); + collectNestedSubclasses(baseDescriptor, descriptor, subclasses) } } } - private static boolean isWhenOnSealedClassExhaustive( - @NotNull KtWhenExpression expression, - @NotNull BindingTrace trace, - @NotNull ClassDescriptor classDescriptor - ) { - assert classDescriptor.getModality() == Modality.SEALED : - "isWhenOnSealedClassExhaustive should be called with a sealed class descriptor"; - Set memberClassDescriptors = new HashSet(); - collectNestedSubclasses(classDescriptor, classDescriptor, memberClassDescriptors); + private fun isWhenOnSealedClassExhaustive( + expression: KtWhenExpression, + trace: BindingTrace, + classDescriptor: ClassDescriptor): Boolean { + assert(classDescriptor.modality === Modality.SEALED) { "isWhenOnSealedClassExhaustive should be called with a sealed class descriptor" } + val memberClassDescriptors = HashSet() + collectNestedSubclasses(classDescriptor, classDescriptor, memberClassDescriptors) // When on a sealed class without derived members is considered non-exhaustive (see test WhenOnEmptySealed) - return !memberClassDescriptors.isEmpty() && containsAllClassCases(expression, memberClassDescriptors, trace); + return !memberClassDescriptors.isEmpty() && containsAllClassCases(expression, memberClassDescriptors, trace) } /** * It's assumed that function is called for a final type. In this case the only possible smart cast is to not nullable type. * @return true if type is nullable, and cannot be smart casted */ - private static boolean isNullableTypeWithoutPossibleSmartCast( - @Nullable KtExpression expression, - @NotNull KotlinType type, - @NotNull BindingContext context - ) { - if (expression == null) return false; // Normally should not happen - if (!TypeUtils.isNullableType(type)) return false; + private fun isNullableTypeWithoutPossibleSmartCast( + expression: KtExpression?, + type: KotlinType, + context: BindingContext): Boolean { + if (expression == null) return false // Normally should not happen + if (!TypeUtils.isNullableType(type)) return false // We cannot read data flow information here due to lack of inputs (module descriptor is necessary) if (context.get(BindingContext.SMARTCAST, expression) != null) { // We have smart cast from enum or boolean to something // Not very nice but we *can* decide it was smart cast to not-null // because both enum and boolean are final - return false; + return false } - return true; + return true } - public static boolean isWhenExhaustive(@NotNull KtWhenExpression expression, @NotNull BindingTrace trace) { - KotlinType type = whenSubjectType(expression, trace.getBindingContext()); - if (type == null) return false; - ClassDescriptor enumClassDescriptor = getClassDescriptorOfTypeIfEnum(type); + @JvmStatic + fun isWhenExhaustive(expression: KtWhenExpression, trace: BindingTrace): Boolean { + val type = whenSubjectType(expression, trace.bindingContext) ?: return false + val enumClassDescriptor = getClassDescriptorOfTypeIfEnum(type) - boolean exhaustive; + val exhaustive: Boolean if (enumClassDescriptor == null) { if (KotlinBuiltIns.isBoolean(TypeUtils.makeNotNullable(type))) { - exhaustive = isWhenOnBooleanExhaustive(expression, trace); + exhaustive = isWhenOnBooleanExhaustive(expression, trace) } else { - ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(type); - exhaustive = (classDescriptor != null - && classDescriptor.getModality() == Modality.SEALED - && isWhenOnSealedClassExhaustive(expression, trace, classDescriptor)); + val classDescriptor = TypeUtils.getClassDescriptor(type) + exhaustive = (classDescriptor != null && + classDescriptor.modality === Modality.SEALED && + isWhenOnSealedClassExhaustive(expression, trace, classDescriptor)) } } else { - exhaustive = isWhenOnEnumExhaustive(expression, trace, enumClassDescriptor); + exhaustive = isWhenOnEnumExhaustive(expression, trace, enumClassDescriptor) } if (exhaustive) { - if (// Flexible (nullable) enum types are also counted as exhaustive - (enumClassDescriptor != null && FlexibleTypesKt.isFlexible(type)) - || containsNullCase(expression, trace) - || !isNullableTypeWithoutPossibleSmartCast(expression.getSubjectExpression(), type, trace.getBindingContext())) { + // Flexible (nullable) enum types are also counted as exhaustive + if ((enumClassDescriptor != null && type.isFlexible()) || + containsNullCase(expression, trace) || + !isNullableTypeWithoutPossibleSmartCast(expression.subjectExpression, type, trace.bindingContext)) { - trace.record(BindingContext.EXHAUSTIVE_WHEN, expression); - return true; + trace.record(BindingContext.EXHAUSTIVE_WHEN, expression) + return true } } - return false; + return false } - private static boolean containsAllClassCases( - @NotNull KtWhenExpression whenExpression, - @NotNull Set memberDescriptors, - @NotNull BindingTrace trace - ) { - Set checkedDescriptors = new HashSet(); - for (KtWhenEntry whenEntry : whenExpression.getEntries()) { - for (KtWhenCondition condition : whenEntry.getConditions()) { - boolean negated = false; - ClassDescriptor checkedDescriptor = null; - if (condition instanceof KtWhenConditionIsPattern) { - KtWhenConditionIsPattern conditionIsPattern = (KtWhenConditionIsPattern) condition; - KotlinType checkedType = trace.get(BindingContext.TYPE, conditionIsPattern.getTypeReference()); + private fun containsAllClassCases( + whenExpression: KtWhenExpression, + memberDescriptors: Set, + trace: BindingTrace): Boolean { + val checkedDescriptors = HashSet() + for (whenEntry in whenExpression.entries) { + for (condition in whenEntry.conditions) { + var negated = false + var checkedDescriptor: ClassDescriptor? = null + if (condition is KtWhenConditionIsPattern) { + val checkedType = trace.get(BindingContext.TYPE, condition.typeReference) if (checkedType != null) { - checkedDescriptor = TypeUtils.getClassDescriptor(checkedType); + checkedDescriptor = TypeUtils.getClassDescriptor(checkedType) } - negated = conditionIsPattern.isNegated(); + negated = condition.isNegated } - else if (condition instanceof KtWhenConditionWithExpression) { - KtWhenConditionWithExpression conditionWithExpression = (KtWhenConditionWithExpression) condition; - if (conditionWithExpression.getExpression() != null) { - KtSimpleNameExpression reference = getReference(conditionWithExpression.getExpression()); + else if (condition is KtWhenConditionWithExpression) { + if (condition.expression != null) { + val reference = getReference(condition.expression) if (reference != null) { - DeclarationDescriptor target = trace.get(BindingContext.REFERENCE_TARGET, reference); - if (target instanceof ClassDescriptor) { - checkedDescriptor = (ClassDescriptor) target; + val target = trace.get(BindingContext.REFERENCE_TARGET, reference) + if (target is ClassDescriptor) { + checkedDescriptor = target } } } @@ -220,72 +202,72 @@ public final class WhenChecker { // Checks are important only for nested subclasses of the sealed class // In additional, check without "is" is important only for objects - if (checkedDescriptor == null - || !memberDescriptors.contains(checkedDescriptor) - || (condition instanceof KtWhenConditionWithExpression - && !DescriptorUtils.isObject(checkedDescriptor) - && !DescriptorUtils.isEnumEntry(checkedDescriptor))) { - continue; + if (checkedDescriptor == null || + !memberDescriptors.contains(checkedDescriptor) || + (condition is KtWhenConditionWithExpression && + !DescriptorUtils.isObject(checkedDescriptor) && + !DescriptorUtils.isEnumEntry(checkedDescriptor))) { + continue } if (negated) { - if (checkedDescriptors.contains(checkedDescriptor)) return true; // all members are already there - checkedDescriptors.addAll(memberDescriptors); - checkedDescriptors.remove(checkedDescriptor); + if (checkedDescriptors.contains(checkedDescriptor)) return true // all members are already there + checkedDescriptors.addAll(memberDescriptors) + checkedDescriptors.remove(checkedDescriptor) } else { - checkedDescriptors.add(checkedDescriptor); + checkedDescriptors.add(checkedDescriptor) } } } - return checkedDescriptors.containsAll(memberDescriptors); + return checkedDescriptors.containsAll(memberDescriptors) } - public static boolean containsNullCase(@NotNull KtWhenExpression expression, @NotNull BindingTrace trace) { - for (KtWhenEntry entry : expression.getEntries()) { - for (KtWhenCondition condition : entry.getConditions()) { - if (condition instanceof KtWhenConditionWithExpression) { - KtWhenConditionWithExpression conditionWithExpression = (KtWhenConditionWithExpression) condition; - if (conditionWithExpression.getExpression() != null) { - KotlinType type = trace.getBindingContext().getType(conditionWithExpression.getExpression()); + @JvmStatic + fun containsNullCase(expression: KtWhenExpression, trace: BindingTrace): Boolean { + for (entry in expression.entries) { + for (condition in entry.conditions) { + if (condition is KtWhenConditionWithExpression) { + condition.expression?.let { + val type = trace.bindingContext.getType(it) if (type != null && KotlinBuiltIns.isNothingOrNullableNothing(type)) { - return true; + return true } } } } } - return false; + return false } - @Nullable - private static KtSimpleNameExpression getReference(@Nullable KtExpression expression) { - if (expression == null) { - return null; + private fun getReference(expression: KtExpression?): KtSimpleNameExpression? { + if (expression is KtSimpleNameExpression) { + return expression } - if (expression instanceof KtSimpleNameExpression) { - return (KtSimpleNameExpression) expression; + if (expression is KtQualifiedExpression) { + return getReference(expression.selectorExpression) } - if (expression instanceof KtQualifiedExpression) { - return getReference(((KtQualifiedExpression) expression).getSelectorExpression()); - } - return null; + return null } - public static void checkDeprecatedWhenSyntax(@NotNull BindingTrace trace, @NotNull KtWhenExpression expression) { - if (expression.getSubjectExpression() != null) return; + @JvmStatic + fun checkDeprecatedWhenSyntax(trace: BindingTrace, expression: KtWhenExpression) { + if (expression.subjectExpression != null) return - for (KtWhenEntry entry : expression.getEntries()) { - if (entry.isElse()) continue; - for (PsiElement child = entry.getFirstChild(); child != null; child = child.getNextSibling()) { - if (child.getNode().getElementType() == KtTokens.COMMA) { - trace.report(Errors.COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT.on(child)); + for (entry in expression.entries) { + if (entry.isElse) continue + var child: PsiElement? = entry.firstChild + while (child != null) { + if (child.node.elementType === KtTokens.COMMA) { + trace.report(Errors.COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT.on(child)) } - if (child.getNode().getElementType() == KtTokens.ARROW) break; + if (child.node.elementType === KtTokens.ARROW) break + child = child.nextSibling } } } - public static void checkReservedPrefix(@NotNull BindingTrace trace, @NotNull KtWhenExpression expression) { - KtPsiUtilKt.checkReservedPrefixWord(trace, expression.getWhenKeyword(), "sealed", TokenSet.EMPTY, "sealed when"); + @JvmStatic + fun checkReservedPrefix(trace: BindingTrace, expression: KtWhenExpression) { + checkReservedPrefixWord(trace, expression.whenKeyword, "sealed", TokenSet.EMPTY, "sealed when") } }