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); } }