File: Semantics\StructsTests.cs
Web Access
Project: src\src\Compilers\CSharp\Test\Semantic\Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Semantic.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.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols.Retargeting;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
using Basic.Reference.Assemblies;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics
{
    public class StructsTests : CompilingTestBase
    {
        [WorkItem(540982, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/540982")]
        [Fact()]
        public void TestInitFieldStruct()
        {
            var text = @"
public struct A
{
    A a = new A();   // CS8036
    public static int Main() { return 1; }
}
";
            CreateCompilation(text, parseOptions: TestOptions.Regular9).VerifyDiagnostics(
                // (2,15): error CS8983: A 'struct' with field initializers must include an explicitly declared constructor.
                // public struct A
                Diagnostic(ErrorCode.ERR_StructHasInitializersAndNoDeclaredConstructor, "A").WithLocation(2, 15),
                // (4,7): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater.
                //     A a = new A();   // CS8036
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "a").WithArguments("struct field initializers", "10.0").WithLocation(4, 7),
                // (4,7): error CS0523: Struct member 'A.a' of type 'A' causes a cycle in the struct layout
                //     A a = new A();   // CS8036
                Diagnostic(ErrorCode.ERR_StructLayoutCycle, "a").WithArguments("A.a", "A").WithLocation(4, 7),
                // (4,7): warning CS0169: The field 'A.a' is never used
                //     A a = new A();   // CS8036
                Diagnostic(ErrorCode.WRN_UnreferencedField, "a").WithArguments("A.a").WithLocation(4, 7));
        }
 
        [WorkItem(1075325, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1075325"), WorkItem(343, "CodePlex")]
        [Fact()]
        public void TestInitEventStruct_01()
        {
            var text = @"
struct S {
    event System.Action E = null;
 
    void M()
    {
        E();
    }
}
";
            CreateCompilation(text, parseOptions: TestOptions.Regular9).VerifyDiagnostics(
                // (2,8): error CS8983: A 'struct' with field initializers must include an explicitly declared constructor.
                // struct S {
                Diagnostic(ErrorCode.ERR_StructHasInitializersAndNoDeclaredConstructor, "S").WithLocation(2, 8),
                // (3,25): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater.
                //     event System.Action E = null;
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "E").WithArguments("struct field initializers", "10.0").WithLocation(3, 25));
 
            CreateCompilation(text).VerifyDiagnostics(
                // (2,8): error CS8983: A 'struct' with field initializers must include an explicitly declared constructor.
                // struct S {
                Diagnostic(ErrorCode.ERR_StructHasInitializersAndNoDeclaredConstructor, "S").WithLocation(2, 8));
        }
 
        [Fact()]
        public void TestInitEventStruct_02()
        {
            var text = @"
struct S {
    event System.Action E = null;
    public S() { }
    void M()
    {
        E();
    }
}
";
            CreateCompilation(text, parseOptions: TestOptions.Regular9).VerifyDiagnostics(
                // (3,25): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater.
                //     event System.Action E = null;
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "E").WithArguments("struct field initializers", "10.0").WithLocation(3, 25),
                // (4,12): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater.
                //     public S() { }
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S").WithArguments("parameterless struct constructors", "10.0").WithLocation(4, 12));
 
            CreateCompilation(text).VerifyDiagnostics();
        }
 
        [WorkItem(1075325, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1075325"), WorkItem(343, "CodePlex")]
        [Fact()]
        public void TestStaticInitInStruct()
        {
            var text = @"
struct S {
    static event System.Action E = M;
    static int F = 10;
    static int P {get; set;} = 20;
 
    static void M()
    {
    }
 
    static void Main()
    {
        System.Console.WriteLine(""{0} {1} {2}"", S.F, S.P, S.E == null);
    }
}
";
            var comp = CreateCompilation(text, options: TestOptions.DebugExe);
 
            CompileAndVerify(comp, expectedOutput: "10 20 False").VerifyDiagnostics();
        }
 
        // Test constructor forwarding works for structs
        [WorkItem(540896, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/540896")]
        [Fact]
        public void TestConstructorStruct()
        {
            var text = @"
struct  Goo
{
    public Goo(int x) : this(5, 6)
    {
    }
    public Goo(int x, int y) 
    {
        m_x = x;
        m_y = y;
    }
    public int m_x;
    public int m_y;
    public static void Main()
    { }
}
";
            CompileAndVerify(text).VerifyDiagnostics();
        }
 
        // Calling struct default constructor in another constructor
        [WorkItem(540896, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/540896")]
        [Fact]
        public void TestConstructorStruct02()
        {
            var text = @"
public struct Struct
{
    public int x;
    public Struct(int x) : this()
    {
        this.x = x;
    }
    public static void Main()
    {
    }
}
";
            CompileAndVerify(text).VerifyDiagnostics();
        }
 
        // Test constructor forwarding works for structs
        [WorkItem(540896, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/540896")]
        [Fact]
        public void TestConstructorStruct03()
        {
            var text = @"
struct S
{
    public int i;
    public int j;
    public S(int x)
    {
        j = i = x;
        Init(x);
    }
    void Init(int x)
    {
    }
    public void Set(S s)
    {
        s.Copy(out this);
    }
    public void CopySelf()
    {
        this.Copy(out this);
    }
    public void Copy(out S s)
    {
        s = this;
    }
}
 
class Program
{ 
    static void Main(string[] args)
    {
        S s;
        s.i = 0;
        s.j = 1;
        S s2 = s;
        s2.i = 2;
        s.Set(s2);
        System.Console.Write(s.i);
        s.CopySelf();
        System.Console.Write(s.i);
    }
}
";
            CompileAndVerify(text, expectedOutput: "22").VerifyDiagnostics();
        }
 
        // Overriding base System.Object methods on struct
        [WorkItem(20496, "https://github.com/dotnet/roslyn/issues/20496")]
        [WorkItem(540990, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/540990")]
        [ClrOnlyFact(ClrOnlyReason.MemberOrder)]
        public void TestOverridingBaseConstructorStruct()
        {
            var text = @"
using System;
public struct Gen<T>
{
    public override bool Equals(object obj)
    {
        Console.WriteLine(""Gen{0}::Equals"", typeof(T));
        return base.Equals(obj);
    }
    public override int GetHashCode()
    {
        Console.WriteLine(""Gen{0}::GetHashCode"", typeof(T));
        return base.GetHashCode();
    }
    public override string ToString()
    {
        Console.WriteLine(""Gen{0}::ToString"", typeof(T));
        return base.ToString();
    }
}
public struct S
{
    public override bool Equals(object obj)
    {
        Console.WriteLine(""S::Equals"");
        return base.Equals(obj);
    }
    public override int GetHashCode()
    {
        Console.WriteLine(""S::GetHashCode"");
        return base.GetHashCode();
    }
    public override string ToString()
    {
        Console.WriteLine(""S::ToString"");
        return base.ToString();
    }
}
public class Test
{
    public static int counter = 0;
    public static bool result = true;
    public static void Eval(bool exp)
    {
        counter++;
        if (!exp)
        {
            result = exp;
            Console.WriteLine(""Test Failed at location: "" + counter);
        }
    }
    public static void Main()
    {
        Gen<int> gInt = new Gen<int>();
        Test.Eval(gInt.Equals(null) == false);
        Test.Eval(gInt.GetHashCode() == gInt.GetHashCode());
        Test.Eval(gInt.ToString() == ""Gen`1[System.Int32]"");
        Gen<object> gObject = new Gen<object>();
        Test.Eval(gObject.Equals(null) == false);
        Test.Eval(gObject.GetHashCode() == gObject.GetHashCode());
        Test.Eval(gObject.ToString() == ""Gen`1[System.Object]"");
        S s = new S();
        Test.Eval(s.Equals(null) == false);
        Test.Eval(s.GetHashCode() == s.GetHashCode());
        Test.Eval(s.ToString() == ""S"");
    }
}
";
            var expectedOutput = @"GenSystem.Int32::Equals
GenSystem.Int32::GetHashCode
GenSystem.Int32::GetHashCode
GenSystem.Int32::ToString
GenSystem.Object::Equals
GenSystem.Object::GetHashCode
GenSystem.Object::GetHashCode
GenSystem.Object::ToString
S::Equals
S::GetHashCode
S::GetHashCode
S::ToString";
            CompileAndVerify(text, expectedOutput: expectedOutput);
        }
 
        // Test constructor for generic struct
        [WorkItem(540993, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/540993")]
        [Fact]
        public void TestConstructorForGenericStruct()
        {
            var text = @"
using System;
struct C<T>
{
    public int num;
    public int Goo1()
    {
        return this.num;
    }
}
class Test
{
    static void Main(string[] args)
    {
        C<object> c;
        c.num = 1;
        bool verify = c.Goo1() == 1;
        Console.WriteLine(verify);
    }
}
";
            var expectedOutput = @"True";
            CompileAndVerify(text, expectedOutput: expectedOutput);
        }
 
        // Assign to decimal in struct constructor
        [WorkItem(540994, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/540994")]
        [Fact]
        public void TestAssigntoDecimalInStructConstructor()
        {
            var text = @"
using System;
public struct Struct
{
    public decimal Price;
    public Struct(decimal price)
    {
        Price = price;
    }
}
class Test
{
    public static void Main()
    {
    }
}
";
            var expectedIL = @"{
  // Code size        8 (0x8)
  .maxstack  2
  IL_0000:  ldarg.0
  IL_0001:  ldarg.1
  IL_0002:  stfld      ""decimal Struct.Price""
  IL_0007:  ret
}";
            CompileAndVerify(text).VerifyIL("Struct..ctor(decimal)", expectedIL);
        }
 
        [Fact]
        public void RetargetedSynthesizedStructConstructor()
        {
            var oldMsCorLib = Net40.References.mscorlib;
 
            var c1 = CSharpCompilation.Create("C1",
                new[] { Parse(@"public struct S { }") },
                new[] { oldMsCorLib },
                TestOptions.ReleaseDll);
 
            var c2 = CSharpCompilation.Create("C2",
                new[] { Parse(@"public class C { void M() { S s = new S(); System.Console.WriteLine(s);} }") },
                new[] { MscorlibRef, new CSharpCompilationReference(c1) },
                TestOptions.ReleaseDll);
 
            var c1AsmRef = c2.GetReferencedAssemblySymbol(new CSharpCompilationReference(c1));
 
            Assert.NotSame(c1.Assembly, c1AsmRef);
 
            var mscorlibAssembly = c2.GetReferencedAssemblySymbol(MscorlibRef);
 
            Assert.NotSame(mscorlibAssembly, c1.GetReferencedAssemblySymbol(oldMsCorLib));
 
            var @struct = c2.GlobalNamespace.GetMember<RetargetingNamedTypeSymbol>("S");
            var method = (RetargetingMethodSymbol)@struct.GetMembers().Single();
 
            Assert.True(method.IsDefaultValueTypeConstructor());
 
            //TODO (tomat)
            CompileAndVerify(c2).VerifyIL("C.M", @"
{
  // Code size       20 (0x14)
  .maxstack  1
  .locals init (S V_0)
  IL_0000:  ldloca.s   V_0
  IL_0002:  initobj    ""S""
  IL_0008:  ldloc.0
  IL_0009:  box        ""S""
  IL_000e:  call       ""void System.Console.WriteLine(object)""
  IL_0013:  ret
}");
        }
 
        [Fact]
        public void SubstitutedSynthesizedStructConstructor()
        {
            string text = @"
public struct S<T>
{
}
 
public class C 
{ 
    void M() 
    { 
        S<int> s = new S<int>(); 
        System.Console.WriteLine(s);
    }
}
";
 
            CompileAndVerify(text).VerifyIL("C.M", @"
{
  // Code size       20 (0x14)
  .maxstack  1
  .locals init (S<int> V_0)
  IL_0000:  ldloca.s   V_0
  IL_0002:  initobj    ""S<int>""
  IL_0008:  ldloc.0
  IL_0009:  box        ""S<int>""
  IL_000e:  call       ""void System.Console.WriteLine(object)""
  IL_0013:  ret
}");
        }
 
        [Fact]
        public void PublicParameterlessConstructorInMetadata()
        {
            string ilSource = @"
.class public sequential ansi sealed beforefieldinit S
       extends [mscorlib]System.ValueType
{
  .pack 0
  .size 1
 
  .method public hidebysig specialname rtspecialname 
        instance void  .ctor() cil managed
  {
    ret
  }
}
";
 
            string csharpSource = @"
public class C 
{ 
    void M() 
    { 
        S s = new S();
        System.Console.WriteLine(s);
        s = default(S);
        System.Console.WriteLine(s);
    }
}
";
 
            // Calls constructor (vs initobj), then initobj
            var compilation = CreateCompilationWithILAndMscorlib40(csharpSource, ilSource);
            // TODO (tomat)
            CompileAndVerify(compilation).VerifyIL("C.M", @"
{
  // Code size       35 (0x23)
  .maxstack  1
  .locals init (S V_0)
  IL_0000:  newobj     ""S..ctor()""
  IL_0005:  box        ""S""
  IL_000a:  call       ""void System.Console.WriteLine(object)""
  IL_000f:  ldloca.s   V_0
  IL_0011:  initobj    ""S""
  IL_0017:  ldloc.0
  IL_0018:  box        ""S""
  IL_001d:  call       ""void System.Console.WriteLine(object)""
  IL_0022:  ret
}");
        }
 
        [WorkItem(541309, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/541309")]
        [Fact]
        public void PrivateParameterlessConstructorInMetadata()
        {
            string ilSource = @"
.class public sequential ansi sealed beforefieldinit S
       extends [mscorlib]System.ValueType
{
  .pack 0
  .size 1
 
  .method private hidebysig specialname rtspecialname 
        instance void  .ctor() cil managed
  {
    ret
  }
}
";
 
            string csharpSource = @"
public class C 
{ 
    void M() 
    { 
        S s = new S();
        System.Console.WriteLine(s);
        s = default(S);
        System.Console.WriteLine(s);
    }
}
";
 
            // Uses initobj for both
            // CONSIDER: This is the dev10 behavior, but it seems like a bug.
            // Shouldn't there be an error for trying to call an inaccessible ctor?
            var comp = CreateCompilationWithILAndMscorlib40(csharpSource, ilSource);
            CompileAndVerify(comp).VerifyIL("C.M", @"
{
  // Code size       39 (0x27)
  .maxstack  1
  .locals init (S V_0)
  IL_0000:  ldloca.s   V_0
  IL_0002:  initobj    ""S""
  IL_0008:  ldloc.0
  IL_0009:  box        ""S""
  IL_000e:  call       ""void System.Console.WriteLine(object)""
  IL_0013:  ldloca.s   V_0
  IL_0015:  initobj    ""S""
  IL_001b:  ldloc.0
  IL_001c:  box        ""S""
  IL_0021:  call       ""void System.Console.WriteLine(object)""
  IL_0026:  ret
}");
        }
 
        [WorkItem(543934, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543934")]
        [Fact]
        public void ObjectCreationExprStructTypeInstanceFieldAssign()
        {
            var csSource = @"
public struct TestStruct
{
    public int IntI;
}
 
public class TestClass
{
    public static void Main()
    {
        new TestStruct().IntI = 3;
    }
}
";
            CreateCompilation(csSource).VerifyDiagnostics(
                // (13,9): error CS0131: The left-hand side of an assignment must be a variable, property or indexer
                Diagnostic(ErrorCode.ERR_AssgLvalueExpected, "new TestStruct().IntI")
                );
        }
 
        [WorkItem(543896, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543896")]
        [Fact]
        public void ObjectCreationExprStructTypePropertyAssign()
        {
            var csSource = @"
public struct S
{
    int n;
    public int P 
    {
        set { n = value; System.Console.WriteLine(n); } 
    }
}
public class mem033
{
    public static void Main()
    {
        new S().P = 1; // CS0131 
    }
}";
            CreateCompilation(csSource).VerifyDiagnostics(
                // (14,9): error CS0131: The left-hand side of an assignment must be a variable, property or indexer
                //         new S().P = 1; // CS0131 
                Diagnostic(ErrorCode.ERR_AssgLvalueExpected, "new S().P")
                );
        }
 
        [WorkItem(545498, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545498")]
        [Fact]
        public void StructMemberNullableTypeCausesCycle()
        {
            string source = @"
public struct X
{
    public X? recursiveFld;
}
";
            CreateCompilation(source, targetFramework: TargetFramework.Mscorlib461).VerifyDiagnostics(
                // (4,15): error CS0523: Struct member 'X.recursiveFld' of type 'X?' causes a cycle in the struct layout
                //     public X? recursiveFld;
                Diagnostic(ErrorCode.ERR_StructLayoutCycle, "recursiveFld").WithArguments("X.recursiveFld", "X?")
                );
        }
 
        [Fact]
        public void StructParameterlessCtorNotPublic()
        {
            string source = @"
public struct X
{
    private X()
    {
    }
}
 
public struct X1
{
    X1()
    {
    }
}
";
            CreateCompilation(source, parseOptions: TestOptions.Regular9).VerifyDiagnostics(
                // (4,13): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater.
                //     private X()
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "X").WithArguments("parameterless struct constructors", "10.0").WithLocation(4, 13),
                // (4,13): error CS8938: The parameterless struct constructor must be 'public'.
                //     private X()
                Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "X").WithLocation(4, 13),
                // (11,5): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater.
                //     X1()
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "X1").WithArguments("parameterless struct constructors", "10.0").WithLocation(11, 5),
                // (11,5): error CS8938: The parameterless struct constructor must be 'public'.
                //     X1()
                Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "X1").WithLocation(11, 5));
 
            CreateCompilation(source).VerifyDiagnostics(
                // (4,13): error CS8918: The parameterless struct constructor must be 'public'.
                //     private X()
                Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "X").WithLocation(4, 13),
                // (11,5): error CS8918: The parameterless struct constructor must be 'public'.
                //     X1()
                Diagnostic(ErrorCode.ERR_NonPublicParameterlessStructConstructor, "X1").WithLocation(11, 5));
        }
 
        [Fact]
        public void StructNonAutoPropertyInitializer()
        {
            var text = @"struct S
{
    public int I { get { throw null; } set {} } = 9;
}";
 
            var comp = CreateCompilation(text, parseOptions: TestOptions.Regular9);
            comp.VerifyDiagnostics(
                // (1,8): error CS8983: A 'struct' with field initializers must include an explicitly declared constructor.
                // struct S
                Diagnostic(ErrorCode.ERR_StructHasInitializersAndNoDeclaredConstructor, "S").WithLocation(1, 8),
                // (3,16): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater.
                //     public int I { get { throw null; } set {} } = 9;
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "I").WithArguments("struct field initializers", "10.0").WithLocation(3, 16),
                // (3,16): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
                //     public int I { get { throw null; } set {} } = 9;
                Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "I").WithLocation(3, 16));
 
            comp = CreateCompilation(text);
            comp.VerifyDiagnostics(
                // (1,8): error CS8983: A 'struct' with field initializers must include an explicitly declared constructor.
                // struct S
                Diagnostic(ErrorCode.ERR_StructHasInitializersAndNoDeclaredConstructor, "S").WithLocation(1, 8),
                // (3,16): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
                //     public int I { get { throw null; } set {} } = 9;
                Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "I").WithLocation(3, 16));
        }
    }
}