Skip to content

Commit f1cf4ee

Browse files
add EM0015 Open Interface Subtype of a Closed Type Must Be a Case
1 parent 1a50252 commit f1cf4ee

File tree

9 files changed

+98
-13
lines changed

9 files changed

+98
-13
lines changed

ExhaustiveMatching.Analyzer.Tests/TypeDeclarationAnalyzerTests.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace ExhaustiveMatching.Analyzer.Tests
1010
public class TypeDeclarationAnalyzerTests : CodeFixVerifier
1111
{
1212
[TestMethod]
13-
public void SubtypeOfClosedTypeMustBeCase()
13+
public void ConcreteSubtypeOfClosedTypeMustBeCase()
1414
{
1515
const string test = @"using ExhaustiveMatching;
1616
namespace TestNamespace
@@ -35,6 +35,32 @@ public class Triangle : Shape { }
3535
VerifyCSharpDiagnostic(test, expected);
3636
}
3737

38+
[TestMethod]
39+
public void OpenInterfaceSubtypeOfClosedTypeMustBeCase()
40+
{
41+
const string test = @"using ExhaustiveMatching;
42+
namespace TestNamespace
43+
{
44+
[Closed(
45+
typeof(ISquare),
46+
typeof(ICircle))]
47+
public interface IShape { }
48+
public interface ISquare : IShape { }
49+
public interface ICircle : IShape { }
50+
public interface ITriangle : IShape { }
51+
}";
52+
53+
var expected = new DiagnosticResult
54+
{
55+
Id = "EM0015",
56+
Message = "Open interface TestNamespace.ITriangle is not a case of its closed supertype: TestNamespace.IShape",
57+
Severity = DiagnosticSeverity.Error,
58+
Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 22, 9) }
59+
};
60+
61+
VerifyCSharpDiagnostic(test, expected);
62+
}
63+
3864
[TestMethod]
3965
public void CaseTypeMustBeSubtype()
4066
{

ExhaustiveMatching.Analyzer.nuspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<package>
33
<metadata>
44
<id>ExhaustiveMatching.Analyzer</id>
5-
<version>0.3.2</version>
5+
<version>0.4.0</version>
66
<authors>Jeff Walker</authors>
77
<owners>Jeff Walker</owners>
88
<license type="expression">BSD-3-Clause</license>

ExhaustiveMatching.Analyzer/ExhaustiveMatchAnalyzer.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ public class ExhaustiveMatchAnalyzer : DiagnosticAnalyzer
4040
private static readonly LocalizableString EM0014Message = LoadString(nameof(Resources.EM0014Message));
4141
private static readonly LocalizableString EM0014Description = LoadString(Resources.EM0014Description);
4242

43+
private static readonly LocalizableString EM0015Title = LoadString(nameof(Resources.EM0015Title));
44+
private static readonly LocalizableString EM0015Message = LoadString(nameof(Resources.EM0015Message));
45+
private static readonly LocalizableString EM0015Description = LoadString(Resources.EM0015Description);
46+
4347
private static readonly LocalizableString EM0100Title = LoadString(nameof(Resources.EM0100Title));
4448
private static readonly LocalizableString EM0100Message = LoadString(nameof(Resources.EM0100Message));
4549
private static readonly LocalizableString EM0100Description = LoadString(Resources.EM0100Description);
@@ -86,6 +90,10 @@ public class ExhaustiveMatchAnalyzer : DiagnosticAnalyzer
8690
new DiagnosticDescriptor("EM0014", EM0014Title, EM0014Message, Category,
8791
DiagnosticSeverity.Error, isEnabledByDefault: true, EM0014Description);
8892

93+
public static readonly DiagnosticDescriptor OpenInterfaceSubtypeMustBeCaseOfClosedType =
94+
new DiagnosticDescriptor("EM0015", EM0015Title, EM0015Message, Category,
95+
DiagnosticSeverity.Error, isEnabledByDefault: true, EM0015Description);
96+
8997
public static readonly DiagnosticDescriptor WhenGuardNotSupported =
9098
new DiagnosticDescriptor("EM0100", EM0100Title, EM0100Message, Category,
9199
DiagnosticSeverity.Error, isEnabledByDefault: true, EM0100Description);
@@ -105,7 +113,8 @@ public class ExhaustiveMatchAnalyzer : DiagnosticAnalyzer
105113
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
106114
ImmutableArray.Create(NotExhaustiveEnumSwitch, NotExhaustiveObjectSwitch,
107115
NotExhaustiveNullableEnumSwitch, ConcreteSubtypeMustBeCaseOfClosedType,
108-
MustBeDirectSubtype, MustBeSubtype, SubtypeMustBeCovered, WhenGuardNotSupported,
116+
MustBeDirectSubtype, MustBeSubtype, SubtypeMustBeCovered,
117+
OpenInterfaceSubtypeMustBeCaseOfClosedType, WhenGuardNotSupported,
109118
CaseClauseTypeNotSupported, OpenTypeNotSupported, MatchMustBeOnCaseType);
110119

111120
public override void Initialize(AnalysisContext context)

ExhaustiveMatching.Analyzer/ExhaustiveMatching.Analyzer.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
55
<IncludeBuildOutput>false</IncludeBuildOutput>
6-
<AssemblyVersion>0.3.2.0</AssemblyVersion>
7-
<FileVersion>0.3.2.0</FileVersion>
8-
<Version>0.3.2</Version>
6+
<AssemblyVersion>0.4.0.0</AssemblyVersion>
7+
<FileVersion>0.4.0.0</FileVersion>
8+
<Version>0.4.0</Version>
99
</PropertyGroup>
1010

1111
<ItemGroup>

ExhaustiveMatching.Analyzer/Resources.Designer.cs

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ExhaustiveMatching.Analyzer/Resources.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,15 @@
180180
<data name="EM0014Title" xml:space="preserve">
181181
<value>Concrete Subtype of a Closed Type Must Be a Covered</value>
182182
</data>
183+
<data name="EM0015Description" xml:space="preserve">
184+
<value>An open interface subtype of a type marked with the "Closed" attribute must be a case of the closed type.</value>
185+
</data>
186+
<data name="EM0015Message" xml:space="preserve">
187+
<value>Open interface {0} is not a case of its closed supertype: {1}</value>
188+
</data>
189+
<data name="EM0015Title" xml:space="preserve">
190+
<value>Open Interface Subtype of a Closed Type Must Be a Case</value>
191+
</data>
183192
<data name="EM0100Description" xml:space="preserve">
184193
<value>An exhustive switch statement does not support case clauses with "when" guards</value>
185194
</data>

ExhaustiveMatching.Analyzer/TypeDeclarationAnalyzer.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,13 @@ private static void MustBeCase(
2828
ITypeSymbol typeSymbol,
2929
INamedTypeSymbol closedAttribute)
3030
{
31-
if (typeSymbol.IsAbstract)
31+
var isConcrete = !typeSymbol.IsAbstract;
32+
var isOpenInterface = typeSymbol.TypeKind == TypeKind.Interface
33+
&& !typeSymbol.HasAttribute(closedAttribute);
34+
if (!isConcrete && !isOpenInterface)
3235
return;
3336

34-
// Any concrete type directly inheriting from a closed type must be listed in the cases
37+
// Any concrete type or open interface directly inheriting from a closed type must be listed in the cases
3538
var directSuperTypes = typeSymbol.DirectSuperTypes();
3639
var closedSuperTypes = directSuperTypes
3740
.Where(t => t.HasAttribute(closedAttribute))
@@ -44,11 +47,18 @@ private static void MustBeCase(
4447
if (isMember)
4548
continue;
4649

47-
var diagnostic = Diagnostic.Create(ExhaustiveMatchAnalyzer.ConcreteSubtypeMustBeCaseOfClosedType,
48-
typeDeclaration.Identifier.GetLocation(), typeSymbol.GetFullName(), superType.GetFullName());
50+
var descriptor = isConcrete
51+
? ExhaustiveMatchAnalyzer.ConcreteSubtypeMustBeCaseOfClosedType
52+
// else isOpenInterface is always true
53+
: ExhaustiveMatchAnalyzer.OpenInterfaceSubtypeMustBeCaseOfClosedType;
54+
55+
var diagnostic = Diagnostic.Create(descriptor, typeDeclaration.Identifier.GetLocation(),
56+
typeSymbol.GetFullName(), superType.GetFullName());
4957
context.ReportDiagnostic(diagnostic);
5058
}
5159

60+
if (!isConcrete) return;
61+
5262
// Any concrete type indirectly inheriting from a closed type must be covered by a case type
5363
// that isn't itself closed. If it were closed, then it could be matched by all the cases, and
5464
// this type would not be matched.

ExhaustiveMatching/ExhaustiveMatching.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
5-
<AssemblyVersion>0.3.2.0</AssemblyVersion>
6-
<FileVersion>0.3.2.0</FileVersion>
7-
<Version>0.3.2</Version>
5+
<AssemblyVersion>0.4.0.0</AssemblyVersion>
6+
<FileVersion>0.4.0.0</FileVersion>
7+
<Version>0.4.0</Version>
88
</PropertyGroup>
99

1010
<ItemGroup>

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,10 @@ The analyzer reports various errors for incorrect code. The table below gives a
188188
<th>EM0014</th>
189189
<td>A concrete subtype of a closed type is not covered by some case</td>
190190
</tr>
191+
<tr>
192+
<th>EM0015</th>
193+
<td>An open interface is not listed as a case in a closed type it is a direct subtype of</td>
194+
</tr>
191195
<tr>
192196
<th>EM0100</th>
193197
<td>An exhaustive switch can't contain when guards</td>

0 commit comments

Comments
 (0)