Unify WhenMissingCase from FIR and FE1.0

This commit is contained in:
Ilya Kirillov
2021-02-24 12:23:19 +01:00
parent f4371c670e
commit f19a9af7b3
7 changed files with 47 additions and 50 deletions
@@ -22,7 +22,9 @@ 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.diagnostics.WhenMissingCase
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.checkReservedPrefixWord
import org.jetbrains.kotlin.resolve.BindingContext
@@ -40,19 +42,9 @@ import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import java.util.*
sealed class WhenMissingCase {
abstract val branchConditionText: String
}
// Always must be first in the list
object UnknownMissingCase : WhenMissingCase() {
override fun toString() = "unknown"
override val branchConditionText = "else"
}
val List<WhenMissingCase>.hasUnknown: Boolean
get() = firstOrNull() == UnknownMissingCase
get() = firstOrNull() == WhenMissingCase.Unknown
private interface WhenExhaustivenessChecker {
fun getMissingCases(
@@ -65,12 +57,6 @@ private interface WhenExhaustivenessChecker {
fun isApplicable(subjectType: KotlinType): Boolean = false
}
object NullMissingCase : WhenMissingCase() {
override fun toString() = branchConditionText
override val branchConditionText = "null"
}
// It's not a regular exhaustiveness checker, invoke it only inside other checkers
private object WhenOnNullableExhaustivenessChecker /* : WhenExhaustivenessChecker*/ {
fun getMissingCases(expression: KtWhenExpression, context: BindingContext, nullable: Boolean) =
@@ -89,15 +75,10 @@ private object WhenOnNullableExhaustivenessChecker /* : WhenExhaustivenessChecke
}
}
}
return listOf(NullMissingCase)
return listOf(WhenMissingCase.NullIsMissing)
}
}
class BooleanMissingCase(val b: Boolean) : WhenMissingCase() {
override fun toString() = branchConditionText
override val branchConditionText = b.toString()
}
private object WhenOnBooleanExhaustivenessChecker : WhenExhaustivenessChecker {
override fun getMissingCases(
@@ -118,8 +99,8 @@ private object WhenOnBooleanExhaustivenessChecker : WhenExhaustivenessChecker {
}
}
}
return (if (!containsTrue) listOf(BooleanMissingCase(true)) else listOf()) +
(if (!containsFalse) listOf(BooleanMissingCase(false)) else listOf()) +
return (if (!containsTrue) listOf(WhenMissingCase.BooleanIsMissing.True) else listOf()) +
(if (!containsFalse) listOf(WhenMissingCase.BooleanIsMissing.False) else listOf()) +
WhenOnNullableExhaustivenessChecker.getMissingCases(expression, context, nullable)
}
@@ -128,15 +109,6 @@ private object WhenOnBooleanExhaustivenessChecker : WhenExhaustivenessChecker {
}
}
class ClassMissingCase(val descriptor: ClassDescriptor) : WhenMissingCase() {
val classIsSingleton get() = descriptor.kind.isSingleton
val classFqName get() = DescriptorUtils.getFqNameFromTopLevelClass(descriptor)
override fun toString() = descriptor.name.identifier.let { if (classIsSingleton) it else "is $it" }
override val branchConditionText = classFqName.asString().let { if (classIsSingleton) it else "is $it" }
}
internal abstract class WhenOnClassExhaustivenessChecker : WhenExhaustivenessChecker {
private fun getReference(expression: KtExpression?): KtSimpleNameExpression? =
@@ -195,7 +167,7 @@ internal abstract class WhenOnClassExhaustivenessChecker : WhenExhaustivenessChe
context: BindingContext
): List<WhenMissingCase> {
// when on empty enum / sealed is considered non-exhaustive, see test whenOnEmptySealed
if (subclasses.isEmpty()) return listOf(UnknownMissingCase)
if (subclasses.isEmpty()) return listOf(WhenMissingCase.Unknown)
val checkedDescriptors = linkedSetOf<ClassDescriptor>()
for (whenEntry in whenExpression.entries) {
@@ -219,7 +191,20 @@ internal abstract class WhenOnClassExhaustivenessChecker : WhenExhaustivenessChe
}
}
}
return (subclasses - checkedDescriptors).map(::ClassMissingCase)
return (subclasses - checkedDescriptors).map(::createWhenMissingCaseForClassOrEnum)
}
private fun createWhenMissingCaseForClassOrEnum(classDescriptor: ClassDescriptor): WhenMissingCase {
val classId = DescriptorUtils.getClassIdForNonLocalClass(classDescriptor)
return if (classDescriptor.kind != ClassKind.ENUM_ENTRY) {
WhenMissingCase.IsTypeCheckIsMissing(
classId = DescriptorUtils.getClassIdForNonLocalClass(classDescriptor),
isSingleton = classDescriptor.kind.isSingleton
)
} else {
val enumClassId = classId.outerClassId ?: error("Enum should have class id")
WhenMissingCase.EnumCheckIsMissing(CallableId(enumClassId, classId.shortClassName))
}
}
}
@@ -329,10 +314,10 @@ object WhenChecker {
) = WhenOnSealedExhaustivenessChecker.getMissingCases(expression, context, sealedClassDescriptor, false)
fun getMissingCases(expression: KtWhenExpression, context: BindingContext): List<WhenMissingCase> {
val type = whenSubjectType(expression, context) ?: return listOf(UnknownMissingCase)
val type = whenSubjectType(expression, context) ?: return listOf(WhenMissingCase.Unknown)
val nullable = type.isMarkedNullable
val checkers = exhaustivenessCheckers.filter { it.isApplicable(type) }
if (checkers.isEmpty()) return listOf(UnknownMissingCase)
if (checkers.isEmpty()) return listOf(WhenMissingCase.Unknown)
return checkers.map { it.getMissingCases(expression, context, TypeUtils.getClassDescriptor(type), nullable) }.flatten()
}
@@ -10,7 +10,6 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import kotlin.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cfg.WhenMissingCase;
import org.jetbrains.kotlin.config.LanguageFeature;
import org.jetbrains.kotlin.config.LanguageVersion;
import org.jetbrains.kotlin.config.LanguageVersionSettings;
@@ -23,10 +23,10 @@ import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.analyzer.moduleInfo
import org.jetbrains.kotlin.analyzer.unwrapPlatform
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.cfg.WhenMissingCase
import org.jetbrains.kotlin.cfg.hasUnknown
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor
import org.jetbrains.kotlin.diagnostics.WhenMissingCase
import org.jetbrains.kotlin.diagnostics.rendering.TabledDescriptorRenderer.newTable
import org.jetbrains.kotlin.diagnostics.rendering.TabledDescriptorRenderer.newText
import org.jetbrains.kotlin.name.FqName
@@ -12,12 +12,10 @@ import org.jetbrains.kotlin.builtins.UnsignedTypes;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.Annotated;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.descriptors.impl.PackageFragmentDescriptorImpl;
import org.jetbrains.kotlin.incremental.components.LookupLocation;
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.FqNameUnsafe;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.name.SpecialNames;
import org.jetbrains.kotlin.name.*;
import org.jetbrains.kotlin.resolve.constants.ConstantValue;
import org.jetbrains.kotlin.resolve.constants.StringValue;
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter;
@@ -114,6 +112,20 @@ public class DescriptorUtils {
return getFqNameFromTopLevelClass(containingDeclaration).child(name);
}
@NotNull
public static ClassId getClassIdForNonLocalClass(@NotNull DeclarationDescriptor descriptor) {
DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
Name name = descriptor.getName();
if (containingDeclaration instanceof PackageFragmentDescriptorImpl) {
FqName packageFqName = ((PackageFragmentDescriptorImpl) containingDeclaration).getFqName();
return new ClassId(packageFqName, name);
}
if (!(containingDeclaration instanceof ClassDescriptor)) {
return new ClassId(FqName.ROOT, name);
}
return getClassIdForNonLocalClass(containingDeclaration).createNestedClassId(name);
}
public static boolean isTopLevelDeclaration(@Nullable DeclarationDescriptor descriptor) {
return descriptor != null && descriptor.getContainingDeclaration() instanceof PackageFragmentDescriptor;
}
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.WhenMissingCase
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiTypeElement
import org.jetbrains.kotlin.diagnostics.WhenMissingCase
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirPsiDiagnostic
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
@@ -14,7 +15,6 @@ import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirTypeParameter
import org.jetbrains.kotlin.fir.expressions.WhenMissingCase
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtDeclaration
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.cfg.*
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.WhenMissingCase
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.ShortenReferences
@@ -89,14 +90,15 @@ class AddWhenRemainingBranchesFix(
(whenCloseBrace.prevSibling as? PsiWhiteSpace)?.replace(psiFactory.createNewLine())
for (case in missingCases) {
val branchConditionText = when (case) {
UnknownMissingCase, NullMissingCase, is BooleanMissingCase ->
WhenMissingCase.Unknown, WhenMissingCase.NullIsMissing, is WhenMissingCase.BooleanIsMissing ->
case.branchConditionText
is ClassMissingCase ->
if (case.classIsSingleton) {
is WhenMissingCase.IsTypeCheckIsMissing ->
if (case.isSingleton) {
""
} else {
"is "
} + case.descriptor.fqNameSafe.quoteIfNeeded().asString()
} + case.classId.asSingleFqName().quoteIfNeeded().asString()
is WhenMissingCase.EnumCheckIsMissing -> case.callableId.asFqNameForDebugInfo().quoteIfNeeded().asString()
}
val entry = psiFactory.createWhenEntry("$branchConditionText -> TODO()")
if (elseBranch != null) {