|
// 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.
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
public class RecordTests : CompilingTestBase
{
private static CSharpCompilation CreateCompilation(CSharpTestSource source)
=> CSharpTestBase.CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular9);
private CompilationVerifier CompileAndVerify(CSharpTestSource src, string? expectedOutput = null)
=> base.CompileAndVerify(new[] { src, IsExternalInitTypeDefinition },
expectedOutput: expectedOutput,
parseOptions: TestOptions.Regular9,
// init-only fails verification
verify: Verification.FailsPEVerify);
[Fact]
public void GeneratedConstructor()
{
var comp = CreateCompilation(@"record C(int x, string y);");
comp.VerifyDiagnostics();
var c = comp.GlobalNamespace.GetTypeMember("C");
var ctor = (MethodSymbol)c.GetMembers(".ctor")[0];
Assert.Equal(2, ctor.ParameterCount);
var x = ctor.Parameters[0];
Assert.Equal(SpecialType.System_Int32, x.Type.SpecialType);
Assert.Equal("x", x.Name);
var y = ctor.Parameters[1];
Assert.Equal(SpecialType.System_String, y.Type.SpecialType);
Assert.Equal("y", y.Name);
}
[Fact]
public void GeneratedConstructorDefaultValues()
{
var comp = CreateCompilation(@"record C<T>(int x, T t = default);");
comp.VerifyDiagnostics();
var c = comp.GlobalNamespace.GetTypeMember("C");
Assert.Equal(1, c.Arity);
var ctor = (MethodSymbol)c.GetMembers(".ctor")[0];
Assert.Equal(0, ctor.Arity);
Assert.Equal(2, ctor.ParameterCount);
var x = ctor.Parameters[0];
Assert.Equal(SpecialType.System_Int32, x.Type.SpecialType);
Assert.Equal("x", x.Name);
var t = ctor.Parameters[1];
Assert.Equal(c.TypeParameters[0], t.Type);
Assert.Equal("t", t.Name);
}
[Fact]
public void RecordExistingConstructor1()
{
var comp = CreateCompilation(@"
record C(int x, string y)
{
public C(int a, string b)
{
}
}");
comp.VerifyDiagnostics(
// (4,12): error CS0111: Type 'C' already defines a member called 'C' with the same parameter types
// public C(int a, string b)
Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "C").WithArguments("C", "C").WithLocation(4, 12),
// (4,12): error CS8862: A constructor declared in a type with parameter list must have 'this' constructor initializer.
// public C(int a, string b)
Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "C").WithLocation(4, 12)
);
var c = comp.GlobalNamespace.GetTypeMember("C");
var ctor = (MethodSymbol)c.GetMembers(".ctor")[2];
Assert.Equal(2, ctor.ParameterCount);
var a = ctor.Parameters[0];
Assert.Equal(SpecialType.System_Int32, a.Type.SpecialType);
Assert.Equal("a", a.Name);
var b = ctor.Parameters[1];
Assert.Equal(SpecialType.System_String, b.Type.SpecialType);
Assert.Equal("b", b.Name);
}
[Fact]
public void RecordExistingConstructor01()
{
var comp = CreateCompilation(@"
record C(int x, string y)
{
public C(int a, int b) // overload
{
}
}");
comp.VerifyDiagnostics(
// (4,12): error CS8862: A constructor declared in a type with parameter list must have 'this' constructor initializer.
// public C(int a, int b) // overload
Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "C").WithLocation(4, 12)
);
var c = comp.GlobalNamespace.GetTypeMember("C");
var ctors = c.GetMembers(".ctor");
Assert.Equal(3, ctors.Length);
foreach (MethodSymbol ctor in ctors)
{
if (ctor.ParameterCount == 2)
{
var p1 = ctor.Parameters[0];
Assert.Equal(SpecialType.System_Int32, p1.Type.SpecialType);
var p2 = ctor.Parameters[1];
if (ctor is SynthesizedPrimaryConstructor)
{
Assert.Equal("x", p1.Name);
Assert.Equal("y", p2.Name);
Assert.Equal(SpecialType.System_String, p2.Type.SpecialType);
}
else
{
Assert.Equal("a", p1.Name);
Assert.Equal("b", p2.Name);
Assert.Equal(SpecialType.System_Int32, p2.Type.SpecialType);
}
}
else
{
Assert.Equal(1, ctor.ParameterCount);
Assert.True(c.Equals(ctor.Parameters[0].Type, TypeCompareKind.ConsiderEverything));
}
}
}
[Fact]
public void GeneratedProperties()
{
var comp = CreateCompilation("record C(int x, int y);");
comp.VerifyDiagnostics();
var c = comp.GlobalNamespace.GetTypeMember("C");
var x = (SourcePropertySymbolBase)c.GetProperty("x");
Assert.NotNull(x.GetMethod);
Assert.Equal(MethodKind.PropertyGet, x.GetMethod!.MethodKind);
Assert.Equal(SpecialType.System_Int32, x.Type.SpecialType);
Assert.False(x.IsReadOnly);
Assert.False(x.IsWriteOnly);
Assert.False(x.IsImplicitlyDeclared);
Assert.Equal(Accessibility.Public, x.DeclaredAccessibility);
Assert.False(x.IsVirtual);
Assert.False(x.IsStatic);
Assert.Equal(c, x.ContainingType);
Assert.Equal(c, x.ContainingSymbol);
var backing = x.BackingField;
Assert.Equal(x, backing.AssociatedSymbol);
Assert.Equal(c, backing.ContainingSymbol);
Assert.Equal(c, backing.ContainingType);
Assert.True(backing.IsImplicitlyDeclared);
var getAccessor = x.GetMethod;
Assert.Equal(x, getAccessor.AssociatedSymbol);
Assert.True(getAccessor.IsImplicitlyDeclared);
Assert.Equal(c, getAccessor.ContainingSymbol);
Assert.Equal(c, getAccessor.ContainingType);
Assert.Equal(Accessibility.Public, getAccessor.DeclaredAccessibility);
var setAccessor = x.SetMethod;
Assert.Equal(x, setAccessor!.AssociatedSymbol);
Assert.True(setAccessor.IsImplicitlyDeclared);
Assert.Equal(c, setAccessor.ContainingSymbol);
Assert.Equal(c, setAccessor.ContainingType);
Assert.Equal(Accessibility.Public, setAccessor.DeclaredAccessibility);
Assert.True(setAccessor.IsInitOnly);
var y = (SourcePropertySymbolBase)c.GetProperty("y");
Assert.NotNull(y.GetMethod);
Assert.Equal(MethodKind.PropertyGet, y.GetMethod!.MethodKind);
Assert.Equal(SpecialType.System_Int32, y.Type.SpecialType);
Assert.False(y.IsReadOnly);
Assert.False(y.IsWriteOnly);
Assert.False(y.IsImplicitlyDeclared);
Assert.Equal(Accessibility.Public, y.DeclaredAccessibility);
Assert.False(x.IsVirtual);
Assert.False(x.IsStatic);
Assert.Equal(c, y.ContainingType);
Assert.Equal(c, y.ContainingSymbol);
backing = y.BackingField;
Assert.Equal(y, backing.AssociatedSymbol);
Assert.Equal(c, backing.ContainingSymbol);
Assert.Equal(c, backing.ContainingType);
Assert.True(backing.IsImplicitlyDeclared);
getAccessor = y.GetMethod;
Assert.Equal(y, getAccessor.AssociatedSymbol);
Assert.True(getAccessor.IsImplicitlyDeclared);
Assert.Equal(c, getAccessor.ContainingSymbol);
Assert.Equal(c, getAccessor.ContainingType);
setAccessor = y.SetMethod;
Assert.Equal(y, setAccessor!.AssociatedSymbol);
Assert.True(setAccessor.IsImplicitlyDeclared);
Assert.Equal(c, setAccessor.ContainingSymbol);
Assert.Equal(c, setAccessor.ContainingType);
Assert.Equal(Accessibility.Public, setAccessor.DeclaredAccessibility);
Assert.True(setAccessor.IsInitOnly);
}
[Fact]
public void RecordEquals_01()
{
var comp = CreateCompilation(@"
record C(int X, int Y)
{
public bool Equals(C c) => throw null;
public override bool Equals(object o) => false;
}
");
comp.VerifyDiagnostics(
// (4,17): error CS8872: 'C.Equals(C)' must allow overriding because the containing record is not sealed.
// public bool Equals(C c) => throw null;
Diagnostic(ErrorCode.ERR_NotOverridableAPIInRecord, "Equals").WithArguments("C.Equals(C)").WithLocation(4, 17),
// (4,17): warning CS8851: 'C' defines 'Equals' but not 'GetHashCode'
// public bool Equals(C c) => throw null;
Diagnostic(ErrorCode.WRN_RecordEqualsWithoutGetHashCode, "Equals").WithArguments("C").WithLocation(4, 17),
// (5,26): error CS0111: Type 'C' already defines a member called 'Equals' with the same parameter types
// public override bool Equals(object o) => false;
Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "Equals").WithArguments("Equals", "C").WithLocation(5, 26)
);
comp = CreateCompilation(@"
record C
{
public int Equals(object o) => throw null;
}
record D : C
{
}
");
comp.VerifyDiagnostics(
// (4,16): warning CS0114: 'C.Equals(object)' hides inherited member 'object.Equals(object)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.
// public int Equals(object o) => throw null;
Diagnostic(ErrorCode.WRN_NewOrOverrideExpected, "Equals").WithArguments("C.Equals(object)", "object.Equals(object)").WithLocation(4, 16),
// (4,16): error CS0111: Type 'C' already defines a member called 'Equals' with the same parameter types
// public int Equals(object o) => throw null;
Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "Equals").WithArguments("Equals", "C").WithLocation(4, 16)
);
CompileAndVerify(@"
using System;
record C(int X, int Y)
{
public static void Main()
{
object c = new C(0, 0);
Console.WriteLine(c.Equals(c));
}
public virtual bool Equals(C c) => false;
}", expectedOutput: "False").VerifyDiagnostics(
// (10,25): warning CS8851: 'C' defines 'Equals' but not 'GetHashCode'
// public virtual bool Equals(C c) => false;
Diagnostic(ErrorCode.WRN_RecordEqualsWithoutGetHashCode, "Equals").WithArguments("C").WithLocation(10, 25)
);
}
[Fact]
public void RecordEquals_02()
{
CompileAndVerify(@"
using System;
record C(int X, int Y)
{
public static void Main()
{
object c = new C(1, 1);
var c2 = new C(1, 1);
Console.WriteLine(c.Equals(c));
Console.WriteLine(c.Equals(c2));
}
}", expectedOutput: @"True
True").VerifyDiagnostics();
}
[Fact]
public void RecordEquals_03()
{
var verifier = CompileAndVerify(@"
using System;
sealed record C(int X, int Y)
{
public static void Main()
{
object c = new C(0, 0);
var c2 = new C(0, 0);
var c3 = new C(1, 1);
Console.WriteLine(c.Equals(c2));
Console.WriteLine(c.Equals(c3));
}
public bool Equals(C c) => X == c.X && Y == c.Y;
}", expectedOutput: @"True
False").VerifyDiagnostics(
// (13,17): warning CS8851: 'C' defines 'Equals' but not 'GetHashCode'
// public bool Equals(C c) => X == c.X && Y == c.Y;
Diagnostic(ErrorCode.WRN_RecordEqualsWithoutGetHashCode, "Equals").WithArguments("C").WithLocation(13, 17)
);
verifier.VerifyIL("C.Equals(object)", @"
{
// Code size 13 (0xd)
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: isinst ""C""
IL_0007: call ""bool C.Equals(C)""
IL_000c: ret
}");
verifier.VerifyIL("C.Equals(C)", @"
{
// Code size 31 (0x1f)
.maxstack 2
IL_0000: ldarg.0
IL_0001: call ""int C.X.get""
IL_0006: ldarg.1
IL_0007: callvirt ""int C.X.get""
IL_000c: bne.un.s IL_001d
IL_000e: ldarg.0
IL_000f: call ""int C.Y.get""
IL_0014: ldarg.1
IL_0015: callvirt ""int C.Y.get""
IL_001a: ceq
IL_001c: ret
IL_001d: ldc.i4.0
IL_001e: ret
}");
}
[Fact]
public void RecordEquals_04()
{
var verifier = CompileAndVerify(@"
using System;
record C(int X, int Y)
{
public static void Main()
{
object c = new C(0, 0);
var c2 = new C(0, 0);
var c3 = new C(1, 1);
Console.WriteLine(c.Equals(c2));
Console.WriteLine(c.Equals(c3));
}
}", expectedOutput: @"True
False").VerifyDiagnostics();
verifier.VerifyIL("C.Equals(object)", @"
{
// Code size 13 (0xd)
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: isinst ""C""
IL_0007: callvirt ""bool C.Equals(C)""
IL_000c: ret
}");
verifier.VerifyIL("C.Equals(C)", @"
{
// Code size 77 (0x4d)
.maxstack 3
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: beq.s IL_004b
IL_0004: ldarg.1
IL_0005: brfalse.s IL_0049
IL_0007: ldarg.0
IL_0008: callvirt ""System.Type C.EqualityContract.get""
IL_000d: ldarg.1
IL_000e: callvirt ""System.Type C.EqualityContract.get""
IL_0013: call ""bool System.Type.op_Equality(System.Type, System.Type)""
IL_0018: brfalse.s IL_0049
IL_001a: call ""System.Collections.Generic.EqualityComparer<int> System.Collections.Generic.EqualityComparer<int>.Default.get""
IL_001f: ldarg.0
IL_0020: ldfld ""int C.<X>k__BackingField""
IL_0025: ldarg.1
IL_0026: ldfld ""int C.<X>k__BackingField""
IL_002b: callvirt ""bool System.Collections.Generic.EqualityComparer<int>.Equals(int, int)""
IL_0030: brfalse.s IL_0049
IL_0032: call ""System.Collections.Generic.EqualityComparer<int> System.Collections.Generic.EqualityComparer<int>.Default.get""
IL_0037: ldarg.0
IL_0038: ldfld ""int C.<Y>k__BackingField""
IL_003d: ldarg.1
IL_003e: ldfld ""int C.<Y>k__BackingField""
IL_0043: callvirt ""bool System.Collections.Generic.EqualityComparer<int>.Equals(int, int)""
IL_0048: ret
IL_0049: ldc.i4.0
IL_004a: ret
IL_004b: ldc.i4.1
IL_004c: ret
}");
}
[Fact]
public void RecordEquals_06()
{
var verifier = CompileAndVerify(@"
using System;
record C(int X, int Y)
{
public static void Main()
{
var c = new C(0, 0);
object c2 = null;
C c3 = null;
Console.WriteLine(c.Equals(c2));
Console.WriteLine(c.Equals(c3));
}
}", expectedOutput: @"False
False").VerifyDiagnostics();
}
[Fact]
public void RecordEquals_07()
{
var verifier = CompileAndVerify(@"
using System;
record C(int[] X, string Y)
{
public static void Main()
{
var arr = new[] {1, 2};
var c = new C(arr, ""abc"");
var c2 = new C(new[] {1, 2}, ""abc"");
var c3 = new C(arr, ""abc"");
Console.WriteLine(c.Equals(c2));
Console.WriteLine(c.Equals(c3));
}
}", expectedOutput: @"False
True").VerifyDiagnostics();
}
[Fact]
public void RecordEquals_08()
{
var verifier = CompileAndVerify(@"
using System;
record C(int X, int Y)
{
public int Z;
public static void Main()
{
var c = new C(1, 2);
c.Z = 3;
var c2 = new C(1, 2);
c2.Z = 4;
Console.WriteLine(c.Equals(c2));
Console.WriteLine(c.Equals((object)c2));
c2.Z = 3;
Console.WriteLine(c.Equals(c2));
Console.WriteLine(c.Equals((object)c2));
}
}", expectedOutput: @"False
False
True
True").VerifyDiagnostics();
verifier.VerifyIL("C.Equals(C)", @"
{
// Code size 101 (0x65)
.maxstack 3
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: beq.s IL_0063
IL_0004: ldarg.1
IL_0005: brfalse.s IL_0061
IL_0007: ldarg.0
IL_0008: callvirt ""System.Type C.EqualityContract.get""
IL_000d: ldarg.1
IL_000e: callvirt ""System.Type C.EqualityContract.get""
IL_0013: call ""bool System.Type.op_Equality(System.Type, System.Type)""
IL_0018: brfalse.s IL_0061
IL_001a: call ""System.Collections.Generic.EqualityComparer<int> System.Collections.Generic.EqualityComparer<int>.Default.get""
IL_001f: ldarg.0
IL_0020: ldfld ""int C.<X>k__BackingField""
IL_0025: ldarg.1
IL_0026: ldfld ""int C.<X>k__BackingField""
IL_002b: callvirt ""bool System.Collections.Generic.EqualityComparer<int>.Equals(int, int)""
IL_0030: brfalse.s IL_0061
IL_0032: call ""System.Collections.Generic.EqualityComparer<int> System.Collections.Generic.EqualityComparer<int>.Default.get""
IL_0037: ldarg.0
IL_0038: ldfld ""int C.<Y>k__BackingField""
IL_003d: ldarg.1
IL_003e: ldfld ""int C.<Y>k__BackingField""
IL_0043: callvirt ""bool System.Collections.Generic.EqualityComparer<int>.Equals(int, int)""
IL_0048: brfalse.s IL_0061
IL_004a: call ""System.Collections.Generic.EqualityComparer<int> System.Collections.Generic.EqualityComparer<int>.Default.get""
IL_004f: ldarg.0
IL_0050: ldfld ""int C.Z""
IL_0055: ldarg.1
IL_0056: ldfld ""int C.Z""
IL_005b: callvirt ""bool System.Collections.Generic.EqualityComparer<int>.Equals(int, int)""
IL_0060: ret
IL_0061: ldc.i4.0
IL_0062: ret
IL_0063: ldc.i4.1
IL_0064: ret
}");
}
[Fact]
public void RecordEquals_09()
{
var verifier = CompileAndVerify(@"
using System;
record C(int X, int Y)
{
public int Z { get; set; }
public static void Main()
{
var c = new C(1, 2);
c.Z = 3;
var c2 = new C(1, 2);
c2.Z = 4;
Console.WriteLine(c.Equals(c2));
Console.WriteLine(c.Equals((object)c2));
c2.Z = 3;
Console.WriteLine(c.Equals(c2));
Console.WriteLine(c.Equals((object)c2));
}
}", expectedOutput: @"False
False
True
True").VerifyDiagnostics();
}
[Fact]
public void RecordEquals_10()
{
var verifier = CompileAndVerify(@"
using System;
record C(int X, int Y)
{
public static int Z;
public static void Main()
{
var c = new C(1, 2);
C.Z = 3;
var c2 = new C(1, 2);
C.Z = 4;
Console.WriteLine(c.Equals(c2));
Console.WriteLine(c.Equals((object)c2));
C.Z = 3;
Console.WriteLine(c.Equals(c2));
Console.WriteLine(c.Equals((object)c2));
}
}", expectedOutput: @"True
True
True
True").VerifyDiagnostics();
verifier.VerifyIL("C.Equals(C)", @"
{
// Code size 77 (0x4d)
.maxstack 3
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: beq.s IL_004b
IL_0004: ldarg.1
IL_0005: brfalse.s IL_0049
IL_0007: ldarg.0
IL_0008: callvirt ""System.Type C.EqualityContract.get""
IL_000d: ldarg.1
IL_000e: callvirt ""System.Type C.EqualityContract.get""
IL_0013: call ""bool System.Type.op_Equality(System.Type, System.Type)""
IL_0018: brfalse.s IL_0049
IL_001a: call ""System.Collections.Generic.EqualityComparer<int> System.Collections.Generic.EqualityComparer<int>.Default.get""
IL_001f: ldarg.0
IL_0020: ldfld ""int C.<X>k__BackingField""
IL_0025: ldarg.1
IL_0026: ldfld ""int C.<X>k__BackingField""
IL_002b: callvirt ""bool System.Collections.Generic.EqualityComparer<int>.Equals(int, int)""
IL_0030: brfalse.s IL_0049
IL_0032: call ""System.Collections.Generic.EqualityComparer<int> System.Collections.Generic.EqualityComparer<int>.Default.get""
IL_0037: ldarg.0
IL_0038: ldfld ""int C.<Y>k__BackingField""
IL_003d: ldarg.1
IL_003e: ldfld ""int C.<Y>k__BackingField""
IL_0043: callvirt ""bool System.Collections.Generic.EqualityComparer<int>.Equals(int, int)""
IL_0048: ret
IL_0049: ldc.i4.0
IL_004a: ret
IL_004b: ldc.i4.1
IL_004c: ret
}");
}
[Fact]
public void RecordEquals_11()
{
var verifier = CompileAndVerify(@"
using System;
using System.Collections.Generic;
record C(int X, int Y)
{
static Dictionary<C, int> s_dict = new Dictionary<C, int>();
public int Z { get => s_dict[this]; set => s_dict[this] = value; }
public static void Main()
{
var c = new C(1, 2);
c.Z = 3;
var c2 = new C(1, 2);
c2.Z = 4;
Console.WriteLine(c.Equals(c2));
Console.WriteLine(c.Equals((object)c2));
c2.Z = 3;
Console.WriteLine(c.Equals(c2));
Console.WriteLine(c.Equals((object)c2));
}
}", expectedOutput: @"True
True
True
True");
verifier.VerifyIL("C.Equals(C)", @"
{
// Code size 77 (0x4d)
.maxstack 3
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: beq.s IL_004b
IL_0004: ldarg.1
IL_0005: brfalse.s IL_0049
IL_0007: ldarg.0
IL_0008: callvirt ""System.Type C.EqualityContract.get""
IL_000d: ldarg.1
IL_000e: callvirt ""System.Type C.EqualityContract.get""
IL_0013: call ""bool System.Type.op_Equality(System.Type, System.Type)""
IL_0018: brfalse.s IL_0049
IL_001a: call ""System.Collections.Generic.EqualityComparer<int> System.Collections.Generic.EqualityComparer<int>.Default.get""
IL_001f: ldarg.0
IL_0020: ldfld ""int C.<X>k__BackingField""
IL_0025: ldarg.1
IL_0026: ldfld ""int C.<X>k__BackingField""
IL_002b: callvirt ""bool System.Collections.Generic.EqualityComparer<int>.Equals(int, int)""
IL_0030: brfalse.s IL_0049
IL_0032: call ""System.Collections.Generic.EqualityComparer<int> System.Collections.Generic.EqualityComparer<int>.Default.get""
IL_0037: ldarg.0
IL_0038: ldfld ""int C.<Y>k__BackingField""
IL_003d: ldarg.1
IL_003e: ldfld ""int C.<Y>k__BackingField""
IL_0043: callvirt ""bool System.Collections.Generic.EqualityComparer<int>.Equals(int, int)""
IL_0048: ret
IL_0049: ldc.i4.0
IL_004a: ret
IL_004b: ldc.i4.1
IL_004c: ret
}");
}
[Fact]
public void RecordEquals_12()
{
var verifier = CompileAndVerify(@"
using System;
record C(int X, int Y)
{
private event Action E;
public static void Main()
{
var c = new C(1, 2);
c.E = () => { };
var c2 = new C(1, 2);
c2.E = () => { };
Console.WriteLine(c.Equals(c2));
Console.WriteLine(c.Equals((object)c2));
c2.E = c.E;
Console.WriteLine(c.Equals(c2));
Console.WriteLine(c.Equals((object)c2));
}
}", expectedOutput: @"False
False
True
True").VerifyDiagnostics();
verifier.VerifyIL("C.Equals(C)", @"
{
// Code size 101 (0x65)
.maxstack 3
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: beq.s IL_0063
IL_0004: ldarg.1
IL_0005: brfalse.s IL_0061
IL_0007: ldarg.0
IL_0008: callvirt ""System.Type C.EqualityContract.get""
IL_000d: ldarg.1
IL_000e: callvirt ""System.Type C.EqualityContract.get""
IL_0013: call ""bool System.Type.op_Equality(System.Type, System.Type)""
IL_0018: brfalse.s IL_0061
IL_001a: call ""System.Collections.Generic.EqualityComparer<int> System.Collections.Generic.EqualityComparer<int>.Default.get""
IL_001f: ldarg.0
IL_0020: ldfld ""int C.<X>k__BackingField""
IL_0025: ldarg.1
IL_0026: ldfld ""int C.<X>k__BackingField""
IL_002b: callvirt ""bool System.Collections.Generic.EqualityComparer<int>.Equals(int, int)""
IL_0030: brfalse.s IL_0061
IL_0032: call ""System.Collections.Generic.EqualityComparer<int> System.Collections.Generic.EqualityComparer<int>.Default.get""
IL_0037: ldarg.0
IL_0038: ldfld ""int C.<Y>k__BackingField""
IL_003d: ldarg.1
IL_003e: ldfld ""int C.<Y>k__BackingField""
IL_0043: callvirt ""bool System.Collections.Generic.EqualityComparer<int>.Equals(int, int)""
IL_0048: brfalse.s IL_0061
IL_004a: call ""System.Collections.Generic.EqualityComparer<System.Action> System.Collections.Generic.EqualityComparer<System.Action>.Default.get""
IL_004f: ldarg.0
IL_0050: ldfld ""System.Action C.E""
IL_0055: ldarg.1
IL_0056: ldfld ""System.Action C.E""
IL_005b: callvirt ""bool System.Collections.Generic.EqualityComparer<System.Action>.Equals(System.Action, System.Action)""
IL_0060: ret
IL_0061: ldc.i4.0
IL_0062: ret
IL_0063: ldc.i4.1
IL_0064: ret
}");
}
[Fact]
public void RecordClone1()
{
var comp = CreateCompilation("record C(int x, int y);");
comp.VerifyDiagnostics();
var c = comp.GlobalNamespace.GetTypeMember("C");
var clone = c.GetMethod(WellKnownMemberNames.CloneMethodName);
Assert.Equal(0, clone.Arity);
Assert.Equal(0, clone.ParameterCount);
Assert.Equal(c, clone.ReturnType);
var ctor = (MethodSymbol)c.GetMembers(".ctor")[1];
Assert.Equal(1, ctor.ParameterCount);
Assert.True(ctor.Parameters[0].Type.Equals(c, TypeCompareKind.ConsiderEverything));
// PEVerify: Cannot change initonly field outside its .ctor.
var verifier = CompileAndVerify(comp, verify: Verification.FailsPEVerify).VerifyDiagnostics();
verifier.VerifyIL("C." + WellKnownMemberNames.CloneMethodName, @"
{
// Code size 7 (0x7)
.maxstack 1
IL_0000: ldarg.0
IL_0001: newobj ""C..ctor(C)""
IL_0006: ret
}
");
verifier.VerifyIL("C..ctor(C)", @"
{
// Code size 31 (0x1f)
.maxstack 2
IL_0000: ldarg.0
IL_0001: call ""object..ctor()""
IL_0006: ldarg.0
IL_0007: ldarg.1
IL_0008: ldfld ""int C.<x>k__BackingField""
IL_000d: stfld ""int C.<x>k__BackingField""
IL_0012: ldarg.0
IL_0013: ldarg.1
IL_0014: ldfld ""int C.<y>k__BackingField""
IL_0019: stfld ""int C.<y>k__BackingField""
IL_001e: ret
}");
}
[Fact]
public void RecordClone2_0()
{
var comp = CreateCompilation(@"
record C(int x, int y)
{
public C(C other)
{
x = other.x;
y = other.y;
}
}");
comp.VerifyDiagnostics();
var c = comp.GlobalNamespace.GetTypeMember("C");
var clone = c.GetMethod(WellKnownMemberNames.CloneMethodName);
Assert.Equal(0, clone.Arity);
Assert.Equal(0, clone.ParameterCount);
Assert.Equal(c, clone.ReturnType);
var ctor = (MethodSymbol)c.GetMembers(".ctor")[1];
Assert.Equal(1, ctor.ParameterCount);
Assert.True(ctor.Parameters[0].Type.Equals(c, TypeCompareKind.ConsiderEverything));
// PEVerify: Cannot change initonly field outside its .ctor.
var verifier = CompileAndVerify(comp, verify: Verification.FailsPEVerify).VerifyDiagnostics();
verifier.VerifyIL("C." + WellKnownMemberNames.CloneMethodName, @"
{
// Code size 7 (0x7)
.maxstack 1
IL_0000: ldarg.0
IL_0001: newobj ""C..ctor(C)""
IL_0006: ret
}
");
verifier.VerifyIL("C..ctor(C)", @"
{
// Code size 31 (0x1f)
.maxstack 2
IL_0000: ldarg.0
IL_0001: call ""object..ctor()""
IL_0006: ldarg.0
IL_0007: ldarg.1
IL_0008: callvirt ""int C.x.get""
IL_000d: call ""void C.x.init""
IL_0012: ldarg.0
IL_0013: ldarg.1
IL_0014: callvirt ""int C.y.get""
IL_0019: call ""void C.y.init""
IL_001e: ret
}
");
}
[Fact]
public void RecordClone2_0_WithThisInitializer()
{
var comp = CreateCompilation(@"
record C(int x, int y)
{
public C(C other) : this(other.x, other.y) { }
}");
comp.VerifyDiagnostics(
// (4,25): error CS8868: A copy constructor in a record must call a copy constructor of the base, or a parameterless object constructor if the record inherits from object.
// public C(C other) : this(other.x, other.y) { }
Diagnostic(ErrorCode.ERR_CopyConstructorMustInvokeBaseCopyConstructor, "this").WithLocation(4, 25)
);
}
[Fact]
[WorkItem(44781, "https://github.com/dotnet/roslyn/issues/44781")]
public void RecordClone2_1()
{
var comp = CreateCompilation(@"
record C(int x, int y)
{
public C(C other) { }
}");
comp.VerifyDiagnostics();
}
[Fact]
[WorkItem(44781, "https://github.com/dotnet/roslyn/issues/44781")]
public void RecordClone2_2()
{
var comp = CreateCompilation(@"
record C(int x, int y)
{
public C(C other) : base() { }
}");
comp.VerifyDiagnostics();
}
[Fact]
[WorkItem(44782, "https://github.com/dotnet/roslyn/issues/44782")]
public void RecordClone3()
{
var comp = CreateCompilation(@"
using System;
public record C(int x, int y)
{
public event Action E;
public int Z;
public int W = 123;
}");
comp.VerifyDiagnostics(
// (5,25): warning CS0067: The event 'C.E' is never used
// public event Action E;
Diagnostic(ErrorCode.WRN_UnreferencedEvent, "E").WithArguments("C.E").WithLocation(5, 25)
);
var c = comp.GlobalNamespace.GetTypeMember("C");
var clone = c.GetMethod(WellKnownMemberNames.CloneMethodName);
Assert.Equal(0, clone.Arity);
Assert.Equal(0, clone.ParameterCount);
Assert.Equal(c, clone.ReturnType);
var ctor = (MethodSymbol)c.GetMembers(".ctor")[1];
Assert.Equal(1, ctor.ParameterCount);
Assert.True(ctor.Parameters[0].Type.Equals(c, TypeCompareKind.ConsiderEverything));
// PEVerify: Cannot change initonly field outside its .ctor.
var verifier = CompileAndVerify(comp, verify: Verification.FailsPEVerify).VerifyDiagnostics(
// (5,25): warning CS0067: The event 'C.E' is never used
// public event Action E;
Diagnostic(ErrorCode.WRN_UnreferencedEvent, "E").WithArguments("C.E").WithLocation(5, 25)
);
verifier.VerifyIL("C." + WellKnownMemberNames.CloneMethodName, @"
{
// Code size 7 (0x7)
.maxstack 1
IL_0000: ldarg.0
IL_0001: newobj ""C..ctor(C)""
IL_0006: ret
}");
verifier.VerifyIL("C..ctor(C)", @"
{
// Code size 67 (0x43)
.maxstack 2
IL_0000: ldarg.0
IL_0001: call ""object..ctor()""
IL_0006: ldarg.0
IL_0007: ldarg.1
IL_0008: ldfld ""int C.<x>k__BackingField""
IL_000d: stfld ""int C.<x>k__BackingField""
IL_0012: ldarg.0
IL_0013: ldarg.1
IL_0014: ldfld ""int C.<y>k__BackingField""
IL_0019: stfld ""int C.<y>k__BackingField""
IL_001e: ldarg.0
IL_001f: ldarg.1
IL_0020: ldfld ""System.Action C.E""
IL_0025: stfld ""System.Action C.E""
IL_002a: ldarg.0
IL_002b: ldarg.1
IL_002c: ldfld ""int C.Z""
IL_0031: stfld ""int C.Z""
IL_0036: ldarg.0
IL_0037: ldarg.1
IL_0038: ldfld ""int C.W""
IL_003d: stfld ""int C.W""
IL_0042: ret
}
");
}
[Fact]
public void NominalRecordEquals()
{
var verifier = CompileAndVerify(@"
using System;
record C
{
private int X;
private int Y { get; set; }
private event Action E;
public static void Main()
{
var c = new C { X = 1, Y = 2 };
c.E = () => { };
var c2 = new C { X = 1, Y = 2 };
c2.E = () => { };
Console.WriteLine(c.Equals(c2));
Console.WriteLine(c.Equals((object)c2));
c2.E = c.E;
Console.WriteLine(c.Equals(c2));
Console.WriteLine(c.Equals((object)c2));
}
}", verify: Verification.Passes, expectedOutput: @"False
False
True
True").VerifyDiagnostics(
// (5,17): warning CS0414: The field 'C.X' is assigned but its value is never used
// private int X;
Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "X").WithArguments("C.X").WithLocation(5, 17)
);
verifier.VerifyIL("C.Equals(object)", @"
{
// Code size 13 (0xd)
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: isinst ""C""
IL_0007: callvirt ""bool C.Equals(C)""
IL_000c: ret
}");
verifier.VerifyIL("C.Equals(C)", @"
{
// Code size 101 (0x65)
.maxstack 3
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: beq.s IL_0063
IL_0004: ldarg.1
IL_0005: brfalse.s IL_0061
IL_0007: ldarg.0
IL_0008: callvirt ""System.Type C.EqualityContract.get""
IL_000d: ldarg.1
IL_000e: callvirt ""System.Type C.EqualityContract.get""
IL_0013: call ""bool System.Type.op_Equality(System.Type, System.Type)""
IL_0018: brfalse.s IL_0061
IL_001a: call ""System.Collections.Generic.EqualityComparer<int> System.Collections.Generic.EqualityComparer<int>.Default.get""
IL_001f: ldarg.0
IL_0020: ldfld ""int C.X""
IL_0025: ldarg.1
IL_0026: ldfld ""int C.X""
IL_002b: callvirt ""bool System.Collections.Generic.EqualityComparer<int>.Equals(int, int)""
IL_0030: brfalse.s IL_0061
IL_0032: call ""System.Collections.Generic.EqualityComparer<int> System.Collections.Generic.EqualityComparer<int>.Default.get""
IL_0037: ldarg.0
IL_0038: ldfld ""int C.<Y>k__BackingField""
IL_003d: ldarg.1
IL_003e: ldfld ""int C.<Y>k__BackingField""
IL_0043: callvirt ""bool System.Collections.Generic.EqualityComparer<int>.Equals(int, int)""
IL_0048: brfalse.s IL_0061
IL_004a: call ""System.Collections.Generic.EqualityComparer<System.Action> System.Collections.Generic.EqualityComparer<System.Action>.Default.get""
IL_004f: ldarg.0
IL_0050: ldfld ""System.Action C.E""
IL_0055: ldarg.1
IL_0056: ldfld ""System.Action C.E""
IL_005b: callvirt ""bool System.Collections.Generic.EqualityComparer<System.Action>.Equals(System.Action, System.Action)""
IL_0060: ret
IL_0061: ldc.i4.0
IL_0062: ret
IL_0063: ldc.i4.1
IL_0064: ret
}");
}
[Fact]
public void PositionalAndNominalSameEquals()
{
var v1 = CompileAndVerify(@"
using System;
record C(int X, string Y)
{
public event Action E;
}
").VerifyDiagnostics(
// (5,25): warning CS0067: The event 'C.E' is never used
// public event Action E;
Diagnostic(ErrorCode.WRN_UnreferencedEvent, "E").WithArguments("C.E").WithLocation(5, 25)
);
var v2 = CompileAndVerify(@"
using System;
record C
{
public int X { get; }
public string Y { get; }
public event Action E;
}", verify: Verification.Passes).VerifyDiagnostics(
// (7,25): warning CS0067: The event 'C.E' is never used
// public event Action E;
Diagnostic(ErrorCode.WRN_UnreferencedEvent, "E").WithArguments("C.E").WithLocation(7, 25)
);
Assert.Equal(v1.VisualizeIL("C.Equals(C)"), v2.VisualizeIL("C.Equals(C)"));
Assert.Equal(v1.VisualizeIL("C.Equals(object)"), v2.VisualizeIL("C.Equals(object)"));
}
[Fact]
public void NominalRecordMembers()
{
var comp = CreateCompilation(@"
#nullable enable
record C
{
public int X { get; init; }
public string Y { get; init; }
}");
var members = comp.GlobalNamespace.GetTypeMember("C").GetMembers();
AssertEx.Equal(new[] {
"System.Type! C.EqualityContract.get",
"System.Type! C.EqualityContract { get; }",
"System.Int32 C.X.field",
"System.Int32 C.X { get; init; }",
"System.Int32 C.X.get",
"void C.X.init",
"System.String! C.Y.field",
"System.String! C.Y { get; init; }",
"System.String! C.Y.get",
"void C.Y.init",
"System.String! C.ToString()",
"System.Boolean C." + WellKnownMemberNames.PrintMembersMethodName + "(System.Text.StringBuilder! builder)",
"System.Boolean C.operator !=(C? left, C? right)",
"System.Boolean C.operator ==(C? left, C? right)",
"System.Int32 C.GetHashCode()",
"System.Boolean C.Equals(System.Object? obj)",
"System.Boolean C.Equals(C? other)",
"C! C." + WellKnownMemberNames.CloneMethodName + "()",
"C.C(C! original)",
"C.C()",
}, members.Select(m => m.ToTestDisplayString(includeNonNullable: true)));
}
[Fact]
public void PartialTypes_01()
{
var src = @"
using System;
partial record C(int X, int Y)
{
public static void Main()
{
var c = new C(1, 2);
Console.WriteLine(c.X);
Console.WriteLine(c.Y);
}
}
partial record C(int X, int Y)
{
}
";
var comp = CreateCompilation(src);
comp.VerifyDiagnostics(
// (13,17): error CS8863: Only a single partial type declaration may have a parameter list
// partial record C(int X, int Y)
Diagnostic(ErrorCode.ERR_MultipleRecordParameterLists, "(int X, int Y)").WithLocation(13, 17)
);
Assert.Equal(new[] { "C..ctor(System.Int32 X, System.Int32 Y)", "C..ctor(C original)" }, comp.GetTypeByMetadataName("C")!.Constructors.Select(m => m.ToTestDisplayString()));
}
[Fact]
public void PartialTypes_02()
{
var src = @"
using System;
partial record C(int X, int Y)
{
public static void Main()
{
var c = new C(1, 2);
Console.WriteLine(c.X);
Console.WriteLine(c.Y);
}
}
partial record C(int X)
{
}
";
var comp = CreateCompilation(src);
comp.VerifyDiagnostics(
// (13,17): error CS8863: Only a single partial type declaration may have a parameter list
// partial record C(int X)
Diagnostic(ErrorCode.ERR_MultipleRecordParameterLists, "(int X)").WithLocation(13, 17)
);
Assert.Equal(new[] { "C..ctor(System.Int32 X, System.Int32 Y)", "C..ctor(C original)" }, comp.GetTypeByMetadataName("C")!.Constructors.Select(m => m.ToTestDisplayString()));
}
[Fact]
public void PartialTypes_03()
{
var src = @"
partial record C
{
public int X = 1;
}
partial record C(int Y);
partial record C
{
public int Z { get; } = 2;
}";
var verifier = CompileAndVerify(src).VerifyDiagnostics();
verifier.VerifyIL("C..ctor(int)", @"
{
// Code size 28 (0x1c)
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldc.i4.1
IL_0002: stfld ""int C.X""
IL_0007: ldarg.0
IL_0008: ldarg.1
IL_0009: stfld ""int C.<Y>k__BackingField""
IL_000e: ldarg.0
IL_000f: ldc.i4.2
IL_0010: stfld ""int C.<Z>k__BackingField""
IL_0015: ldarg.0
IL_0016: call ""object..ctor()""
IL_001b: ret
}");
}
[Fact]
public void PartialTypes_04_PartialBeforeModifiers()
{
var src = @"
partial public record C
{
}
";
CreateCompilation(src).VerifyDiagnostics(
// (2,1): error CS0267: The 'partial' modifier can only appear immediately before 'class', 'record', 'struct', 'interface', or a method return type.
// partial public record C
Diagnostic(ErrorCode.ERR_PartialMisplaced, "partial").WithLocation(2, 1)
);
}
[Fact]
public void DataClassAndStruct()
{
var src = @"
data class C1 { }
data class C2(int X, int Y);
data struct S1 { }
data struct S2(int X, int Y);";
var comp = CreateCompilation(src, options: TestOptions.ReleaseDll);
comp.VerifyDiagnostics(
// (2,6): error CS1001: Identifier expected
// data class C1 { }
Diagnostic(ErrorCode.ERR_IdentifierExpected, "class").WithLocation(2, 6),
// (2,6): error CS1002: ; expected
// data class C1 { }
Diagnostic(ErrorCode.ERR_SemicolonExpected, "class").WithLocation(2, 6),
// (3,1): error CS8803: Top-level statements must precede namespace and type declarations.
// data class C2(int X, int Y);
Diagnostic(ErrorCode.ERR_TopLevelStatementAfterNamespaceOrType, "data ").WithLocation(3, 1),
// (3,6): error CS1001: Identifier expected
// data class C2(int X, int Y);
Diagnostic(ErrorCode.ERR_IdentifierExpected, "class").WithLocation(3, 6),
// (3,6): error CS1002: ; expected
// data class C2(int X, int Y);
Diagnostic(ErrorCode.ERR_SemicolonExpected, "class").WithLocation(3, 6),
// (3,19): warning CS9113: Parameter 'X' is unread.
// data class C2(int X, int Y);
Diagnostic(ErrorCode.WRN_UnreadPrimaryConstructorParameter, "X").WithArguments("X").WithLocation(3, 19),
// (3,26): warning CS9113: Parameter 'Y' is unread.
// data class C2(int X, int Y);
Diagnostic(ErrorCode.WRN_UnreadPrimaryConstructorParameter, "Y").WithArguments("Y").WithLocation(3, 26),
// (5,20): warning CS9113: Parameter 'X' is unread.
// data struct S2(int X, int Y);
Diagnostic(ErrorCode.WRN_UnreadPrimaryConstructorParameter, "X").WithArguments("X").WithLocation(5, 20),
// (5,27): warning CS9113: Parameter 'Y' is unread.
// data struct S2(int X, int Y);
Diagnostic(ErrorCode.WRN_UnreadPrimaryConstructorParameter, "Y").WithArguments("Y").WithLocation(5, 27),
// (4,6): error CS1001: Identifier expected
// data struct S1 { }
Diagnostic(ErrorCode.ERR_IdentifierExpected, "struct").WithLocation(4, 6),
// (4,6): error CS1002: ; expected
// data struct S1 { }
Diagnostic(ErrorCode.ERR_SemicolonExpected, "struct").WithLocation(4, 6),
// (5,6): error CS1001: Identifier expected
// data struct S2(int X, int Y);
Diagnostic(ErrorCode.ERR_IdentifierExpected, "struct").WithLocation(5, 6),
// (5,6): error CS1002: ; expected
// data struct S2(int X, int Y);
Diagnostic(ErrorCode.ERR_SemicolonExpected, "struct").WithLocation(5, 6),
// (2,1): error CS8805: Program using top-level statements must be an executable.
// data class C1 { }
Diagnostic(ErrorCode.ERR_SimpleProgramNotAnExecutable, "data ").WithLocation(2, 1),
// (2,1): error CS0246: The type or namespace name 'data' could not be found (are you missing a using directive or an assembly reference?)
// data class C1 { }
Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "data").WithArguments("data").WithLocation(2, 1),
// (3,1): error CS0246: The type or namespace name 'data' could not be found (are you missing a using directive or an assembly reference?)
// data class C2(int X, int Y);
Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "data").WithArguments("data").WithLocation(3, 1),
// (4,1): error CS0246: The type or namespace name 'data' could not be found (are you missing a using directive or an assembly reference?)
// data struct S1 { }
Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "data").WithArguments("data").WithLocation(4, 1),
// (5,1): error CS0246: The type or namespace name 'data' could not be found (are you missing a using directive or an assembly reference?)
// data struct S2(int X, int Y);
Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "data").WithArguments("data").WithLocation(5, 1)
);
}
[WorkItem(44781, "https://github.com/dotnet/roslyn/issues/44781")]
[Fact]
public void ClassInheritingFromRecord()
{
var src = @"
abstract record AbstractRecord {}
class SomeClass : AbstractRecord {}";
var comp = CreateCompilation(src);
comp.VerifyDiagnostics(
// (3,19): error CS8865: Only records may inherit from records.
// class SomeClass : AbstractRecord {}
Diagnostic(ErrorCode.ERR_BadInheritanceFromRecord, "AbstractRecord").WithLocation(3, 19)
);
}
[Fact]
public void RecordInheritance()
{
var src = @"
class A { }
record B : A { }
record C : B { }
class D : C { }
interface E : C { }
struct F : C { }
enum G : C { }";
var comp = CreateCompilation(src);
comp.VerifyDiagnostics(
// (3,8): error CS0115: 'B.EqualityContract': no suitable method found to override
// record B : A { }
Diagnostic(ErrorCode.ERR_OverrideNotExpected, "B").WithArguments("B.EqualityContract").WithLocation(3, 8),
// (3,8): error CS0115: 'B.Equals(A?)': no suitable method found to override
// record B : A { }
Diagnostic(ErrorCode.ERR_OverrideNotExpected, "B").WithArguments("B.Equals(A?)").WithLocation(3, 8),
// (3,8): error CS0115: 'B.PrintMembers(StringBuilder)': no suitable method found to override
// record B : A { }
Diagnostic(ErrorCode.ERR_OverrideNotExpected, "B").WithArguments("B.PrintMembers(System.Text.StringBuilder)").WithLocation(3, 8),
// (3,8): error CS8867: No accessible copy constructor found in base type 'A'.
// record B : A { }
Diagnostic(ErrorCode.ERR_NoCopyConstructorInBaseType, "B").WithArguments("A").WithLocation(3, 8),
// (3,12): error CS8864: Records may only inherit from object or another record
// record B : A { }
Diagnostic(ErrorCode.ERR_BadRecordBase, "A").WithLocation(3, 12),
// (5,11): error CS8865: Only records may inherit from records.
// class D : C { }
Diagnostic(ErrorCode.ERR_BadInheritanceFromRecord, "C").WithLocation(5, 11),
// (6,15): error CS0527: Type 'C' in interface list is not an interface
// interface E : C { }
Diagnostic(ErrorCode.ERR_NonInterfaceInInterfaceList, "C").WithArguments("C").WithLocation(6, 15),
// (7,12): error CS0527: Type 'C' in interface list is not an interface
// struct F : C { }
Diagnostic(ErrorCode.ERR_NonInterfaceInInterfaceList, "C").WithArguments("C").WithLocation(7, 12),
// (8,10): error CS1008: Type byte, sbyte, short, ushort, int, uint, long, or ulong expected
// enum G : C { }
Diagnostic(ErrorCode.ERR_IntegralTypeExpected, "C").WithLocation(8, 10)
);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void RecordInheritance2(bool emitReference)
{
var src = @"
public class A { }
public record B { }
public record C : B { }";
var comp = CreateCompilation(src);
var src2 = @"
record D : C { }
record E : A { }
interface F : C { }
struct G : C { }
enum H : C { }
";
var comp2 = CreateCompilation(src2,
parseOptions: TestOptions.Regular9,
references: new[] {
emitReference ? comp.EmitToImageReference() : comp.ToMetadataReference()
});
comp2.VerifyDiagnostics(
// (3,8): error CS0115: 'E.EqualityContract': no suitable method found to override
// record E : A { }
Diagnostic(ErrorCode.ERR_OverrideNotExpected, "E").WithArguments("E.EqualityContract").WithLocation(3, 8),
// (3,8): error CS0115: 'E.Equals(A?)': no suitable method found to override
// record E : A { }
Diagnostic(ErrorCode.ERR_OverrideNotExpected, "E").WithArguments("E.Equals(A?)").WithLocation(3, 8),
// (3,8): error CS0115: 'E.PrintMembers(StringBuilder)': no suitable method found to override
// record E : A { }
Diagnostic(ErrorCode.ERR_OverrideNotExpected, "E").WithArguments("E.PrintMembers(System.Text.StringBuilder)").WithLocation(3, 8),
// (3,8): error CS8867: No accessible copy constructor found in base type 'A'.
// record E : A { }
Diagnostic(ErrorCode.ERR_NoCopyConstructorInBaseType, "E").WithArguments("A").WithLocation(3, 8),
// (3,12): error CS8864: Records may only inherit from object or another record
// record E : A { }
Diagnostic(ErrorCode.ERR_BadRecordBase, "A").WithLocation(3, 12),
// (4,15): error CS0527: Type 'C' in interface list is not an interface
// interface F : C { }
Diagnostic(ErrorCode.ERR_NonInterfaceInInterfaceList, "C").WithArguments("C").WithLocation(4, 15),
// (5,12): error CS0527: Type 'C' in interface list is not an interface
// struct G : C { }
Diagnostic(ErrorCode.ERR_NonInterfaceInInterfaceList, "C").WithArguments("C").WithLocation(5, 12),
// (6,10): error CS1008: Type byte, sbyte, short, ushort, int, uint, long, or ulong expected
// enum H : C { }
Diagnostic(ErrorCode.ERR_IntegralTypeExpected, "C").WithLocation(6, 10)
);
}
[Fact]
public void GenericRecord()
{
var src = @"
using System;
record A<T>
{
public T Prop { get; init; }
}
record B : A<int>;
record C<T>(T Prop2) : A<T>;
class P
{
public static void Main()
{
var a = new A<int>() { Prop = 1 };
var a2 = a with { Prop = 2 };
Console.WriteLine(a.Prop + "" "" + a2.Prop);
var b = new B() { Prop = 3 };
var b2 = b with { Prop = 4 };
Console.WriteLine(b.Prop + "" "" + b2.Prop);
var c = new C<int>(5) { Prop = 6 };
var c2 = c with { Prop = 7, Prop2 = 8 };
Console.WriteLine(c.Prop + "" "" + c.Prop2);
Console.WriteLine(c2.Prop2 + "" "" + c2.Prop);
}
}";
CompileAndVerify(src, expectedOutput: @"
1 2
3 4
6 5
8 7").VerifyDiagnostics();
}
[Fact]
public void RecordCloneSymbol()
{
var src = @"
record R;
record R2 : R";
var comp = CreateCompilation(src);
var r = comp.GlobalNamespace.GetTypeMember("R");
var clone = (MethodSymbol)r.GetMembers(WellKnownMemberNames.CloneMethodName).Single();
Assert.False(clone.IsOverride);
Assert.True(clone.IsVirtual);
Assert.False(clone.IsAbstract);
Assert.Equal(0, clone.ParameterCount);
Assert.Equal(0, clone.Arity);
var r2 = comp.GlobalNamespace.GetTypeMember("R2");
var clone2 = (MethodSymbol)r2.GetMembers(WellKnownMemberNames.CloneMethodName).Single();
Assert.True(clone2.IsOverride);
Assert.False(clone2.IsVirtual);
Assert.False(clone2.IsAbstract);
Assert.Equal(0, clone2.ParameterCount);
Assert.Equal(0, clone2.Arity);
Assert.True(clone2.OverriddenMethod.Equals(clone, TypeCompareKind.ConsiderEverything));
}
[Fact]
public void AbstractRecordClone()
{
var src = @"
abstract record R;
abstract record R2 : R;
record R3 : R2;
abstract record R4 : R3;
record R5 : R4;
class C
{
public static void Main()
{
R r = new R3();
r = r with { };
R4 r4 = new R5();
r4 = r4 with { };
}
}";
var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition },
parseOptions: TestOptions.Regular9,
options: TestOptions.ReleaseExe);
var r = comp.GlobalNamespace.GetTypeMember("R");
var clone = (MethodSymbol)r.GetMembers(WellKnownMemberNames.CloneMethodName).Single();
Assert.False(clone.IsOverride);
Assert.False(clone.IsVirtual);
Assert.True(clone.IsAbstract);
Assert.Equal(0, clone.ParameterCount);
Assert.Equal(0, clone.Arity);
Assert.Equal("R R." + WellKnownMemberNames.CloneMethodName + "()", clone.ToTestDisplayString());
Assert.True(clone.IsImplicitlyDeclared);
var r2 = comp.GlobalNamespace.GetTypeMember("R2");
var clone2 = (MethodSymbol)r2.GetMembers(WellKnownMemberNames.CloneMethodName).Single();
Assert.True(clone2.IsOverride);
Assert.False(clone2.IsVirtual);
Assert.True(clone2.IsAbstract);
Assert.Equal(0, clone2.ParameterCount);
Assert.Equal(0, clone2.Arity);
Assert.True(clone2.OverriddenMethod.Equals(clone, TypeCompareKind.ConsiderEverything));
Assert.Equal("R R2." + WellKnownMemberNames.CloneMethodName + "()", clone2.ToTestDisplayString());
Assert.True(clone2.IsImplicitlyDeclared);
var r3 = comp.GlobalNamespace.GetTypeMember("R3");
var clone3 = (MethodSymbol)r3.GetMembers(WellKnownMemberNames.CloneMethodName).Single();
Assert.True(clone3.IsOverride);
Assert.False(clone3.IsVirtual);
Assert.False(clone3.IsAbstract);
Assert.Equal(0, clone3.ParameterCount);
Assert.Equal(0, clone3.Arity);
Assert.True(clone3.OverriddenMethod.Equals(clone2, TypeCompareKind.ConsiderEverything));
Assert.Equal("R R3." + WellKnownMemberNames.CloneMethodName + "()", clone3.ToTestDisplayString());
Assert.True(clone3.IsImplicitlyDeclared);
var r4 = comp.GlobalNamespace.GetTypeMember("R4");
var clone4 = (MethodSymbol)r4.GetMembers(WellKnownMemberNames.CloneMethodName).Single();
Assert.True(clone4.IsOverride);
Assert.False(clone4.IsVirtual);
Assert.True(clone4.IsAbstract);
Assert.Equal(0, clone4.ParameterCount);
Assert.Equal(0, clone4.Arity);
Assert.True(clone4.OverriddenMethod.Equals(clone3, TypeCompareKind.ConsiderEverything));
Assert.Equal("R R4." + WellKnownMemberNames.CloneMethodName + "()", clone4.ToTestDisplayString());
Assert.True(clone4.IsImplicitlyDeclared);
var r5 = comp.GlobalNamespace.GetTypeMember("R5");
var clone5 = (MethodSymbol)r5.GetMembers(WellKnownMemberNames.CloneMethodName).Single();
Assert.True(clone5.IsOverride);
Assert.False(clone5.IsVirtual);
Assert.False(clone5.IsAbstract);
Assert.Equal(0, clone5.ParameterCount);
Assert.Equal(0, clone5.Arity);
Assert.True(clone5.OverriddenMethod.Equals(clone4, TypeCompareKind.ConsiderEverything));
Assert.Equal("R R5." + WellKnownMemberNames.CloneMethodName + "()", clone5.ToTestDisplayString());
Assert.True(clone5.IsImplicitlyDeclared);
var verifier = CompileAndVerify(comp, expectedOutput: "", verify: Verification.Passes).VerifyDiagnostics();
verifier.VerifyIL("C.Main", @"
{
// Code size 28 (0x1c)
.maxstack 1
IL_0000: newobj ""R3..ctor()""
IL_0005: callvirt ""R R." + WellKnownMemberNames.CloneMethodName + @"()""
IL_000a: pop
IL_000b: newobj ""R5..ctor()""
IL_0010: callvirt ""R R." + WellKnownMemberNames.CloneMethodName + @"()""
IL_0015: castclass ""R4""
IL_001a: pop
IL_001b: ret
}");
}
[Fact]
[WorkItem(49286, "https://github.com/dotnet/roslyn/issues/49286")]
public void RecordWithEventImplicitlyImplementingAnInterface()
{
var src = @"
using System;
public interface I1
{
event Action E1;
}
public record R1 : I1
{
public event Action E1 { add { } remove { } }
}
";
var comp = CreateCompilation(src);
comp.VerifyEmitDiagnostics();
}
[Fact]
[WorkItem(49286, "https://github.com/dotnet/roslyn/issues/49286")]
public void RecordWithPropertyImplicitlyImplementingAnInterface()
{
var src = @"
using System;
public interface I1
{
Action P1 { get; set; }
}
public record R1 : I1
{
public Action P1 { get; set; }
}
";
var comp = CreateCompilation(src);
comp.VerifyEmitDiagnostics();
}
[Fact]
[WorkItem(49286, "https://github.com/dotnet/roslyn/issues/49286")]
public void RecordWithMethodImplicitlyImplementingAnInterface()
{
var src = @"
using System;
public interface I1
{
Action M1();
}
public record R1 : I1
{
public Action M1() => throw null;
}
";
var comp = CreateCompilation(src);
comp.VerifyEmitDiagnostics();
}
[Fact]
public void MergeInitializers_01()
{
var src = @"
record C(int X)
{
public int Y = 22;
}
class Test
{
static void Main()
{
System.Console.Write((new C(11)).ToString());
}
}
";
CompileAndVerify(src, expectedOutput: "C { X = 11, Y = 22 }").VerifyDiagnostics();
}
[Fact]
public void MergeInitializers_02()
{
var src1 = @"
partial record C(int X)
{
}
";
var src2 = @"
partial record C
{
public int Y = 22;
}
class Test
{
static void Main()
{
System.Console.Write((new C(11)).ToString());
}
}
";
CompileAndVerify(src1 + src2, expectedOutput: "C { X = 11, Y = 22 }").VerifyDiagnostics();
CompileAndVerify(new[] { src1, src2 }, expectedOutput: "C { X = 11, Y = 22 }").VerifyDiagnostics();
}
[Fact]
public void MergeInitializers_03()
{
var src1 = @"
partial record C
{
public int Y = 22;
}
";
var src2 = @"
partial record C(int X)
{
}
class Test
{
static void Main()
{
System.Console.Write((new C(11)).ToString());
}
}
";
CompileAndVerify(src1 + src2, expectedOutput: "C { Y = 22, X = 11 }").VerifyDiagnostics();
CompileAndVerify(new[] { src1, src2 }, expectedOutput: "C { Y = 22, X = 11 }").VerifyDiagnostics();
}
[Fact]
public void MergeInitializers_04()
{
var src1 = @"
partial record C
{
public int Y = 22;
}
";
var src2 = @"
partial record C(int X)
{
}
";
var src3 = @"
partial record C
{
public int Z = 33;
}
class Test
{
static void Main()
{
System.Console.Write((new C(11)).ToString());
}
}
";
CompileAndVerify(src1 + src2 + src3, expectedOutput: "C { Y = 22, X = 11, Z = 33 }").VerifyDiagnostics();
CompileAndVerify(new[] { src1, src2, src3 }, expectedOutput: "C { Y = 22, X = 11, Z = 33 }").VerifyDiagnostics();
}
[Fact]
public void MergeInitializers_05()
{
var src1 = @"
partial record C(int X)
{
public int U = 44;
}
";
var src2 = @"
partial record C
{
public int Y = 22;
}
class Test
{
static void Main()
{
System.Console.Write((new C(11)).ToString());
}
}
";
CompileAndVerify(src1 + src2, expectedOutput: "C { X = 11, U = 44, Y = 22 }").VerifyDiagnostics();
CompileAndVerify(new[] { src1, src2 }, expectedOutput: "C { X = 11, U = 44, Y = 22 }").VerifyDiagnostics();
}
[Fact]
public void MergeInitializers_06()
{
var src1 = @"
partial record C
{
public int Y = 22;
}
";
var src2 = @"
partial record C(int X)
{
public int U = 44;
}
class Test
{
static void Main()
{
System.Console.Write((new C(11)).ToString());
}
}
";
CompileAndVerify(src1 + src2, expectedOutput: "C { Y = 22, X = 11, U = 44 }").VerifyDiagnostics();
CompileAndVerify(new[] { src1, src2 }, expectedOutput: "C { Y = 22, X = 11, U = 44 }").VerifyDiagnostics();
}
[Fact]
public void MergeInitializers_07()
{
var src1 = @"
partial record C
{
public int Y = 22;
}
";
var src2 = @"
partial record C(int X)
{
public int U = 44;
}
";
var src3 = @"
partial record C
{
public int Z = 33;
}
class Test
{
static void Main()
{
System.Console.Write((new C(11)).ToString());
}
}
";
CompileAndVerify(src1 + src2 + src3, expectedOutput: "C { Y = 22, X = 11, U = 44, Z = 33 }").VerifyDiagnostics();
CompileAndVerify(new[] { src1, src2, src3 }, expectedOutput: "C { Y = 22, X = 11, U = 44, Z = 33 }").VerifyDiagnostics();
}
}
}
|