Example #1
0
        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);
        }
Example #7
0
        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);
        }
Example #8
0
        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);
        }
Example #9
0
        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);
        }
Example #10
0
        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);
        }
Example #12
0
        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);
        }
Example #14
0
        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);
        }
Example #15
0
        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);
        }
Example #16
0
        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);
        }
Example #17
0
        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);
        }
Example #18
0
        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);
        }
Example #20
0
        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);
        }
Example #22
0
        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);
        }
Example #23
0
        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);
        }
Example #24
0
        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);
        }