public async Task DuplicateClosedAttribute() { const string source = @"using ExhaustiveMatching; using System; namespace TestNamespace { [Closed(typeof(Square))] [◊1⟦Closed(typeof(Circle))⟧] [◊2⟦Closed(typeof(Triangle))⟧] public abstract class Shape { } public class Square : Shape { } public class Circle : Shape { } public class Triangle : Shape { } }"; var expected1 = DiagnosticResult .Error("EM0104", "Duplicate 'Closed' attribute") .AddLocation(source, 1); var expected2 = DiagnosticResult .Error("EM0104", "Duplicate 'Closed' attribute") .AddLocation(source, 2); await VerifyCSharpDiagnosticsAsync(source, expected1, expected2); }
public async Task ErrorForMatchOnTypesOutsideOfHierarchy() { const string args = "Shape shape"; const string test = @" var result = shape switch { Square square => ""Square: "" + square, Circle circle => ""Circle: "" + circle, ◊1⟦EquilateralTriangle equilateralTriangle⟧ => ""EquilateralTriangle: "" + equilateralTriangle, Triangle triangle => ""Triangle: "" + triangle, ◊3⟦◊2⟦string⟧ s⟧ => ""string: "" + s, _ => throw ExhaustiveMatch.Failed(shape), };"; var source = CodeContext.Shapes(args, test); var expected1 = DiagnosticResult .Error("EM0103", "TestNamespace.EquilateralTriangle is not a case type inheriting from type being matched: TestNamespace.Shape") .AddLocation(source, 1); var compileError = DiagnosticResult .Error("CS8121", "An expression of type 'Shape' cannot be handled by a pattern of type 'string'.") .AddLocation(source, 2); var expected2 = DiagnosticResult .Error("EM0103", "System.String is not a case type inheriting from type being matched: TestNamespace.Shape") .AddLocation(source, 3); await VerifyCSharpDiagnosticsAsync(source, expected1, compileError, expected2); }
public async Task SelfTypeAsCaseType() { const string source = @"using ExhaustiveMatching; namespace TestNamespace { [Closed(typeof(◊1⟦IToken⟧), typeof(IKeywordToken))] public interface IToken { } public interface IKeywordToken : IToken { } class TestClass { void TestMethod(IToken token) { _ = token switch { IKeywordToken _ => ""foreach"", _ => throw ExhaustiveMatch.Failed(token), }; } } }"; var expected = DiagnosticResult .Error("EM0013", "Closed type case is not a subtype: TestNamespace.IToken") .AddLocation(source, 1); await VerifyCSharpDiagnosticsAsync(source, expected); }
public async Task EmptyTypeofDoesNotCauseMissingCase() { const string source = @"using System; using ExhaustiveMatching; namespace TestNamespace { [Closed( typeof(Square), typeof(◊1⟦⟧), typeof(Circle))] public abstract class Shape { } public class Square : Shape { } public class Circle : Shape { } class TestClass { void TestMethod(Shape shape) { _ = shape switch { Square square => ""Square: "" + square, Circle circle => ""Circle: "" + circle, _ => throw ExhaustiveMatch.Failed(shape), }; } } }"; var compileError = DiagnosticResult .Error("CS1031", "Type expected") .AddLocation(source, 1); await VerifyCSharpDiagnosticsAsync(source, compileError); }
public async Task UnsupportedCaseClauses() { const string args = "Shape shape"; const string test = @" var result = shape switch { Square square => ""Square: "" + square, Circle circle => ""Circle: "" + circle, Triangle triangle ◊1⟦when true⟧ => ""Triangle: "" + triangle, ◊2⟦12⟧ => null, _ => throw ExhaustiveMatch.Failed(shape), };"; var source = CodeContext.Shapes(args, test); var expected1 = DiagnosticResult .Error("EM0100", "When guard is not supported in an exhaustive switch") .AddLocation(source, 1); var compileError = DiagnosticResult .Error("CS0029", "Cannot implicitly convert type 'int' to 'TestNamespace.Shape'") .AddLocation(source, 2); var expected2 = DiagnosticResult .Error("EM0101", "Case pattern not supported in exhaustive switch: 12") .AddLocation(source, 2); await VerifyCSharpDiagnosticsAsync(source, expected1, compileError, expected2); }
public async Task SwitchOnNonClosedType() { const string args = "object o"; const string test = @" var result = ◊1⟦o⟧ switch { string s => ""string: "" + s, Triangle triangle ◊2⟦when true⟧ => ""Triangle: "" + triangle, ◊3⟦12⟧ => null, _ => throw ExhaustiveMatch.Failed(o), };"; var source = CodeContext.Shapes(args, test); var expected1 = DiagnosticResult .Error("EM0102", "Exhaustive switch must be on enum or closed type, was on: System.Object") .AddLocation(source, 1); // Still reports these errors var expected2 = DiagnosticResult .Error("EM0100", "When guard is not supported in an exhaustive switch") .AddLocation(source, 2); var expected3 = DiagnosticResult .Error("EM0101", "Case pattern not supported in exhaustive switch: 12") .AddLocation(source, 3); await VerifyCSharpDiagnosticsAsync(source, expected1, expected2, expected3); }
public async Task SwitchOnNullableEnum() { const string args = "DayOfWeek? dayOfWeek"; const string test = @" ◊1⟦switch⟧ (dayOfWeek) { default: throw ExhaustiveMatch.Failed(dayOfWeek); case DayOfWeek.Monday: case DayOfWeek.Tuesday: case DayOfWeek.Wednesday: case DayOfWeek.Thursday: // Omitted Friday Console.WriteLine(""Weekday""); break; case DayOfWeek.Saturday: // Omitted Sunday Console.WriteLine(""Weekend""); break; }"; var source = CodeContext.Basic(args, test); var expectedNull = DiagnosticResult .Error("EM0002", "null value not handled by switch") .AddLocation(source, 1); var expectedFriday = DiagnosticResult .Error("EM0001", "Enum value not handled by switch: Friday") .AddLocation(source, 1); var expectedSunday = DiagnosticResult .Error("EM0001", "Enum value not handled by switch: Sunday") .AddLocation(source, 1); await VerifyCSharpDiagnosticsAsync(CodeContext.Basic(args, test), expectedNull, expectedFriday, expectedSunday); }
public async Task SwitchOnNullableTuple() { const string args = "Shape shape1, Shape shape2"; const string test = @" (Shape, Shape)? value = (shape1, shape2); switch (◊1⟦value⟧) { case ◊2⟦(Square square1, Square square2)⟧: Console.WriteLine(""Square: "" + square1); Console.WriteLine(""Square: "" + square2); break; default: throw ExhaustiveMatch.Failed(value); }"; var source = CodeContext.Shapes(args, test); // TODO type name is bad var expected1 = DiagnosticResult .Error("EM0102", "Exhaustive switch must be on enum or closed type, was on: System.Nullable") .AddLocation(source, 1); var expected2 = DiagnosticResult .Error("EM0101", "Case pattern not supported in exhaustive switch: (Square square1, Square square2)") .AddLocation(source, 2); await VerifyCSharpDiagnosticsAsync(source, expected1, expected2); }
public async Task MirrorHierarchyMustBeCovered() { const string source = @"using ExhaustiveMatching; namespace TestNamespace { [Closed( typeof(ISquare), typeof(ICircle))] public interface IShape { } public interface ISquare : IShape { } public interface ICircle : IShape { } [Closed( typeof(Square), typeof(Circle))] public abstract class Shape : IShape { } public class Square : Shape, ISquare { } public class ◊1⟦Circle⟧ : Shape { } }"; var expected = DiagnosticResult .Error("EM0014", "An exhaustive match on TestNamespace.IShape might not cover the type TestNamespace.Circle. It must be a subtype of a leaf type of the case type tree.") .AddLocation(source, 1); await VerifyCSharpDiagnosticsAsync(source, expected); }
public async Task SwitchOnEnumThrowingInvalidEnumIsNotExhaustiveReportsDiagnostic() { const string args = "DayOfWeek dayOfWeek"; const string test = @" ◊1⟦switch⟧ (dayOfWeek) { default: throw new InvalidEnumArgumentException(nameof(dayOfWeek), (int)dayOfWeek, typeof(DayOfWeek)); case DayOfWeek.Monday: case DayOfWeek.Tuesday: case DayOfWeek.Wednesday: case DayOfWeek.Thursday: // Omitted Friday Console.WriteLine(""Weekday""); break; case DayOfWeek.Saturday: // Omitted Sunday Console.WriteLine(""Weekend""); break; }"; var source = CodeContext.Basic(args, test); var expectedFriday = DiagnosticResult .Error("EM0001", "Enum value not handled by switch: Friday") .AddLocation(source, 1); var expectedSunday = DiagnosticResult .Error("EM0001", "Enum value not handled by switch: Sunday") .AddLocation(source, 1); await VerifyCSharpDiagnosticsAsync(source, expectedFriday, expectedSunday); }
public async Task SwitchOnEnumThrowingExhaustiveMatchFailedIsNotExhaustiveReportsDiagnostic() { const string args = "CoinFlip coinFlip"; const string test = @" var result = coinFlip ◊1⟦switch⟧ { CoinFlip.Heads => ""Heads!"", _ => throw ExhaustiveMatch.Failed(coinFlip), };"; var source = CodeContext.CoinFlip(args, test); var expectedTails = DiagnosticResult.Error("EM0001", "Enum value not handled by switch: Tails") .AddLocation(source, 1); await VerifyCSharpDiagnosticsAsync(source, expectedTails); }
public async Task PrimitiveArgumentToClosedAttributeHandled() { const string source = @"using ExhaustiveMatching; using System; namespace TestNamespace { [Closed(◊1⟦5⟧)] public abstract class Shape { } }"; var compileError = DiagnosticResult .Error("CS1503", "Argument 1: cannot convert from 'int' to 'System.Type'") .AddLocation(source, 1); await VerifyCSharpDiagnosticsAsync(source, compileError); }
public async Task SwitchOnNullableStruct() { const string args = "HashCode? hashCode"; const string test = @" _ = ◊1⟦hashCode⟧ switch { null => ""null"", _ => throw ExhaustiveMatch.Failed(hashCode), };"; var source = CodeContext.Basic(args, test); var expected = DiagnosticResult .Error("EM0102", "Exhaustive switch must be on enum or closed type, was on: System.Nullable") .AddLocation(source, 1); await VerifyCSharpDiagnosticsAsync(source, expected); }
public async Task EmptyTypeofArgumentToClosedAttributeHandled() { const string source = @"using ExhaustiveMatching; using System; namespace TestNamespace { [Closed(typeof(◊1⟦⟧))] public abstract class Shape { } }"; var compileError = DiagnosticResult .Error("CS1031", "Type expected") .AddLocation(source, 1); await VerifyCSharpDiagnosticsAsync(source, compileError); }
public async Task CaseTypeMustBeUnique() { const string source = @"using ExhaustiveMatching; using System; namespace TestNamespace { [Closed(typeof(Square), typeof(◊1⟦Square⟧))] public abstract class Shape { } public class Square : Shape { } }"; var expected = DiagnosticResult .Error("EM0105", "Duplicate case type: TestNamespace.Square") .AddLocation(source, 1); await VerifyCSharpDiagnosticsAsync(source, expected); }
public async Task SwitchOnStruct() { const string args = "HashCode hashCode"; const string test = @" switch (◊1⟦hashCode⟧) { default: throw ExhaustiveMatch.Failed(hashCode); case HashCode code: Console.WriteLine(""Hashcode: "" + code); break; }"; var source = CodeContext.Basic(args, test); var expected = DiagnosticResult.Error("EM0102", "Exhaustive switch must be on enum or closed type, was on: System.HashCode") .AddLocation(source, 1); await VerifyCSharpDiagnosticsAsync(source, expected); }
public async Task HandlesMultipleClosedAttributes() { const string source = @"using System; using ExhaustiveMatching; namespace TestNamespace { [Closed( typeof(Square), typeof(Circle))] [◊1⟦Closed(typeof(Triangle))⟧] public abstract class Shape { } public class Square : Shape { } public class Circle : Shape { } public class Triangle : Shape { } class TestClass { void TestMethod(Shape shape) { switch (shape) { default: throw ExhaustiveMatch.Failed(shape); case Square square: Console.WriteLine(""Square: "" + square); break; case Circle circle: Console.WriteLine(""Circle: "" + circle); break; case Triangle triangle: Console.WriteLine(""Triangle: "" + triangle); break; } } } }"; var expected1 = DiagnosticResult .Error("EM0104", "Duplicate 'Closed' attribute") .AddLocation(source, 1); await VerifyCSharpDiagnosticsAsync(source, expected1); }
public async Task OpenInterfaceSubtypeOfClosedTypeMustBeCase() { const string source = @"using ExhaustiveMatching; namespace TestNamespace { [Closed( typeof(ISquare), typeof(ICircle))] public interface IShape { } public interface ISquare : IShape { } public interface ICircle : IShape { } public interface ◊1⟦ITriangle⟧ : IShape { } }"; var expected = DiagnosticResult .Error("EM0015", "Open interface TestNamespace.ITriangle is not a case of its closed supertype: TestNamespace.IShape") .AddLocation(source, 1); await VerifyCSharpDiagnosticsAsync(source, expected); }
public async Task SwitchOnStruct() { const string args = "HashCode hashCode"; const string test = @" _ = ◊1⟦hashCode⟧ switch { HashCode code => ""Hashcode: "" + code, ◊2⟦_⟧ => throw ExhaustiveMatch.Failed(hashCode), };"; var source = CodeContext.Basic(args, test); var expected = DiagnosticResult .Error("EM0102", "Exhaustive switch must be on enum or closed type, was on: System.HashCode") .AddLocation(source, 1); var compileError = DiagnosticResult .Error("CS8510", "The pattern has already been handled by a previous arm of the switch expression.") .AddLocation(source, 2); await VerifyCSharpDiagnosticsAsync(source, expected, compileError); }
public async Task ConcreteSubtypeOfClosedTypeMustBeCase() { const string source = @"using ExhaustiveMatching; namespace TestNamespace { [Closed( typeof(Square), typeof(Circle))] public abstract class Shape { } public class Square : Shape { } public class Circle : Shape { } public class ◊1⟦Triangle⟧ : Shape { } }"; var expected = DiagnosticResult .Error("EM0011", "TestNamespace.Triangle is not a case of its closed supertype: TestNamespace.Shape") .AddLocation(source, 1); await VerifyCSharpDiagnosticsAsync(source, expected); }
public async Task SwitchOnClosedThrowingExhaustiveMatchFailedIsNotExhaustiveReportsDiagnostic() { const string args = "Shape shape"; const string test = @" var result = shape ◊1⟦switch⟧ { Square square => ""Square: "" + square, _ => throw ExhaustiveMatch.Failed(shape) };"; var source = CodeContext.Shapes(args, test); var expectedCircle = DiagnosticResult .Error("EM0003", "Subtype not handled by switch: TestNamespace.Circle") .AddLocation(source, 1); var expectedTriangle = DiagnosticResult .Error("EM0003", "Subtype not handled by switch: TestNamespace.Triangle") .AddLocation(source, 1); await VerifyCSharpDiagnosticsAsync(source, expectedCircle, expectedTriangle); }
public async Task CaseTypeMustBeDirectSubtype() { const string source = @"using ExhaustiveMatching; using System; namespace TestNamespace { [Closed( typeof(Rectangle), typeof(◊1⟦Square⟧))] public abstract class Shape { } public class Rectangle : Shape { } public class Square : Rectangle { } }"; var expected = DiagnosticResult .Error("EM0012", "Closed type case is not a direct subtype: TestNamespace.Square") .AddLocation(source, 1); await VerifyCSharpDiagnosticsAsync(source, expected); }
public async Task CaseTypeMustBeSubtype() { const string source = @"using ExhaustiveMatching; using System; namespace TestNamespace { [Closed( typeof(Square), typeof(Circle), typeof(◊1⟦String⟧))] public abstract class Shape { } public class Square : Shape { } public class Circle : Shape { } }"; var expected = DiagnosticResult .Error("EM0013", "Closed type case is not a subtype: System.String") .AddLocation(source, 1); await VerifyCSharpDiagnosticsAsync(source, expected); }
public async Task CaseTypeMustBeUniqueWithMultipleCloseAttributes() { const string source = @"using ExhaustiveMatching; using System; namespace TestNamespace { [Closed(typeof(Square))] [◊1⟦Closed(typeof(◊2⟦Square⟧))⟧] public abstract class Shape { } public class Square : Shape { } }"; var expected1 = DiagnosticResult .Error("EM0104", "Duplicate 'Closed' attribute") .AddLocation(source, 1); var expected2 = DiagnosticResult .Error("EM0105", "Duplicate case type: TestNamespace.Square") .AddLocation(source, 2); await VerifyCSharpDiagnosticsAsync(source, expected1, expected2); }
public async Task SwitchOnTuple() { const string args = "Shape shape1, Shape shape2"; const string test = @" _ = ◊1⟦(shape1, shape2)⟧ switch { ◊2⟦(Square square1, Square square2)⟧ => ""squares"", _ => throw ExhaustiveMatch.Failed((shape1, shape2)), };"; var source = CodeContext.Shapes(args, test); // TODO type name is bad var expected1 = DiagnosticResult .Error("EM0102", "Exhaustive switch must be on enum or closed type, was on: System.") .AddLocation(source, 1); var expected2 = DiagnosticResult .Error("EM0101", "Case pattern not supported in exhaustive switch: (Square square1, Square square2)") .AddLocation(source, 2); await VerifyCSharpDiagnosticsAsync(source, expected1, expected2); }