-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Record structs: add properties and primary ctor #51720
Conversation
ParameterListSyntax? paramList = declaredMembersAndInitializers.RecordDeclarationWithParameters?.ParameterList; | ||
ParameterListSyntax? paramList = declaredMembersAndInitializers.RecordDeclarationWithParameters switch | ||
{ | ||
RecordDeclarationSyntax recordDecl => recordDecl.ParameterList, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels like this could be a useful extension method on TypeDeclarationSyntax #ByDesign
@@ -795,6 +795,7 @@ public static SyntaxKind GetTypeDeclarationKind(SyntaxKind kind) | |||
case SyntaxKind.InterfaceKeyword: | |||
return SyntaxKind.InterfaceDeclaration; | |||
case SyntaxKind.RecordKeyword: | |||
// PROTOTYPE(record-structs): anything we can do? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might need an additional parameter for the record keyword. #WontFix
@@ -270,7 +271,8 @@ internal override bool TryGetSpeculativeSemanticModelCore(SyntaxTreeSemanticMode | |||
internal override bool TryGetSpeculativeSemanticModelCore(SyntaxTreeSemanticModel parentModel, int position, PrimaryConstructorBaseTypeSyntax constructorInitializer, out SemanticModel speculativeModel) | |||
{ | |||
if (MemberSymbol is SynthesizedRecordConstructor primaryCtor && | |||
Root.FindToken(position).Parent?.AncestorsAndSelf().OfType<PrimaryConstructorBaseTypeSyntax>().FirstOrDefault() == primaryCtor.GetSyntax().PrimaryConstructorBaseType) | |||
primaryCtor.GetSyntax() is RecordDeclarationSyntax recordDecl && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it expected that we can't get a speculative model for a record struct here? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic here relates to the primary constructor base type, which doesn't exist for record structs. That's why we only need to handle record classes here.
In reply to: 589956647 [](ancestors = 589956647)
@@ -796,6 +796,7 @@ partial class C | |||
[Fact] | |||
public void PartialRecord_ParametersInScopeOfBothParts() | |||
{ | |||
// PROTOTYPE(record-structs): ported |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the significance of these comments? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not much. Just helps keep track of which tests have been duplicated/ported for record-structs. I think this will help with feature review at the end (are there some more tests that we want to move over?). I'll bulk remove those comments once the feature review is done.
In reply to: 589957946 [](ancestors = 589957946)
@jcouv Could you please clarify the following:
If I remember correctly, structures in C# cannot declare parameter-less constructors and instance initializers are not permitted in structures for this reason, i.e. no constructor to perform the initialization. #Closed |
It looks like the spec doesn't define a "copy constructor". #Closed |
Consider clarifying what is the error to eliminate any guess work. #Closed |
@@ -3414,6 +3427,11 @@ private void CheckForStructBadInitializers(DeclaredMembersAndInitializersBuilder | |||
{ | |||
Debug.Assert(TypeKind == TypeKind.Struct); | |||
|
|||
if (builder.RecordDeclarationWithParameters is RecordStructDeclarationSyntax { ParameterList: { ParameterCount: >= 0 } }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ParameterCount: >= 0 [](start = 108, length = 20)
What is the point of this check? #Closed
var ctor = declaredMembersAndInitializers.RecordPrimaryConstructor; | ||
Debug.Assert(ctor is object); | ||
members.Add(ctor); | ||
|
||
if (ctor.ParameterCount != 0) | ||
{ | ||
// properties and Deconstruct | ||
var existingOrAddedMembers = addProperties(ctor.Parameters); | ||
addDeconstruct(ctor, existingOrAddedMembers); | ||
} | ||
|
||
primaryAndCopyCtorAmbiguity = ctor.ParameterCount == 1 && ctor.Parameters[0].Type.Equals(this, TypeCompareKind.AllIgnoreOptions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
primaryAndCopyCtorAmbiguity = ctor.ParameterCount == 1 && ctor.Parameters[0].Type.Equals(this, TypeCompareKind.AllIgnoreOptions); [](start = 16, length = 129)
A concept of a copy constructor is not defined in the spec for a record struct
. This calculation doesn't make sense then. #Closed
// Ignore the record copy constructor | ||
if (!IsRecord || | ||
!(SynthesizedRecordCopyCtor.HasCopyConstructorSignature(method) && method is not SynthesizedRecordConstructor)) | ||
if (!(IsRecord && SynthesizedRecordCopyCtor.HasCopyConstructorSignature(method) && method is not SynthesizedRecordConstructor)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!(IsRecord && SynthesizedRecordCopyCtor.HasCopyConstructorSignature(method) && method is not SynthesizedRecordConstructor)) [](start = 32, length = 123)
It is not clear what is the motivation for this change. If it is just a refactoring, please revert it because it doesn't look semantically equivalent. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's easier to understand this way, but if you feel strongly I could revert.
If it is not broken, don't fix it
In reply to: 590784798 [](ancestors = 590784798)
@@ -270,7 +271,8 @@ internal override bool TryGetSpeculativeSemanticModelCore(SyntaxTreeSemanticMode | |||
internal override bool TryGetSpeculativeSemanticModelCore(SyntaxTreeSemanticModel parentModel, int position, PrimaryConstructorBaseTypeSyntax constructorInitializer, out SemanticModel speculativeModel) | |||
{ | |||
if (MemberSymbol is SynthesizedRecordConstructor primaryCtor && | |||
Root.FindToken(position).Parent?.AncestorsAndSelf().OfType<PrimaryConstructorBaseTypeSyntax>().FirstOrDefault() == primaryCtor.GetSyntax().PrimaryConstructorBaseType) | |||
primaryCtor.GetSyntax() is RecordDeclarationSyntax recordDecl && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
primaryCtor.GetSyntax() is RecordDeclarationSyntax recordDecl && [](start = 16, length = 64)
Alternatively we could check if the containing type is a class. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the current approach is okay and relatively compact.
In reply to: 590762612 [](ancestors = 590762612)
{ | ||
Debug.Assert(recordStructDecl.ParameterList is object); | ||
|
||
Binder bodyBinder = this.GetBinder(recordStructDecl); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Binder bodyBinder = this.GetBinder(recordStructDecl) [](start = 12, length = 52)
It doesn't look like the binder is needed #Closed
As far as the meaning of the spec, there is context in the intro: "Record structs will also follow the same rules as structs for parameterless instance constructors and field initializers, but this document assumes that we will lift those restrictions for structs generally." So the intention was that instance initializers would be allowed in record-structs. In reply to: 794467406 [](ancestors = 794467406) |
PR for fixing spec: dotnet/csharplang#4515 In reply to: 794471038 [](ancestors = 794471038) |
I think "it works" isn't clearly defined at this point. This looks like a violation of the spec: "A record struct is not permitted to declare a parameterless primary constructor." I think an empty parameter list should be disallowed. That should be designed, implemented and tested separately when the other feature is in, or the other feature should take care of this, if record structs are in first. In reply to: 794541167 [](ancestors = 794541167,794467406) |
src/Compilers/CSharp/Portable/Binder/ExpressionVariableFinder.cs
Outdated
Show resolved
Hide resolved
Please make sure to adjust PR description accordingly and reflect the change in the commit comment when this PR will be merged. In reply to: 794545994 [](ancestors = 794545994,794471038) |
src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Compilation/MethodBodySemanticModel.cs
Outdated
Show resolved
Hide resolved
No, I haven't been including quotes from the spec into the commit comment. So that won't be a problem. In reply to: 794600331 [](ancestors = 794600331,794590011,794574016,794545994,794471038) |
src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs
Outdated
Show resolved
Hide resolved
Done with review pass (commit 3) #Closed |
Added a test to illustrate. I'll update the spec In reply to: 794476198 [](ancestors = 794476198) |
Consider asserting that we are not getting here for a record struct. #Closed Refers to: src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordConstructor.cs:69 in 244a64a. [](commit_id = 244a64a, deletion_comment = False) |
@@ -140,8 +140,7 @@ internal Binder GetBinder(SyntaxNode node, int position, CSharpSyntaxNode member | |||
|
|||
internal InMethodBinder GetRecordConstructorInMethodBinder(SynthesizedRecordConstructor constructor) | |||
{ | |||
// PROTOTYPE(record-structs): update for record structs | |||
RecordDeclarationSyntax typeDecl = constructor.GetSyntax(); | |||
TypeDeclarationSyntax typeDecl = constructor.GetSyntax(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typeDecl [](start = 34, length = 8)
Consider asserting that this is not a record struct #Closed
{ | ||
Debug.Assert(typeDecl.Kind() is SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or SyntaxKind.RecordStructDeclaration [](start = 73, length = 37)
It feels like we should never call this method for a record struct. #Closed
@@ -1963,9 +1967,9 @@ private static void ReportCtorInitializerCycles(MethodSymbol method, BoundExpres | |||
CSharpSyntaxNode containerNode = constructor.GetNonNullSyntaxNode(); | |||
BinderFactory binderFactory = compilation.GetBinderFactory(containerNode.SyntaxTree); | |||
|
|||
if (containerNode is RecordDeclarationSyntax recordDecl) | |||
if (containerNode is RecordDeclarationSyntax or RecordStructDeclarationSyntax) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (containerNode is RecordDeclarationSyntax or RecordStructDeclarationSyntax) [](start = 16, length = 78)
It feels like the change is not necessary, assuming we return null above. #Closed
@@ -1991,6 +1995,10 @@ private static void ReportCtorInitializerCycles(MethodSymbol method, BoundExpres | |||
outerBinder = binderFactory.GetInRecordBodyBinder(recordDecl); | |||
break; | |||
|
|||
case RecordStructDeclarationSyntax recordStructDecl: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
case RecordStructDeclarationSyntax recordStructDecl: [](start = 16, length = 56)
It feels like the change is not necessary, assuming we return null above. #Closed
In fact, we probably can do this for any constructor in a struct. In reply to: 795698328 [](ancestors = 795698328) Refers to: src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs:1942 in 244a64a. [](commit_id = 244a64a, deletion_comment = False) |
case RecordDeclarationSyntax recordDecl: | ||
return recordDecl; | ||
case RecordStructDeclarationSyntax recordStructDecl: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
case RecordStructDeclarationSyntax recordStructDecl: [](start = 16, length = 52)
It feels like this case is not needed, we should never need the binder. #Closed
Done with review pass (commit 5) #Closed |
It looks like there are legitimate test failures. #Closed |
@@ -1935,7 +1939,11 @@ private static void ReportCtorInitializerCycles(MethodSymbol method, BoundExpres | |||
} | |||
} | |||
|
|||
if (constructor is SynthesizedRecordCopyCtor copyCtor) | |||
if (constructor is SynthesizedRecordConstructor && constructor.ContainingType.IsStructType()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
constructor is SynthesizedRecordConstructor && constructor.ContainingType.IsStructType() [](start = 16, length = 88)
This is not blocking, but I would simply change this condition to check if the containing type is a structure or an enum. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM (commit 7)
... and Deconstruct method.
Test plan: #51199
Relevant sections of the spec:
...