J2K: WhenChecker converted to Kotlin

This commit is contained in:
Mikhail Glukhikh
2015-12-14 19:08:30 +03:00
parent e59fed7d9b
commit a99b2a6799
@@ -14,205 +14,187 @@
* limitations under the License. * 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.*;
import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt; import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.resolve.BindingContext; import com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.resolve.BindingTrace; import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.resolve.CompileTimeConstantUtils; import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.resolve.DescriptorUtils; import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt; import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.types.FlexibleTypesKt; import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.types.KotlinType; import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.types.TypeUtils; 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.HashSet
import java.util.Set;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumClass; import org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumClass
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry; import org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
public final class WhenChecker { object WhenChecker {
private 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) { @JvmStatic
return !BindingContextUtilsKt.isUsedAsStatement(expression, trace.getBindingContext()) && !isWhenExhaustive(expression, trace); fun isWhenByEnum(expression: KtWhenExpression, context: BindingContext): Boolean {
return getClassDescriptorOfTypeIfEnum(whenSubjectType(expression, context)) != null
} }
public static boolean isWhenByEnum(@NotNull KtWhenExpression expression, @NotNull BindingContext context) { @JvmStatic
return getClassDescriptorOfTypeIfEnum(whenSubjectType(expression, context)) != null; 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 private fun whenSubjectType(expression: KtWhenExpression, context: BindingContext): KotlinType? =
public static ClassDescriptor getClassDescriptorOfTypeIfEnum(@Nullable KotlinType type) { expression.subjectExpression?.let { context.getType(it) } ?: null
if (type == null) return null;
ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(type);
if (classDescriptor == null) return null;
if (classDescriptor.getKind() != ClassKind.ENUM_CLASS) return null;
return classDescriptor; private fun isWhenOnBooleanExhaustive(expression: KtWhenExpression, trace: BindingTrace): Boolean {
}
@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) {
// It's assumed (and not checked) that expression is of the boolean type // It's assumed (and not checked) that expression is of the boolean type
boolean containsFalse = false; var containsFalse = false
boolean containsTrue = false; var containsTrue = false
for (KtWhenEntry whenEntry: expression.getEntries()) { for (whenEntry in expression.entries) {
for (KtWhenCondition whenCondition : whenEntry.getConditions()) { for (whenCondition in whenEntry.conditions) {
if (whenCondition instanceof KtWhenConditionWithExpression) { if (whenCondition is KtWhenConditionWithExpression) {
KtExpression whenExpression = ((KtWhenConditionWithExpression) whenCondition).getExpression(); val whenExpression = whenCondition.expression
if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, trace, true)) containsTrue = true; if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, trace, true)) containsTrue = true
if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, trace, false)) containsFalse = true; if (CompileTimeConstantUtils.canBeReducedToBooleanConstant(whenExpression, trace, false)) containsFalse = true
} }
} }
} }
return containsFalse && containsTrue; return containsFalse && containsTrue
} }
public static boolean isWhenOnEnumExhaustive( @JvmStatic
@NotNull KtWhenExpression expression, fun isWhenOnEnumExhaustive(
@NotNull BindingTrace trace, expression: KtWhenExpression,
@NotNull ClassDescriptor enumClassDescriptor trace: BindingTrace,
) { enumClassDescriptor: ClassDescriptor): Boolean {
assert isEnumClass(enumClassDescriptor) : assert(isEnumClass(enumClassDescriptor)) { "isWhenOnEnumExhaustive should be called with an enum class descriptor" }
"isWhenOnEnumExhaustive should be called with an enum class descriptor"; val entryDescriptors =
Set<ClassDescriptor> entryDescriptors = new HashSet<ClassDescriptor>(); DescriptorUtils.getAllDescriptors(enumClassDescriptor.unsubstitutedInnerClassesScope)
for (DeclarationDescriptor descriptor : DescriptorUtils.getAllDescriptors(enumClassDescriptor.getUnsubstitutedInnerClassesScope())) { .filter { isEnumEntry(it) }
if (isEnumEntry(descriptor)) { .filterIsInstance<ClassDescriptor>()
entryDescriptors.add((ClassDescriptor) descriptor); .toSet()
} return !entryDescriptors.isEmpty() && containsAllClassCases(expression, entryDescriptors, trace)
}
return !entryDescriptors.isEmpty() && containsAllClassCases(expression, entryDescriptors, trace);
} }
private static void collectNestedSubclasses( private fun collectNestedSubclasses(
@NotNull ClassDescriptor baseDescriptor, baseDescriptor: ClassDescriptor,
@NotNull ClassDescriptor currentDescriptor, currentDescriptor: ClassDescriptor,
@NotNull Set<ClassDescriptor> subclasses subclasses: MutableSet<ClassDescriptor>) {
) { for (descriptor in DescriptorUtils.getAllDescriptors(currentDescriptor.unsubstitutedInnerClassesScope)) {
for (DeclarationDescriptor descriptor : DescriptorUtils.getAllDescriptors(currentDescriptor.getUnsubstitutedInnerClassesScope())) { if (descriptor is ClassDescriptor) {
if (descriptor instanceof ClassDescriptor) { if (DescriptorUtils.isDirectSubclass(descriptor, baseDescriptor)) {
ClassDescriptor memberClassDescriptor = (ClassDescriptor) descriptor; subclasses.add(descriptor)
if (DescriptorUtils.isDirectSubclass(memberClassDescriptor, baseDescriptor)) {
subclasses.add(memberClassDescriptor);
} }
collectNestedSubclasses(baseDescriptor, memberClassDescriptor, subclasses); collectNestedSubclasses(baseDescriptor, descriptor, subclasses)
} }
} }
} }
private static boolean isWhenOnSealedClassExhaustive( private fun isWhenOnSealedClassExhaustive(
@NotNull KtWhenExpression expression, expression: KtWhenExpression,
@NotNull BindingTrace trace, trace: BindingTrace,
@NotNull ClassDescriptor classDescriptor classDescriptor: ClassDescriptor): Boolean {
) { assert(classDescriptor.modality === Modality.SEALED) { "isWhenOnSealedClassExhaustive should be called with a sealed class descriptor" }
assert classDescriptor.getModality() == Modality.SEALED : val memberClassDescriptors = HashSet<ClassDescriptor>()
"isWhenOnSealedClassExhaustive should be called with a sealed class descriptor"; collectNestedSubclasses(classDescriptor, classDescriptor, memberClassDescriptors)
Set<ClassDescriptor> memberClassDescriptors = new HashSet<ClassDescriptor>();
collectNestedSubclasses(classDescriptor, classDescriptor, 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 !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. * 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 * @return true if type is nullable, and cannot be smart casted
*/ */
private static boolean isNullableTypeWithoutPossibleSmartCast( private fun isNullableTypeWithoutPossibleSmartCast(
@Nullable KtExpression expression, expression: KtExpression?,
@NotNull KotlinType type, type: KotlinType,
@NotNull BindingContext context 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 (context.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
return false; return false
} }
return true; return true
} }
public static boolean isWhenExhaustive(@NotNull KtWhenExpression expression, @NotNull BindingTrace trace) { @JvmStatic
KotlinType type = whenSubjectType(expression, trace.getBindingContext()); fun isWhenExhaustive(expression: KtWhenExpression, trace: BindingTrace): Boolean {
if (type == null) return false; val type = whenSubjectType(expression, trace.bindingContext) ?: return false
ClassDescriptor enumClassDescriptor = getClassDescriptorOfTypeIfEnum(type); val enumClassDescriptor = getClassDescriptorOfTypeIfEnum(type)
boolean exhaustive; val exhaustive: Boolean
if (enumClassDescriptor == null) { if (enumClassDescriptor == null) {
if (KotlinBuiltIns.isBoolean(TypeUtils.makeNotNullable(type))) { if (KotlinBuiltIns.isBoolean(TypeUtils.makeNotNullable(type))) {
exhaustive = isWhenOnBooleanExhaustive(expression, trace); exhaustive = isWhenOnBooleanExhaustive(expression, trace)
} }
else { else {
ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(type); val classDescriptor = TypeUtils.getClassDescriptor(type)
exhaustive = (classDescriptor != null exhaustive = (classDescriptor != null &&
&& classDescriptor.getModality() == Modality.SEALED classDescriptor.modality === Modality.SEALED &&
&& isWhenOnSealedClassExhaustive(expression, trace, classDescriptor)); isWhenOnSealedClassExhaustive(expression, trace, classDescriptor))
} }
} }
else { else {
exhaustive = isWhenOnEnumExhaustive(expression, trace, enumClassDescriptor); exhaustive = isWhenOnEnumExhaustive(expression, trace, enumClassDescriptor)
} }
if (exhaustive) { if (exhaustive) {
if (// Flexible (nullable) enum types are also counted as exhaustive // Flexible (nullable) enum types are also counted as exhaustive
(enumClassDescriptor != null && FlexibleTypesKt.isFlexible(type)) if ((enumClassDescriptor != null && type.isFlexible()) ||
|| containsNullCase(expression, trace) containsNullCase(expression, trace) ||
|| !isNullableTypeWithoutPossibleSmartCast(expression.getSubjectExpression(), type, trace.getBindingContext())) { !isNullableTypeWithoutPossibleSmartCast(expression.subjectExpression, type, trace.bindingContext)) {
trace.record(BindingContext.EXHAUSTIVE_WHEN, expression); trace.record(BindingContext.EXHAUSTIVE_WHEN, expression)
return true; return true
} }
} }
return false; return false
} }
private static boolean containsAllClassCases( private fun containsAllClassCases(
@NotNull KtWhenExpression whenExpression, whenExpression: KtWhenExpression,
@NotNull Set<ClassDescriptor> memberDescriptors, memberDescriptors: Set<ClassDescriptor>,
@NotNull BindingTrace trace trace: BindingTrace): Boolean {
) { val checkedDescriptors = HashSet<ClassDescriptor>()
Set<ClassDescriptor> checkedDescriptors = new HashSet<ClassDescriptor>(); for (whenEntry in whenExpression.entries) {
for (KtWhenEntry whenEntry : whenExpression.getEntries()) { for (condition in whenEntry.conditions) {
for (KtWhenCondition condition : whenEntry.getConditions()) { var negated = false
boolean negated = false; var checkedDescriptor: ClassDescriptor? = null
ClassDescriptor checkedDescriptor = null; if (condition is KtWhenConditionIsPattern) {
if (condition instanceof KtWhenConditionIsPattern) { val checkedType = trace.get(BindingContext.TYPE, condition.typeReference)
KtWhenConditionIsPattern conditionIsPattern = (KtWhenConditionIsPattern) condition;
KotlinType checkedType = trace.get(BindingContext.TYPE, conditionIsPattern.getTypeReference());
if (checkedType != null) { if (checkedType != null) {
checkedDescriptor = TypeUtils.getClassDescriptor(checkedType); checkedDescriptor = TypeUtils.getClassDescriptor(checkedType)
} }
negated = conditionIsPattern.isNegated(); negated = condition.isNegated
} }
else if (condition instanceof KtWhenConditionWithExpression) { else if (condition is KtWhenConditionWithExpression) {
KtWhenConditionWithExpression conditionWithExpression = (KtWhenConditionWithExpression) condition; if (condition.expression != null) {
if (conditionWithExpression.getExpression() != null) { val reference = getReference(condition.expression)
KtSimpleNameExpression reference = getReference(conditionWithExpression.getExpression());
if (reference != null) { if (reference != null) {
DeclarationDescriptor target = trace.get(BindingContext.REFERENCE_TARGET, reference); val target = trace.get(BindingContext.REFERENCE_TARGET, reference)
if (target instanceof ClassDescriptor) { if (target is ClassDescriptor) {
checkedDescriptor = (ClassDescriptor) target; checkedDescriptor = target
} }
} }
} }
@@ -220,72 +202,72 @@ public final class WhenChecker {
// Checks are important only for nested subclasses of the sealed class // Checks are important only for nested subclasses of the sealed class
// In additional, check without "is" is important only for objects // In additional, check without "is" is important only for objects
if (checkedDescriptor == null if (checkedDescriptor == null ||
|| !memberDescriptors.contains(checkedDescriptor) !memberDescriptors.contains(checkedDescriptor) ||
|| (condition instanceof KtWhenConditionWithExpression (condition is KtWhenConditionWithExpression &&
&& !DescriptorUtils.isObject(checkedDescriptor) !DescriptorUtils.isObject(checkedDescriptor) &&
&& !DescriptorUtils.isEnumEntry(checkedDescriptor))) { !DescriptorUtils.isEnumEntry(checkedDescriptor))) {
continue; continue
} }
if (negated) { if (negated) {
if (checkedDescriptors.contains(checkedDescriptor)) return true; // all members are already there if (checkedDescriptors.contains(checkedDescriptor)) return true // all members are already there
checkedDescriptors.addAll(memberDescriptors); checkedDescriptors.addAll(memberDescriptors)
checkedDescriptors.remove(checkedDescriptor); checkedDescriptors.remove(checkedDescriptor)
} }
else { 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) { @JvmStatic
for (KtWhenEntry entry : expression.getEntries()) { fun containsNullCase(expression: KtWhenExpression, trace: BindingTrace): Boolean {
for (KtWhenCondition condition : entry.getConditions()) { for (entry in expression.entries) {
if (condition instanceof KtWhenConditionWithExpression) { for (condition in entry.conditions) {
KtWhenConditionWithExpression conditionWithExpression = (KtWhenConditionWithExpression) condition; if (condition is KtWhenConditionWithExpression) {
if (conditionWithExpression.getExpression() != null) { condition.expression?.let {
KotlinType type = trace.getBindingContext().getType(conditionWithExpression.getExpression()); val type = trace.bindingContext.getType(it)
if (type != null && KotlinBuiltIns.isNothingOrNullableNothing(type)) { if (type != null && KotlinBuiltIns.isNothingOrNullableNothing(type)) {
return true; return true
} }
} }
} }
} }
} }
return false; return false
} }
@Nullable private fun getReference(expression: KtExpression?): KtSimpleNameExpression? {
private static KtSimpleNameExpression getReference(@Nullable KtExpression expression) { if (expression is KtSimpleNameExpression) {
if (expression == null) { return expression
return null;
} }
if (expression instanceof KtSimpleNameExpression) { if (expression is KtQualifiedExpression) {
return (KtSimpleNameExpression) expression; return getReference(expression.selectorExpression)
} }
if (expression instanceof KtQualifiedExpression) { return null
return getReference(((KtQualifiedExpression) expression).getSelectorExpression());
}
return null;
} }
public static void checkDeprecatedWhenSyntax(@NotNull BindingTrace trace, @NotNull KtWhenExpression expression) { @JvmStatic
if (expression.getSubjectExpression() != null) return; fun checkDeprecatedWhenSyntax(trace: BindingTrace, expression: KtWhenExpression) {
if (expression.subjectExpression != null) return
for (KtWhenEntry entry : expression.getEntries()) { for (entry in expression.entries) {
if (entry.isElse()) continue; if (entry.isElse) continue
for (PsiElement child = entry.getFirstChild(); child != null; child = child.getNextSibling()) { var child: PsiElement? = entry.firstChild
if (child.getNode().getElementType() == KtTokens.COMMA) { while (child != null) {
trace.report(Errors.COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT.on(child)); 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) { @JvmStatic
KtPsiUtilKt.checkReservedPrefixWord(trace, expression.getWhenKeyword(), "sealed", TokenSet.EMPTY, "sealed when"); fun checkReservedPrefix(trace: BindingTrace, expression: KtWhenExpression) {
checkReservedPrefixWord(trace, expression.whenKeyword, "sealed", TokenSet.EMPTY, "sealed when")
} }
} }