-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Use init-only for records and adjust binding #44582
Conversation
31f1942
to
68463b2
Compare
Changes records to generate init-only properties instead of get-only, and modifies the `with` expression to treat arguments as assignments to the left-hand side, with the safety rules of object initializers (meaning init-only assignments are allowed). Also changes records to generate a Clone method and a copy constructor. The clone calls the copy constructor and the copy constructor does a memberwise copy of the instance fields of the other type. This change also tries to simplify and reuse more pieces of binding and lowering from other initializer-based forms. The with-expression is now bound as though it were an assignment to a field or member access on a call to the Clone method.
68463b2
to
13e15e1
Compare
These changes do not appear to implement things documented in the specification. #Resolved |
Writing up the spec changes now. #Resolved |
Corresponding spec changes: dotnet/csharplang#3505 |
Can we re-use Refers to: src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_WithExpression.cs:17 in 13e15e1. [](commit_id = 13e15e1, deletion_comment = False) |
I think it's a bad idea to reuse too much of object initializer code here. There are many more things supported in object initializers than are supported in |
not related to this PR: I think we should be using the We may want to use Refers to: src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_WithExpression.cs:38 in 13e15e1. [](commit_id = 13e15e1, deletion_comment = False) |
src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordClone.cs
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordClone.cs
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordClone.cs
Outdated
Show resolved
Hide 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.
Done with review pass (iteration 3). The factoring looks really nice, thanks!
07c92fd
to
1c7b14c
Compare
@@ -2131,7 +2131,7 @@ | |||
<!-- CloneMethod may be null in error scenarios--> | |||
<Field Name="CloneMethod" Type="MethodSymbol?" /> | |||
<!-- Members and expressions passed as arguments to the With expression. --> | |||
<Field Name="Arguments" Type="ImmutableArray<(Symbol? Member, BoundExpression Expression)>" /> | |||
<Field Name="InitializerExpression" Type="BoundObjectInitializerExpressionBase" /> |
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.
This seems to really simplify the code. #Resolved
|
||
public override BoundNode VisitWithExpression(BoundWithExpression withExpr) | ||
{ | ||
RoslynDebug.AssertNotNull(withExpr.CloneMethod); |
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.
Curious: why are we using RoslynDebug.AssertNotNull
here vs. Debug.Assert(withExpr.CloneMethod is object)
(or insert your favorite non-null expression form).
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 I just forgot that debug.assert
is annotated now
src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordClone.cs
Show resolved
Hide resolved
@@ -207,5 +208,137 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, | |||
F.CloseMethod(F.Block(F.Return(F.Field(F.This(), _property.BackingField)))); | |||
} | |||
} | |||
|
|||
private sealed class InitAccessorSymbol : SynthesizedInstanceMethodSymbol |
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.
SythesizedInitAccessorSymbol
?
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.
since this is a private class I prefer the short name, and it's unlikely to get confused with a different init
accessor :)
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.
Sorry, meant to leave my last review as "comment" not "request changes"
…hesizedRecordClone.cs Co-authored-by: Jared Parsons <jaredpparsons@gmail.com>
CI issues seem to only be related to formatting
|
Should the following tests be unskipped in this PR?
|
@RikkiGibson Good question: @jcouv any thoughts? |
Added a bullet in test plan. Closing this thread. In reply to: 634988399 [](ancestors = 634988399) Refers to: src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs:2980 in 13e15e1. [](commit_id = 13e15e1, deletion_comment = False) |
Yes, |
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 Thanks (iteration 8)
@@ -2131,7 +2131,7 @@ | |||
<!-- CloneMethod may be null in error scenarios--> | |||
<Field Name="CloneMethod" Type="MethodSymbol?" /> | |||
<!-- Members and expressions passed as arguments to the With expression. --> | |||
<Field Name="Arguments" Type="ImmutableArray<(Symbol? Member, BoundExpression Expression)>" /> | |||
<Field Name="InitializerExpression" Type="BoundObjectInitializerExpressionBase" /> |
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.
Why was the type made BoundObjectInitializerExpressionBase
instead of BoundObjectInitializerExpression
? Is it just to make it easier to call BindInitializerExpression
?
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.
No particular reason, I was just leaving space for potentially creating a BoundWithInitializerExpression
in the future, if we want it.
@gafter @jaredpar @RikkiGibson Anything I missed? |
// class Point(int x, int y); | ||
Diagnostic(ErrorCode.ERR_BadRecordDeclaration, "(int x, int y)").WithLocation(2, 12), | ||
// (2,17): error CS0518: Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported |
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 there an expectation that eventually the compiler will synthesize an internal version of this attribute if needed, similar to IsReadOnlyAttribute?
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 asked this question as well: no, because there are various ordering problems with synthesizing types used in custom modifiers.
Changes records to generate init-only properties instead of get-only,
and modifies the
with
expression to treat arguments as assignmentsto the left-hand side, with the safety rules of object initializers
(meaning init-only assignments are allowed).
Also changes records to generate a Clone method and a copy constructor.
The clone calls the copy constructor and the copy constructor does a
memberwise copy of the instance fields of the other type.
This change also tries to simplify and reuse more pieces of binding and
lowering from other initializer-based forms. The with-expression is now
bound as though it were an assignment to a field or member access on a
call to the Clone method.
Spec update: dotnet/csharplang#3505
Relates to #40726 (records)