Allow 'with' expression on value types#52319
Allow 'with' expression on value types#52319jcouv merged 11 commits intodotnet:features/record-structsfrom
Conversation
| @@ -6275,7 +6275,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ | |||
| <value>The receiver of a `with` expression must have a non-void type.</value> | |||
| </data> | |||
| <data name="ERR_NoSingleCloneMethod" xml:space="preserve"> | |||
There was a problem hiding this comment.
ERR_NoSingleCloneMethod [](start = 14, length = 23)
Consider renaming the error to more accurately reflect its purpose/conditions under which it is reported #Closed
| case BoundKind.WithExpression: | ||
| { | ||
| var withExpr = (BoundWithExpression)expr; | ||
| var escape = CheckValEscape(node, withExpr.Receiver, escapeFrom, escapeTo, checkingReceiver: false, diagnostics); |
There was a problem hiding this comment.
CheckValEscape(node, withExpr.Receiver, escapeFrom, escapeTo, checkingReceiver: false, diagnostics); [](start = 37, length = 100)
Were we missing this check for 'with' on records? #Closed
There was a problem hiding this comment.
From what I understand, val escape analyses are initiated when we do an assignment (we call GetValEscape and ValidateEscape from BindAssignment, BindDeconstructionAssignment, BindReturn, etc). But it's only relevant if we're assigning a ref-like type (GetValEscape returns ExternalScope for all other types).
So I think record structs introduce the need to analyze with expressions, because the type of the receiver and the with expression now can be a ref-like struct.
For the initializer part, I'm not sure. I've added a test WithExpr_ValEscape which would have thought should produce diagnostics because the initializer contains dangerous assignments. Could you take a look?
If I'm correct, this is an existing bug (an assignment for which we are not triggering val-escape checks).
In reply to: 607968314 [](ancestors = 607968314)
There was a problem hiding this comment.
Chatted with Jared. The scenario in WithExpr_ValEscape with assigning a ref struct value to a property is fine. The setter is essentially void Set(S1), which cannot do anything dangerous (it could not store S1 and it returns void so may not sneak it out).
In reply to: 608258240 [](ancestors = 608258240,607968314)
| // receiver with { P1 = e1, P2 = e2 } | ||
| // | ||
| // we want to lower it to a call to the receiver's `Clone` method, then | ||
| // if the receiver is a struct, then set the given struct properties: |
There was a problem hiding this comment.
given [](start = 57, length = 5)
This sounds somewhat confusing. We are not mutating the given struct, but rather returning a new value. #Closed
There was a problem hiding this comment.
| var src = @" | ||
| var b = new B() { X = 1 }; | ||
| var b2 = b.M(); | ||
| System.Console.Write(b2.X); |
There was a problem hiding this comment.
Consider observing that the original structure is not mutated. #Closed
| var comp = CreateCompilation(src); | ||
| comp.VerifyDiagnostics(); | ||
| var verifier = CompileAndVerify(comp, expectedOutput: "4243"); | ||
| verifier.VerifyIL("<Program>$.<<Main>$>g__M|0_0(System.ValueTuple<int, int>)", @" |
There was a problem hiding this comment.
$.<
$>g__M|0_0(System.ValueTuple<int, int>) [](start = 31, length = 57)
It feels like it would be better to have a regualr method within a type. #Closed
| public readonly int X; | ||
| public B M() | ||
| { | ||
| return this with { X = 42 }; |
There was a problem hiding this comment.
this with { X = 42 }; [](start = 15, length = 21)
Consider testing this error inside a constructor. #Closed
AlekseyTs
left a comment
There was a problem hiding this comment.
LGTM (commit 4) with some comments.
|
|
||
| MethodSymbol? cloneMethod = null; | ||
| if (!receiverType.IsErrorType()) | ||
| if (receiverType.IsValueType) |
There was a problem hiding this comment.
It feels like for enums for example there is nothing you can assign in the right side of the with. Is there any item tracking giving a warning when the right side of the with is empty? #Resolved
There was a problem hiding this comment.
I have a couple of tests with an empty initializers in with. I don't think that should be a warning, as empty initializers are also allowed in object creation.
In reply to: 608246244 [](ancestors = 608246244)
| } | ||
| else | ||
| { | ||
| RoslynDebug.AssertNotNull(withExpr.CloneMethod); |
There was a problem hiding this comment.
nit: We should be using an annotated Debug.Assert at this point, so it should be possible to just say Debug.Assert(withExpr.CloneMethod is not null) #Resolved
| <data name="ERR_NoSingleCloneMethod" xml:space="preserve"> | ||
| <value>The receiver type '{0}' is not a valid record type.</value> | ||
| <data name="ERR_CannotClone" xml:space="preserve"> | ||
| <value>The receiver type '{0}' is not a valid record type and is not a struct type.</value> |
There was a problem hiding this comment.
Consider adding a <comment> that 'record' and 'struct' are language keywords for loc team #WontFix
There was a problem hiding this comment.
In general, I'm not sure about such comments. In this instance, they feel wrong. record and struct are not keywords in this sentence.
If they were keywords, then they could be marked with backticks in markdown, but that doesn't work here: "...is not a valid record type and is not a struct type."
In reply to: 608248749 [](ancestors = 608248749)
| var switchExpr = (BoundSwitchExpression)expr; | ||
| return GetValEscape(switchExpr.SwitchArms.SelectAsArray(a => a.Value), scopeOfTheContainingExpression); | ||
|
|
||
| case BoundKind.WithExpression: |
There was a problem hiding this comment.
case BoundKind.WithExpression: [](start = 16, length = 30)
It might be convenient to place this case next to BoundKind.ObjectCreationExpression: since they both have to deal with an initializer. Same for the other function getting a similar case. #Closed
Test plan #51199