From f19a9af7b3e578db96e1f6c7ab6dbb89b61aa6cc Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Wed, 24 Feb 2021 12:23:19 +0100 Subject: [PATCH] Unify WhenMissingCase from FIR and FE1.0 --- .../org/jetbrains/kotlin/cfg/WhenChecker.kt | 61 +++++++------------ .../jetbrains/kotlin/diagnostics/Errors.java | 1 - .../kotlin/diagnostics/rendering/Renderers.kt | 2 +- .../kotlin/resolve/DescriptorUtils.java | 20 ++++-- .../fir/generator/HLDiagnosticConverter.kt | 1 - .../diagnostics/KtFirDataClassConverters.kt | 2 +- .../quickfix/AddWhenRemainingBranchesFix.kt | 10 +-- 7 files changed, 47 insertions(+), 50 deletions(-) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt index be754db3ec2..3c50d8fa77b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt @@ -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.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 { // 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() 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 { - 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() } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index ee4c986f88d..ccb5cbe69e2 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -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; diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/Renderers.kt b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/Renderers.kt index 1a768205f7a..4ad82c6a3f9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/Renderers.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/Renderers.kt @@ -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 diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.java b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.java index effc64e6849..6cbd382637f 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.java +++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.java @@ -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; } diff --git a/idea/idea-frontend-fir/idea-frontend-fir-generator/src/org/jetbrains/kotlin/idea/frontend/api/fir/generator/HLDiagnosticConverter.kt b/idea/idea-frontend-fir/idea-frontend-fir-generator/src/org/jetbrains/kotlin/idea/frontend/api/fir/generator/HLDiagnosticConverter.kt index 18105ca4984..058c3885915 100644 --- a/idea/idea-frontend-fir/idea-frontend-fir-generator/src/org/jetbrains/kotlin/idea/frontend/api/fir/generator/HLDiagnosticConverter.kt +++ b/idea/idea-frontend-fir/idea-frontend-fir-generator/src/org/jetbrains/kotlin/idea/frontend/api/fir/generator/HLDiagnosticConverter.kt @@ -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 diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt index 08f31317ce9..eec540ccd9c 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -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 diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddWhenRemainingBranchesFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddWhenRemainingBranchesFix.kt index 15c349c79b8..d88d7a23718 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddWhenRemainingBranchesFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddWhenRemainingBranchesFix.kt @@ -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) {