From 2a0e1047c004a62174891260cbae76f3b9d151a8 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Tue, 31 Mar 2020 23:34:40 -0700 Subject: [PATCH 1/5] Support struct records I believe this should work according to the spec and am just cleaning up some implementation gaps. --- .../Portable/Compiler/MethodCompiler.cs | 3 +- .../Source/SourceMemberContainerSymbol.cs | 49 +++--- .../Records/SynthesizedRecordEquals.cs | 10 +- .../Records/SynthesizedRecordObjEquals.cs | 39 ++++- .../Test/Semantic/Semantics/RecordTests.cs | 156 ++++++++++++++++++ 5 files changed, 228 insertions(+), 29 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index a08942f06c2f9..bff77f26bbf93 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -1153,7 +1153,8 @@ private void CompileMethod( // don't emit if the resulting method would contain initializers with errors if (!hasErrors && (hasBody || includeInitializersInBody)) { - Debug.Assert(!methodSymbol.IsImplicitInstanceConstructor || !methodSymbol.ContainingType.IsStructType()); + Debug.Assert(!(methodSymbol.IsImplicitInstanceConstructor && methodSymbol.ParameterCount == 0) || + !methodSymbol.ContainingType.IsStructType()); // Fields must be initialized before constructor initializer (which is the first statement of the analyzed body, if specified), // so that the initialization occurs before any method overridden by the declaring class can be invoked from the base constructor diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 4b5ce80a56f16..e0baa6ec7bada 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -143,6 +143,9 @@ public bool SetNullableContext(byte? value) } } + private static readonly ObjectPool> s_duplicateMemberSignatureDictionary = + PooledDictionary.CreatePool(MemberSignatureComparer.DuplicateSourceComparer); + protected SymbolCompletionState state; private Flags _flags; @@ -2922,20 +2925,26 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde BinderFactory binderFactory = this.DeclaringCompilation.GetBinderFactory(paramList.SyntaxTree); var binder = binderFactory.GetBinder(paramList); - var memberSignatures = new HashSet(members, MemberSignatureComparer.DuplicateSourceComparer); + var memberSignatures = s_duplicateMemberSignatureDictionary.Allocate(); + foreach (var member in members) + { + memberSignatures.Add(member, member); + } var ctor = addCtor(paramList); addProperties(ctor.Parameters); - addObjectEquals(); + var thisEquals = addThisEquals(); + addObjectEquals(thisEquals); addHashCode(); - addThisEquals(); + + memberSignatures.Free(); return; SynthesizedRecordConstructor addCtor(ParameterListSyntax paramList) { var ctor = new SynthesizedRecordConstructor(this, binder, paramList, diagnostics); - if (!memberSignatures.Contains(ctor)) + if (!memberSignatures.ContainsKey(ctor)) { members.Add(ctor); } @@ -2952,7 +2961,7 @@ void addProperties(ImmutableArray recordParameters) foreach (ParameterSymbol param in ctor.Parameters) { var property = new SynthesizedRecordPropertySymbol(this, param); - if (!memberSignatures.Contains(property)) + if (!memberSignatures.ContainsKey(property)) { members.Add(property); members.Add(property.GetMethod); @@ -2961,10 +2970,10 @@ void addProperties(ImmutableArray recordParameters) } } - void addObjectEquals() + void addObjectEquals(MethodSymbol thisEquals) { - var objEquals = new SynthesizedRecordObjEquals(this); - if (!memberSignatures.Contains(objEquals)) + var objEquals = new SynthesizedRecordObjEquals(this, thisEquals); + if (!memberSignatures.ContainsKey(objEquals)) { // PROTOTYPE: Don't add if the overridden method is sealed members.Add(objEquals); @@ -2974,37 +2983,33 @@ void addObjectEquals() void addHashCode() { var hashCode = new SynthesizedRecordGetHashCode(this); - if (!memberSignatures.Contains(hashCode)) + if (!memberSignatures.ContainsKey(hashCode)) { // PROTOTYPE: Don't add if the overridden method is sealed members.Add(hashCode); } } - void addThisEquals() + MethodSymbol addThisEquals() { var thisEquals = new SynthesizedRecordEquals(this); - if (!memberSignatures.Contains(thisEquals)) + if (!memberSignatures.TryGetValue(thisEquals, out var existing)) { members.Add(thisEquals); + return thisEquals; } + return (MethodSymbol)existing; } } private void AddSynthesizedConstructorsIfNecessary(ArrayBuilder members, ArrayBuilder> staticInitializers, DiagnosticBag diagnostics) { - switch (declaration.Kind) - { - case DeclarationKind.Class: - case DeclarationKind.Struct: - break; - } - //we're not calling the helpers on NamedTypeSymbol base, because those call //GetMembers and we're inside a GetMembers call ourselves (i.e. stack overflow) var hasInstanceConstructor = false; var hasParameterlessInstanceConstructor = false; var hasStaticConstructor = false; + bool hasRecordConstructor = false; // CONSIDER: if this traversal becomes a bottleneck, the flags could be made outputs of the // dictionary construction process. For now, this is more encapsulated. @@ -3017,6 +3022,10 @@ private void AddSynthesizedConstructorsIfNecessary(ArrayBuilder members, { case MethodKind.Constructor: hasInstanceConstructor = true; + if (method is SynthesizedRecordConstructor) + { + hasRecordConstructor = true; + } hasParameterlessInstanceConstructor = hasParameterlessInstanceConstructor || method.ParameterCount == 0; break; @@ -3036,7 +3045,9 @@ private void AddSynthesizedConstructorsIfNecessary(ArrayBuilder members, // NOTE: Per section 11.3.8 of the spec, "every struct implicitly has a parameterless instance constructor". // We won't insert a parameterless constructor for a struct if there already is one. // We don't expect anything to be emitted, but it should be in the symbol table. - if ((!hasParameterlessInstanceConstructor && this.IsStructType()) || (!hasInstanceConstructor && !this.IsStatic && !this.IsInterface)) + // PROTOTYPE: struct records do not have a parameterless constructor + if ((!hasParameterlessInstanceConstructor && this.IsStructType() && !hasRecordConstructor) || + (!hasInstanceConstructor && !this.IsStatic && !this.IsInterface)) { members.Add((this.TypeKind == TypeKind.Submission) ? new SynthesizedSubmissionConstructor(this, diagnostics) : diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEquals.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEquals.cs index 011d020186296..411a38d60aba4 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEquals.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEquals.cs @@ -22,6 +22,14 @@ internal sealed class SynthesizedRecordEquals : SynthesizedInstanceMethodSymbol public SynthesizedRecordEquals(NamedTypeSymbol containingType) { ContainingType = containingType; + if (containingType.IsStructType()) + { + // If the record type is a struct, the parameter is marked 'in' + containingType.DeclaringCompilation.EnsureIsReadOnlyAttributeExists( + diagnostics: null, + location: Location.None, + modifyCompilation: true); + } } public override string Name => "Equals"; @@ -152,7 +160,7 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, other, recordProperties, F); - retExpr = F.LogicalAnd(retExpr, comparisons); + retExpr = retExpr is null ? comparisons : F.LogicalAnd(retExpr, comparisons); } recordProperties.Free(); diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordObjEquals.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordObjEquals.cs index fde1f1e96824c..6b930b37d0126 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordObjEquals.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordObjEquals.cs @@ -14,12 +14,16 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols { internal sealed class SynthesizedRecordObjEquals : SynthesizedInstanceMethodSymbol { + private readonly MethodSymbol _typedRecordEquals; + public override NamedTypeSymbol ContainingType { get; } public override ImmutableArray Parameters { get; } - public SynthesizedRecordObjEquals(NamedTypeSymbol containingType) + + public SynthesizedRecordObjEquals(NamedTypeSymbol containingType, MethodSymbol typedRecordEquals) { + _typedRecordEquals = typedRecordEquals; ContainingType = containingType; Parameters = ImmutableArray.Create(SynthesizedParameterSymbol.Create( this, @@ -120,13 +124,32 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, { var F = new SyntheticBoundNodeFactory(this, ContainingType.GetNonNullSyntaxNode(), compilationState, diagnostics); - // return this.Equals(param as ContainingType); - var block = F.Block(ImmutableArray.Create( - F.Return( - F.InstanceCall(F.This(), "Equals", - F.As(F.Parameter(Parameters[0]), ContainingType))))); - - F.CloseMethod(block); + var paramAccess = F.Parameter(Parameters[0]); + + BoundExpression expression; + if (ContainingType.IsStructType()) + { + // For structs: + // + // return param is ContainingType i ? this.Equals(in i) : false; + expression = F.Conditional( + F.Is(paramAccess, ContainingType), + F.Call( + F.This(), + _typedRecordEquals, + ImmutableArray.Create(RefKind.In), + ImmutableArray.Create(F.Convert(ContainingType, paramAccess))), + F.Literal(false), + F.SpecialType(SpecialType.System_Boolean)); + } + else + { + // For classes: + // return this.Equals(param as ContainingType); + expression = F.InstanceCall(F.This(), "Equals", F.As(paramAccess, ContainingType)); + } + + F.CloseMethod(F.Block(ImmutableArray.Create(F.Return(expression)))); } } } \ No newline at end of file diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index ee87bd0c4ffae..f2237be6a907d 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -426,5 +426,161 @@ public void EmptyRecord() Diagnostic(ErrorCode.ERR_BadRecordDeclaration, "()").WithLocation(2, 13) ); } + + [Fact] + public void StructRecord1() + { + var src = @" +data struct Point(int X, int Y);"; + + var verifier = CompileAndVerify(src); + verifier.VerifyIL("Point.Equals(object)", @" +{ + // Code size 26 (0x1a) + .maxstack 2 + .locals init (Point V_0) + IL_0000: ldarg.1 + IL_0001: isinst ""Point"" + IL_0006: brtrue.s IL_000a + IL_0008: ldc.i4.0 + IL_0009: ret + IL_000a: ldarg.0 + IL_000b: ldarg.1 + IL_000c: unbox.any ""Point"" + IL_0011: stloc.0 + IL_0012: ldloca.s V_0 + IL_0014: call ""bool Point.Equals(in Point)"" + IL_0019: ret +}"); + verifier.VerifyIL("Point.Equals(in Point)", @" +{ + // Code size 49 (0x31) + .maxstack 3 + IL_0000: call ""System.Collections.Generic.EqualityComparer System.Collections.Generic.EqualityComparer.Default.get"" + IL_0005: ldarg.0 + IL_0006: ldfld ""int Point.k__BackingField"" + IL_000b: ldarg.1 + IL_000c: ldfld ""int Point.k__BackingField"" + IL_0011: callvirt ""bool System.Collections.Generic.EqualityComparer.Equals(int, int)"" + IL_0016: brfalse.s IL_002f + IL_0018: call ""System.Collections.Generic.EqualityComparer System.Collections.Generic.EqualityComparer.Default.get"" + IL_001d: ldarg.0 + IL_001e: ldfld ""int Point.k__BackingField"" + IL_0023: ldarg.1 + IL_0024: ldfld ""int Point.k__BackingField"" + IL_0029: callvirt ""bool System.Collections.Generic.EqualityComparer.Equals(int, int)"" + IL_002e: ret + IL_002f: ldc.i4.0 + IL_0030: ret +}"); + } + + [Fact] + public void StructRecord2() + { + var src = @" +using System; +data struct S(int X, int Y) +{ + public static void Main() + { + var s1 = new S(0, 1); + var s2 = new S(0, 1); + Console.WriteLine(s1.X); + Console.WriteLine(s1.Y); + Console.WriteLine(s1.Equals(s2)); + Console.WriteLine(s1.Equals(new S(1, 0))); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @"0 +1 +True +False"); + } + + [Fact] + public void StructRecord3() + { + var src = @" +using System; +data struct S(int X, int Y) +{ + public bool Equals(S s) => false; + public static void Main() + { + var s1 = new S(0, 1); + Console.WriteLine(s1.Equals(s1)); + Console.WriteLine(s1.Equals(in s1)); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @"False +True"); + verifier.VerifyIL("S.Main", @" +{ + // Code size 37 (0x25) + .maxstack 3 + .locals init (S V_0) //s1 + IL_0000: ldloca.s V_0 + IL_0002: ldc.i4.0 + IL_0003: ldc.i4.1 + IL_0004: call ""S..ctor(int, int)"" + IL_0009: ldloca.s V_0 + IL_000b: ldloc.0 + IL_000c: call ""bool S.Equals(S)"" + IL_0011: call ""void System.Console.WriteLine(bool)"" + IL_0016: ldloca.s V_0 + IL_0018: ldloca.s V_0 + IL_001a: call ""bool S.Equals(in S)"" + IL_001f: call ""void System.Console.WriteLine(bool)"" + IL_0024: ret +}"); + } + + [Fact] + public void StructRecord4() + { + var src = @" +using System; +data struct S(int X, int Y) +{ + public override bool Equals(object o) + { + Console.WriteLine(""obj""); + return true; + } + public bool Equals(in S s) + { + Console.WriteLine(""s""); + return true; + } + public static void Main() + { + var s1 = new S(0, 1); + s1.Equals((object)s1); + s1.Equals(s1); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @"obj +s"); + } + + [Fact] + public void StructRecordNoCtor() + { + var src = @" +data struct S(int X) +{ + public static void Main() + { + var s = new S(); + } +}"; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (6,21): error CS7036: There is no argument given that corresponds to the required formal parameter 'X' of 'S.S(int)' + // var s = new S(); + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "S").WithArguments("X", "S.S(int)").WithLocation(6, 21) + ); + } } } \ No newline at end of file From b73b5b1736f889145dc72bba3219d479e3a252a5 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Tue, 7 Apr 2020 17:27:01 -0700 Subject: [PATCH 2/5] Keep parameterless constructor for struct records --- .../CSharp/Portable/Symbols/LexicalSortKey.cs | 6 +++-- .../Source/SourceMemberContainerSymbol.cs | 8 +----- .../Records/SynthesizedRecordConstructor.cs | 8 ++++++ .../SynthesizedInstanceConstructor.cs | 2 +- .../Test/Semantic/Semantics/RecordTests.cs | 25 +++++++++---------- 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/LexicalSortKey.cs b/src/Compilers/CSharp/Portable/Symbols/LexicalSortKey.cs index f4cbe72869b0a..eb3175eafdd50 100644 --- a/src/Compilers/CSharp/Portable/Symbols/LexicalSortKey.cs +++ b/src/Compilers/CSharp/Portable/Symbols/LexicalSortKey.cs @@ -44,14 +44,16 @@ public int Position public static readonly LexicalSortKey NotInitialized = new LexicalSortKey() { _treeOrdinal = -1, _position = -1 }; // Put Record Equals right before synthesized constructors. - public static LexicalSortKey SynthesizedRecordEquals => new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue - 3 }; - public static LexicalSortKey SynthesizedRecordObjEquals => new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue - 2 }; + public static LexicalSortKey SynthesizedRecordEquals => new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue - 4 }; + public static LexicalSortKey SynthesizedRecordObjEquals => new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue - 3 }; + // Dev12 compiler adds synthetic constructors to the child list after adding all other members. // Methods are emitted in the children order, but synthetic cctors would be deferred // until later when it is known if they can be optimized or not. // As a result the last emitted method tokens are synthetic ctor and then synthetic cctor (if not optimized) // Since it is not too hard, we will try keeping the same order just to be easy on metadata diffing tools and such. + public static readonly LexicalSortKey SynthesizedRecordCtor = new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue - 2}; public static readonly LexicalSortKey SynthesizedCtor = new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue - 1 }; public static readonly LexicalSortKey SynthesizedCCtor = new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue }; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index e0baa6ec7bada..64979095df22a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -3009,7 +3009,6 @@ private void AddSynthesizedConstructorsIfNecessary(ArrayBuilder members, var hasInstanceConstructor = false; var hasParameterlessInstanceConstructor = false; var hasStaticConstructor = false; - bool hasRecordConstructor = false; // CONSIDER: if this traversal becomes a bottleneck, the flags could be made outputs of the // dictionary construction process. For now, this is more encapsulated. @@ -3022,10 +3021,6 @@ private void AddSynthesizedConstructorsIfNecessary(ArrayBuilder members, { case MethodKind.Constructor: hasInstanceConstructor = true; - if (method is SynthesizedRecordConstructor) - { - hasRecordConstructor = true; - } hasParameterlessInstanceConstructor = hasParameterlessInstanceConstructor || method.ParameterCount == 0; break; @@ -3045,8 +3040,7 @@ private void AddSynthesizedConstructorsIfNecessary(ArrayBuilder members, // NOTE: Per section 11.3.8 of the spec, "every struct implicitly has a parameterless instance constructor". // We won't insert a parameterless constructor for a struct if there already is one. // We don't expect anything to be emitted, but it should be in the symbol table. - // PROTOTYPE: struct records do not have a parameterless constructor - if ((!hasParameterlessInstanceConstructor && this.IsStructType() && !hasRecordConstructor) || + if ((!hasParameterlessInstanceConstructor && this.IsStructType()) || (!hasInstanceConstructor && !this.IsStatic && !this.IsInterface)) { members.Add((this.TypeKind == TypeKind.Submission) ? diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs index d1d860dade922..981fdd358ceaa 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs @@ -32,6 +32,14 @@ public SynthesizedRecordConstructor( addRefReadOnlyModifier: false); } + internal override LexicalSortKey GetLexicalSortKey() + { + // We need a separate sort key because struct records will have two synthesized + // constructors: the record constructor, and the parameterless constructor + return LexicalSortKey.SynthesizedRecordCtor; + } + + internal override void GenerateMethodBodyStatements(SyntheticBoundNodeFactory F, ArrayBuilder statements, DiagnosticBag diagnostics) { // Write assignments to backing fields diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceConstructor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceConstructor.cs index fb1010ec69a3c..f45aee874917c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceConstructor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceConstructor.cs @@ -131,7 +131,7 @@ public sealed override ImmutableArray TypeParameters get { return ImmutableArray.Empty; } } - internal sealed override LexicalSortKey GetLexicalSortKey() + internal override LexicalSortKey GetLexicalSortKey() { //For the sake of matching the metadata output of the native compiler, make synthesized constructors appear last in the metadata. //This is not critical, but it makes it easier on tools that are comparing metadata. diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index f2237be6a907d..cd6a2ed5f7ad8 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -565,22 +565,21 @@ public static void Main() } [Fact] - public void StructRecordNoCtor() + public void StructRecordDefaultCtor() { - var src = @" -data struct S(int X) + const string src = @" +public data struct S(int X);"; + const string src2 = @" +class C { - public static void Main() - { - var s = new S(); - } + public S M() => new S(); }"; - var comp = CreateCompilation(src); - comp.VerifyDiagnostics( - // (6,21): error CS7036: There is no argument given that corresponds to the required formal parameter 'X' of 'S.S(int)' - // var s = new S(); - Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "S").WithArguments("X", "S.S(int)").WithLocation(6, 21) - ); + var comp = CreateCompilation(src + src2); + comp.VerifyDiagnostics(); + + comp = CreateCompilation(src); + var comp2 = CreateCompilation(src2, references: new[] { comp.EmitToImageReference() }); + comp2.VerifyDiagnostics(); } } } \ No newline at end of file From 7955a39228b5943c4784ea4e51cc0ce5eb249bf2 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Wed, 8 Apr 2020 16:04:49 -0700 Subject: [PATCH 3/5] Adjust formatting --- src/Compilers/CSharp/Portable/Symbols/LexicalSortKey.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/LexicalSortKey.cs b/src/Compilers/CSharp/Portable/Symbols/LexicalSortKey.cs index eb3175eafdd50..3838e4b0e8252 100644 --- a/src/Compilers/CSharp/Portable/Symbols/LexicalSortKey.cs +++ b/src/Compilers/CSharp/Portable/Symbols/LexicalSortKey.cs @@ -53,7 +53,7 @@ public int Position // until later when it is known if they can be optimized or not. // As a result the last emitted method tokens are synthetic ctor and then synthetic cctor (if not optimized) // Since it is not too hard, we will try keeping the same order just to be easy on metadata diffing tools and such. - public static readonly LexicalSortKey SynthesizedRecordCtor = new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue - 2}; + public static readonly LexicalSortKey SynthesizedRecordCtor = new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue - 2 }; public static readonly LexicalSortKey SynthesizedCtor = new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue - 1 }; public static readonly LexicalSortKey SynthesizedCCtor = new LexicalSortKey() { _treeOrdinal = int.MaxValue, _position = int.MaxValue }; From 9ddd3c1ad1361cc511476a0e3b8adc52bd62133b Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Wed, 22 Apr 2020 15:08:10 -0700 Subject: [PATCH 4/5] Respond to PR comments --- .../Test/Semantic/Semantics/RecordTests.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index cd6a2ed5f7ad8..973ac2656635d 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -564,6 +564,29 @@ public static void Main() s"); } + [Fact] + public void StructRecord5() + { + var src = @" +using System; +data struct S(int X, int Y) +{ + public bool Equals(in S s) + { + Console.WriteLine(""s""); + return true; + } + public static void Main() + { + var s1 = new S(0, 1); + s1.Equals((object)s1); + s1.Equals(s1); + } +}"; + var verifier = CompileAndVerify(src, expectedOutput: @"s +s"); + } + [Fact] public void StructRecordDefaultCtor() { From df4fc5fa43d6d9a5e1db9a4b157217fa480e2602 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Wed, 22 Apr 2020 15:09:31 -0700 Subject: [PATCH 5/5] Fix formatting --- .../Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs index 981fdd358ceaa..95c2c065cf28f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs @@ -39,7 +39,6 @@ internal override LexicalSortKey GetLexicalSortKey() return LexicalSortKey.SynthesizedRecordCtor; } - internal override void GenerateMethodBodyStatements(SyntheticBoundNodeFactory F, ArrayBuilder statements, DiagnosticBag diagnostics) { // Write assignments to backing fields