private static void MustBeCase(
            SyntaxNodeAnalysisContext context,
            TypeDeclarationSyntax typeDeclaration,
            ITypeSymbol typeSymbol,
            INamedTypeSymbol closedAttribute)
        {
            var isConcrete      = !typeSymbol.IsAbstract;
            var isOpenInterface = typeSymbol.TypeKind == TypeKind.Interface &&
                                  !typeSymbol.HasAttribute(closedAttribute);

            if (!isConcrete && !isOpenInterface)
            {
                return;
            }

            // Any concrete type or open interface directly inheriting from a closed type must be listed in the cases
            var directSuperTypes = typeSymbol.DirectSuperTypes();
            var closedSuperTypes = directSuperTypes
                                   .Where(t => t.HasAttribute(closedAttribute))
                                   .ToList();

            foreach (var superType in closedSuperTypes)
            {
                var isMember = superType.GetCaseTypes(closedAttribute)
                               .Any(t => t.Equals(typeSymbol));
                if (isMember)
                {
                    continue;
                }

                var descriptor = isConcrete
                    ? ExhaustiveMatchAnalyzer.ConcreteSubtypeMustBeCaseOfClosedType
                                 // else isOpenInterface is always true
                    : ExhaustiveMatchAnalyzer.OpenInterfaceSubtypeMustBeCaseOfClosedType;

                var diagnostic = Diagnostic.Create(descriptor, typeDeclaration.Identifier.GetLocation(),
                                                   typeSymbol.GetFullName(), superType.GetFullName());
                context.ReportDiagnostic(diagnostic);
            }

            if (!isConcrete)
            {
                return;
            }

            // Any concrete type indirectly inheriting from a closed type must be covered by a case type
            // that isn't itself closed. If it were closed, then it could be matched by all the cases, and
            // this type would not be matched.
            var otherClosedSuperTypes = typeSymbol.AllSuperTypes()
                                        .Where(t => t.HasAttribute(closedAttribute))
                                        .Except(closedSuperTypes);

            foreach (var superType in otherClosedSuperTypes)
            {
                var isCovered = superType
                                .GetLeafCaseTypes(closedAttribute)
                                .Any(typeSymbol.IsSubtypeOf);

                if (isCovered)
                {
                    continue;
                }

                var diagnostic = Diagnostic.Create(ExhaustiveMatchAnalyzer.SubtypeMustBeCovered,
                                                   typeDeclaration.Identifier.GetLocation(), typeSymbol.GetFullName(), superType.GetFullName());
                context.ReportDiagnostic(diagnostic);
            }
        }