-
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
Allow 'with' expression on value types #52319
Changes from 6 commits
54a5c86
14f5826
9c533ab
c8d9edd
91a6c93
a8cc8bd
fa84319
0393610
e0c0852
c1908e1
7590222
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,15 +32,19 @@ private BoundExpression BindWithExpression(WithExpressionSyntax syntax, BindingD | |
} | ||
|
||
MethodSymbol? cloneMethod = null; | ||
if (!receiverType.IsErrorType()) | ||
if (receiverType.IsValueType) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It feels like for enums for example there is nothing you can assign in the right side of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have a couple of tests with an empty initializers in In reply to: 608246244 [](ancestors = 608246244) |
||
{ | ||
CheckFeatureAvailability(syntax, MessageID.IDS_FeatureWithOnStructs, diagnostics); | ||
} | ||
else if (!receiverType.IsErrorType()) | ||
{ | ||
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); | ||
|
||
cloneMethod = SynthesizedRecordClone.FindValidCloneMethod(receiverType is TypeParameterSymbol typeParameter ? typeParameter.EffectiveBaseClass(ref useSiteInfo) : receiverType, ref useSiteInfo); | ||
if (cloneMethod is null) | ||
{ | ||
hasErrors = true; | ||
diagnostics.Add(ErrorCode.ERR_NoSingleCloneMethod, syntax.Expression.Location, receiverType); | ||
diagnostics.Add(ErrorCode.ERR_CannotClone, syntax.Expression.Location, receiverType); | ||
} | ||
else | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6274,8 +6274,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ | |
<data name="ERR_InvalidWithReceiverType" xml:space="preserve"> | ||
<value>The receiver of a `with` expression must have a non-void type.</value> | ||
</data> | ||
<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. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In general, I'm not sure about such comments. In this instance, they feel wrong. In reply to: 608248749 [](ancestors = 608248749) |
||
</data> | ||
<data name="ERR_AssignmentInitOnly" xml:space="preserve"> | ||
<value>Init-only property or indexer '{0}' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.</value> | ||
|
@@ -6567,6 +6567,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ | |
<value>record structs</value> | ||
<comment>'record structs' is not localizable.</comment> | ||
</data> | ||
<data name="IDS_FeatureWithOnStructs" xml:space="preserve"> | ||
<value>with on structs</value> | ||
</data> | ||
<data name="IDS_FeatureVarianceSafetyForStaticInterfaceMembers" xml:space="preserve"> | ||
<value>variance safety for static interface members</value> | ||
</data> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -103,31 +103,48 @@ public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpre | |
|
||
public override BoundNode VisitWithExpression(BoundWithExpression withExpr) | ||
{ | ||
RoslynDebug.AssertNotNull(withExpr.CloneMethod); | ||
Debug.Assert(withExpr.CloneMethod.ParameterCount == 0); | ||
Debug.Assert(withExpr.Receiver.Type!.Equals(withExpr.Type, TypeCompareKind.ConsiderEverything)); | ||
|
||
// for a with expression of the form | ||
// | ||
// 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, duplicate the value, then set the given struct properties: | ||
// | ||
// var tmp = receiver; | ||
// tmp.P1 = e1; | ||
// tmp.P2 = e2; | ||
// tmp | ||
// | ||
// otherwise the receiver is a record class and we want to lower it to a call to its `Clone` method, then | ||
// set the given record properties. i.e. | ||
// | ||
// var tmp = (ReceiverType)receiver.Clone(); | ||
// tmp.P1 = e1; | ||
// tmp.P2 = e2; | ||
// tmp | ||
|
||
var cloneCall = _factory.Convert( | ||
withExpr.Type, | ||
_factory.Call( | ||
VisitExpression(withExpr.Receiver), | ||
withExpr.CloneMethod)); | ||
BoundExpression expression; | ||
|
||
if (withExpr.Type.IsValueType) | ||
{ | ||
expression = VisitExpression(withExpr.Receiver); | ||
} | ||
else | ||
{ | ||
RoslynDebug.AssertNotNull(withExpr.CloneMethod); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: We should be using an annotated Debug.Assert at this point, so it should be possible to just say |
||
Debug.Assert(withExpr.CloneMethod.ParameterCount == 0); | ||
|
||
expression = _factory.Convert( | ||
withExpr.Type, | ||
_factory.Call( | ||
VisitExpression(withExpr.Receiver), | ||
withExpr.CloneMethod)); | ||
} | ||
|
||
return MakeExpressionWithInitializer( | ||
withExpr.Syntax, | ||
cloneCall, | ||
expression, | ||
withExpr.InitializerExpression, | ||
withExpr.Type); | ||
} | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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.
Were we missing this check for 'with' on records? #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.
From what I understand, val escape analyses are initiated when we do an assignment (we call
GetValEscape
andValidateEscape
fromBindAssignment
,BindDeconstructionAssignment
,BindReturn
, etc). But it's only relevant if we're assigning a ref-like type (GetValEscape
returnsExternalScope
for all other types).So I think record structs introduce the need to analyze
with
expressions, because the type of the receiver and thewith
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chatted with Jared. The scenario in
WithExpr_ValEscape
with assigning a ref struct value to a property is fine. The setter is essentiallyvoid Set(S1)
, which cannot do anything dangerous (it could not storeS1
and it returns void so may not sneak it out).In reply to: 608258240 [](ancestors = 608258240,607968314)