File: Symbols\EnumTests.cs
Web Access
Project: src\src\Compilers\CSharp\Test\Symbol\Microsoft.CodeAnalysis.CSharp.Symbol.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Symbol.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Symbols
{
    public class EnumTests : CSharpTestBase
    {
        // Common example. (Associated Dev10 errors are indicated for cases
        // where the underlying type of the enum cannot be determined.)
        private const string ExampleSource =
@"class C
{
    static void F(E e) { }
    static void Main()
    {
        E e = E.A; // Dev10: 'E.A' is not supported by the language
        F(e); // Dev10: no error
        F(E.B); // Dev10: 'E.B' is not supported by the language
        int b = (int)e; // Dev10: cannot convert 'E' to 'int'
        e = e + 1; // Dev10: operator '+' cannot be applied to operands of type 'E' and 'int'
        e = ~e; // Dev10: operator '~' cannot be applied to operand of type 'E'
    }
}";
 
        [ClrOnlyFact]
        public void EnumWithPrivateInstanceField()
        {
            // No errors.
            CompileWithCustomILSource(
                ExampleSource,
@".class public E extends [mscorlib]System.Enum
{
    .field private specialname rtspecialname int16 _val
	.field public static literal valuetype E A = int16(31)
	.field public static literal valuetype E B = int16(32)
}");
        }
 
        [Fact]
        public void EnumWithNoInstanceFields()
        {
            EnumWithBogusUnderlyingType(
@".class public E extends [mscorlib]System.Enum
{
	.field public static literal valuetype E A = int32(0)
	.field public static literal valuetype E B = int32(1)
}");
        }
 
        [Fact]
        public void EnumWithMultipleInstanceFields()
        {
            // Note that Dev10 reports a single error for this case:
            // "'E' is a type not supported by the language" for "static void F(E e) { }"
            EnumWithBogusUnderlyingType(
@".class public E extends [mscorlib]System.Enum
{
    .field public specialname rtspecialname int32 _val1
    .field public specialname rtspecialname int32 _val2
	.field public static literal valuetype E A = int32(0)
	.field public static literal valuetype E B = int32(1)
}");
        }
 
        [Fact]
        public void EnumWithPrivateLiterals()
        {
            CreateCompilationWithILAndMscorlib40(
@"class C
{
    static void F(E e) { }
    static void Main()
    {
        F(E.A);
        F(E.B);
        F(E.C);
    }
}",
@".class public E extends [mscorlib]System.Enum
{
    .field public specialname rtspecialname int16 _val
	.field public static literal valuetype E A = int16(0)
	.field private static literal valuetype E B = int16(1)
	.field assembly static literal valuetype E C = int16(2)
}").VerifyDiagnostics(
            // (7,13): error CS0117: 'E' does not contain a definition for 'B'
            //         F(E.B);
            Diagnostic(ErrorCode.ERR_NoSuchMember, "B").WithArguments("E", "B"),
            // (8,13): error CS0117: 'E' does not contain a definition for 'C'
            //         F(E.C);
            Diagnostic(ErrorCode.ERR_NoSuchMember, "C").WithArguments("E", "C"));
        }
 
        [Fact]
        public void EnumUnsupportedUnderlyingType()
        {
            // bool
            EnumWithBogusUnderlyingType(
@".class public E extends [mscorlib]System.Enum
{
    .field public specialname rtspecialname bool value__
	.field public static literal valuetype E A = bool(false)
	.field public static literal valuetype E B = bool(true)
}");
            // char
            EnumWithBogusUnderlyingType(
@".class public E extends [mscorlib]System.Enum
{
    .field public specialname rtspecialname char value__
	.field public static literal valuetype E A = char(0)
	.field public static literal valuetype E B = char(1)
}");
            // string
            EnumWithBogusUnderlyingType(
@".class public E extends [mscorlib]System.Enum
{
    .field public specialname rtspecialname string _val
	.field public static literal valuetype E A = int16(0)
	.field public static literal valuetype E B = int16(1)
}");
        }
 
        private void EnumWithBogusUnderlyingType(string ilSource)
        {
            CreateCompilationWithILAndMscorlib40(ExampleSource, ilSource).VerifyDiagnostics(
                // (6,15): error CS0570: 'E.A' is not supported by the language
                //         E e = E.A; // Dev10: 'E.A' is not supported by the language
                Diagnostic(ErrorCode.ERR_BindToBogus, "E.A").WithArguments("E.A"),
                // (8,11): error CS0570: 'E.B' is not supported by the language
                //         F(E.B); // Dev10: 'E.B' is not supported by the language
                Diagnostic(ErrorCode.ERR_BindToBogus, "E.B").WithArguments("E.B"),
                // (10,13): error CS0019: Operator '+' cannot be applied to operands of type 'E' and 'int'
                //         e = e + 1; // Dev10: operator '+' cannot be applied to operands of type 'E' and 'int'
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "e + 1").WithArguments("+", "E", "int"),
                // (11,13): error CS0023: Operator '~' cannot be applied to operand of type 'E'
                //         e = ~e; // Dev10: operator '~' cannot be applied to operand of type 'E'
                Diagnostic(ErrorCode.ERR_BadUnaryOp, "~e").WithArguments("~", "E"));
        }
 
        [Fact]
        public void CycleOneMember()
        {
            var source =
@"enum E
{
    A = A,
}";
            CreateCompilation(source).VerifyDiagnostics(
                // (3,5): error CS0110: The evaluation of the constant value for 'E.A' involves a circular definition
                //     A = A,
                Diagnostic(ErrorCode.ERR_CircConstValue, "A").WithArguments("E.A").WithLocation(3, 5));
        }
 
        [Fact]
        public void CycleTwoMembers()
        {
            var source =
@"enum E
{
    A = B + 1,
    B = A + 1,
}";
            CreateCompilation(source).VerifyDiagnostics(
                // (3,5): error CS0110: The evaluation of the constant value for 'E.A' involves a circular definition
                //     A = B + 1,
                Diagnostic(ErrorCode.ERR_CircConstValue, "A").WithArguments("E.A").WithLocation(3, 5));
        }
 
        [Fact]
        public void TwoConnectedCycles()
        {
            var source =
@"enum E
{
    A = B | C,
    B = A + 1,
    C = A + 2,
}";
            CreateCompilation(source).VerifyDiagnostics(
                // (3,5): error CS0110: The evaluation of the constant value for 'E.A' involves a circular definition
                //     A = B | C,
                Diagnostic(ErrorCode.ERR_CircConstValue, "A").WithArguments("E.A").WithLocation(3, 5));
        }
 
        [Fact]
        public void CyclesAndConnectedFields()
        {
            var source =
@"enum E
{
    A = A | B,
    B = C,
    C = D,
    D = D
}";
            CreateCompilation(source).VerifyDiagnostics(
                // (3,5): error CS0110: The evaluation of the constant value for 'E.A' involves a circular definition
                //     A = A | B,
                Diagnostic(ErrorCode.ERR_CircConstValue, "A").WithArguments("E.A").WithLocation(3, 5),
                // (6,5): error CS0110: The evaluation of the constant value for 'E.D' involves a circular definition
                //     D = D
                Diagnostic(ErrorCode.ERR_CircConstValue, "D").WithArguments("E.D").WithLocation(6, 5));
        }
 
        [Fact]
        public void DependenciesTwoEnums()
        {
            var source =
@"enum E
{
    A = F.A,
    B = F.B + A
}
enum F
{
    A = 1,
    B = E.A
}";
            CreateCompilation(source).VerifyDiagnostics();
        }
 
        [Fact]
        public void MultipleCircularDependencies()
        {
            var source =
@"enum E
{
    A = B + F.B,
    B = A + F.A,
}
enum F
{
    A = E.B + 1,
    B = A + 1,
}";
            CreateCompilation(source).VerifyDiagnostics(
                // (3,5): error CS0110: The evaluation of the constant value for 'E.A' involves a circular definition
                //     A = B + F.B,
                Diagnostic(ErrorCode.ERR_CircConstValue, "A").WithArguments("E.A").WithLocation(3, 5),
                // (4,5): error CS0110: The evaluation of the constant value for 'E.B' involves a circular definition
                //     B = A + F.A,
                Diagnostic(ErrorCode.ERR_CircConstValue, "B").WithArguments("E.B").WithLocation(4, 5));
        }
 
        [Fact]
        public void CircularDefinitionManyMembers_Implicit()
        {
            // enum E { M0 = Mn + 1, M1, ..., Mn, }
            // Dev12 reports "CS1647: An expression is too long or complex to compile" at ~5600 members.
            var source = GenerateEnum(10000, (i, n) => (i == 0) ? string.Format("M{0} + 1", n - 1) : "");
            CreateCompilation(source).VerifyDiagnostics(
                // (3,5): error CS0110: The evaluation of the constant value for 'E.M0' involves a circular definition
                //     M0 = M5999 + 1,
                Diagnostic(ErrorCode.ERR_CircConstValue, "M0").WithArguments("E.M0").WithLocation(3, 5));
        }
 
        [WorkItem(843037, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/843037")]
        [ConditionalFact(typeof(NoIOperationValidation))]
        public void CircularDefinitionManyMembers_Explicit()
        {
            // enum E { M0 = Mn + 1, M1 = M0 + 1, ..., Mn = Mn-1 + 1, }
            // Dev12 reports "CS1647: An expression is too long or complex to compile" at ~1600 members.
            var source = GenerateEnum(10000, (i, n) => string.Format("M{0} + 1", (i == 0) ? (n - 1) : (i - 1)));
            CreateCompilation(source).VerifyDiagnostics(
                // (3,5): error CS0110: The evaluation of the constant value for 'E.M0' involves a circular definition
                //     M0 = M1999 + 1,
                Diagnostic(ErrorCode.ERR_CircConstValue, "M0").WithArguments("E.M0").WithLocation(3, 5));
        }
 
        [WorkItem(843037, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/843037")]
        [ConditionalFact(typeof(NoIOperationValidation))]
        public void InvertedDefinitionManyMembers_Explicit()
        {
            // enum E { M0 = M1 - 1, M1 = M2 - 1, ..., Mn = n, }
            // Dev12 reports "CS1647: An expression is too long or complex to compile" at ~1500 members.
            var source = GenerateEnum(10000, (i, n) => (i < n - 1) ? string.Format("M{0} - 1", i + 1) : i.ToString());
            CreateCompilation(source).VerifyDiagnostics();
        }
 
        /// <summary>
        /// Generate "enum E { M0 = ..., M1 = ..., ..., Mn = ... }".
        /// </summary>
        private static string GenerateEnum(int n, Func<int, int, string> getMemberValue)
        {
            var builder = new StringBuilder();
            builder.AppendLine("enum E");
            builder.AppendLine("{");
            for (int i = 0; i < n; i++)
            {
                builder.Append(string.Format("    M{0}", i));
                var value = getMemberValue(i, n);
                if (!string.IsNullOrEmpty(value))
                {
                    builder.Append(" = ");
                    builder.Append(value);
                }
                builder.AppendLine(",");
            }
            builder.AppendLine("}");
            return builder.ToString();
        }
 
        [WorkItem(45625, "https://github.com/dotnet/roslyn/issues/45625")]
        [Fact]
        public void UseSiteError_01()
        {
            var sourceA =
@"public class A { }";
            var comp = CreateCompilation(sourceA, assemblyName: "UseSiteError_sourceA");
            var refA = comp.EmitToImageReference();
 
            var sourceB =
@"public class B<T>
{
    public enum E { F }
}
public class C
{
    public const B<A>.E F = default;
}";
            comp = CreateCompilation(sourceB, references: new[] { refA });
            var refB = comp.EmitToImageReference();
 
            var sourceC =
@"class Program
{
    static void Main()
    {
        const int x = (int)~C.F;
        System.Console.WriteLine(x);
    }
}";
            comp = CreateCompilation(sourceC, references: new[] { refB });
            comp.VerifyDiagnostics(
                // (5,31): error CS0012: The type 'A' is defined in an assembly that is not referenced. You must add a reference to assembly 'UseSiteError_sourceA, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
                //         const int x = (int)~C.F;
                Diagnostic(ErrorCode.ERR_NoTypeDef, "F").WithArguments("A", "UseSiteError_sourceA, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(5, 31)
                );
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
            var expr = tree.GetRoot().DescendantNodes().Single(n => n.Kind() == SyntaxKind.BitwiseNotExpression);
            var value = model.GetConstantValue(expr);
            Assert.False(value.HasValue);
        }
 
        [WorkItem(45625, "https://github.com/dotnet/roslyn/issues/45625")]
        [Fact]
        public void UseSiteError_02()
        {
            var sourceA =
@"public class A { }";
            var comp = CreateCompilation(sourceA, assemblyName: "UseSiteError_sourceA");
            var refA = comp.EmitToImageReference();
 
            var sourceB =
@"public class B<T>
{
    public enum E { F }
}
public class C
{
    public const B<A>.E F = default;
}";
            comp = CreateCompilation(sourceB, references: new[] { refA });
            var refB = comp.EmitToImageReference();
 
            var sourceC =
@"class Program
{
    static void Main()
    {
        var x = ~C.F;
        System.Console.WriteLine(x);
    }
}";
            comp = CreateCompilation(sourceC, references: new[] { refB }, options: TestOptions.ReleaseExe);
            comp.VerifyDiagnostics(
                // (5,20): error CS0012: The type 'A' is defined in an assembly that is not referenced. You must add a reference to assembly 'UseSiteError_sourceA, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
                //         var x = ~C.F;
                Diagnostic(ErrorCode.ERR_NoTypeDef, "F").WithArguments("A", "UseSiteError_sourceA, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(5, 20)
                );
 
            comp = CreateCompilation(sourceC, references: new[] { refB }, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics(
                // (5,20): error CS0012: The type 'A' is defined in an assembly that is not referenced. You must add a reference to assembly 'UseSiteError_sourceA, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
                //         var x = ~C.F;
                Diagnostic(ErrorCode.ERR_NoTypeDef, "F").WithArguments("A", "UseSiteError_sourceA, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(5, 20)
                );
        }
 
        [Fact]
        public void PartialPublicEnum()
        {
            CreateCompilation("partial public enum E { }").VerifyDiagnostics(
                // (1,1): error CS0267: The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type.
                // partial public enum E { }
                Diagnostic(ErrorCode.ERR_PartialMisplaced, "partial").WithLocation(1, 1),
                // (1,21): error CS0267: The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type.
                // partial public enum E { }
                Diagnostic(ErrorCode.ERR_PartialMisplaced, "E").WithLocation(1, 21));
        }
    }
}