-
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
Allow with
on anonymous types
#53248
Conversation
Should target
In reply to: 834059406 |
|| symbol.ContainingType.IsAnonymousType | ||
|| CanSupportObjectInitializer(symbol); |
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 sure if the new check should be moved to CanSupportObjectInitializer
?
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'm fine with either approach.
/// - Regions group blocks together and represent the lifetime of locals and captures, loosely similar to scopes in C#. | ||
/// There are different kinds of regions, <see cref="ControlFlowRegionKind"/>. | ||
/// - <see cref="ControlFlowGraphBuilder.SpillEvalStack"/> converts values on the stack into captures. It should be | ||
/// called before entering a new region (to avoid doing it later thus giving those captures an incorrect lifetime). |
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 do not think this is 100% accurate. I suggest removing this statement. What is the "correct" lifetime region depends on particular situation. #Closed
@@ -5624,7 +5633,12 @@ public override IOperation VisitFlowCapture(IFlowCaptureOperation operation, int | |||
|
|||
public override IOperation VisitFlowCaptureReference(IFlowCaptureReferenceOperation operation, int? captureIdForResult) | |||
{ | |||
throw ExceptionUtilities.Unreachable; | |||
Debug.Assert(operation.Parent is SimpleAssignmentOperation assignment |
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.
Good catch. Restored exception.
IOperation cloned; | ||
if (operation.Type.IsValueType) | ||
{ | ||
cloned = visitedInstance; |
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.
Yes, I noticed that for structures we were producing an invalid operation because no clone method is available. I added a test and fixed it here, but forgot to add to description.
Updated description.
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.
Also includes fix for with
on pointer types.
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'm thinking this fix may be incorrect though :-/ Do we need to capture it instead? I'll add some tests with control flow and invocations on value types....
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.
Never mind. I recall that HandleObjectOrCollectionInitializer
handles that.
I'll add the tests though
|
||
static bool setAllProperties(ImmutableArray<IOperation> initializers, ImmutableArray<IPropertySymbol> properties) | ||
{ | ||
var set = PooledHashSet<string>.GetInstance(); |
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.
Is it possible to have duplicate names in some error situations?
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.
Name seems simpler.
Yes, it's possible to have duplicate names in error scenarios and this code handles that (we ignore the return value from Add
and Remove
)
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.
Name seems simpler.
Yes, it's possible to have duplicate names in error scenarios and this code handles that (we ignore the return value from Add and Remove)
Does that mean that we will ignore the fact that the duplicate property isn't initialized? And perhaps not include its copy into the AnonymousObjectCreationOperation, thus failing to match the shape of the constructor?
// calls to Visit may enter regions, so we reset things | ||
LeaveRegionsUpTo(innerCaptureRegion); | ||
|
||
var explicitProperties = PooledDictionary<string, IOperation>.GetInstance(); |
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.
simpleAssignment.Target is not InvalidOperation) | ||
{ | ||
var propertyReference = (IPropertyReferenceOperation)simpleAssignment.Target; | ||
_ = set.Remove(propertyReference.Property); |
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 looks like we are making an assumption that a property that we get here belongs to the Anonymous Type that is cloned (otherwise we might be allocating captures that won't be used at the end). I think it is a valid assumption to make, but it might be worth adding an assert about that several lines above, where we asert other aspects of each property reference. Based on that assumption, consider if it is worth reducing the amount of HashSet operations (additions/removals) that we perform. For example, instead of first adding all properties into the set and then removing those that we run into, we could start with an empty set, add properties that we run into and at the end check if the count of items in the set equals to the count of properties in the type. #Closed
I think we would actually want to see what happens with the Control Flow Graph for this error scenario. My understanding is that the purpose of the test is to make sure the builder doesn't crash and the graph looks reasonable. In reply to: 839812576 In reply to: 839812576 Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs:6596 in f930d4b. [](commit_id = f930d4b, deletion_comment = False) |
Consider testing a scenario when the target of an assignment is a name of a function that actually exists in the type. "ToString" for example. In reply to: 839819415 In reply to: 839819415 In reply to: 839819415 Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs:8998 in f930d4b. [](commit_id = f930d4b, deletion_comment = False) |
I think we would actually want to see what happens with the Control Flow Graph for this error scenario. In reply to: 839819719 Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs:9103 in f930d4b. [](commit_id = f930d4b, deletion_comment = False) |
I think we would actually want to see what happens with the Control Flow Graph for this error scenario. In reply to: 839820875 In reply to: 839820875 In reply to: 839820875 Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs:9266 in f930d4b. [](commit_id = f930d4b, deletion_comment = False) |
} | ||
LeaveRegionsUpTo(outerCaptureRegion); | ||
|
||
return new AnonymousObjectCreationOperation(initializerBuilder.ToImmutableAndFree(), semanticModel: null, operation.Syntax, operation.Type, operation.IsImplicit); |
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.
&& withExpr.Initializer.Expressions.Any() | ||
&& withExpr.Expression == (object)syntax) | ||
{ | ||
return true; |
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 is for scenarios like WithExprOnStruct_ControlFlow_WithCoalescingExpressionForValue so that the verifier won't complain about the long-lived capture with "Operation [{operationIndex}] in [{getBlockId(block)}] uses capture [{id.Value}] from another region. Should the regions be merged?" assertion above.
This would be more informative if the memo actually included the actual ids so that we could look at the graph and confirm that we indeed want to suppress the assertion. #Closed
Is this the capture reference that ControlFlowGraphVerifier was complaining about? In reply to: 839841592 In reply to: 839841592 In reply to: 839841592 Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs:6953 in f930d4b. [](commit_id = f930d4b, deletion_comment = False) |
Done with review pass (commit 9) |
Yes (capture 0 used in B5) In reply to: 839841592 Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs:6953 in f930d4b. [](commit_id = f930d4b, deletion_comment = False) |
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 10)
@dotnet/roslyn-compiler for a second review. Thanks |
static bool isAllowedDespiteReadonly(BoundExpression receiver) | ||
{ | ||
// ok: anonymousType with { Property = ... } | ||
if (receiver is BoundObjectOrCollectionValuePlaceholder && receiver.Type.IsAnonymousType) |
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.
nit: it feels like the &&
can be collapsed into a single recursive pattern and this can be inlined at the call site :)
ImmutableArray<BoundExpression> getAnonymousTypeValues(BoundWithExpression withExpr, BoundExpression oldValue, AnonymousTypeManager.AnonymousTypePublicSymbol anonymousType, | ||
ArrayBuilder<BoundExpression> sideEffects, ArrayBuilder<LocalSymbol> temps) | ||
{ | ||
var dictionary = PooledDictionary<PropertySymbol, BoundExpression>.GetInstance(); |
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.
nit: it feels like in this scenario we could use an ArrayBuilder which maps the AnonymousTypePropertySymbol.MemberIndexOpt.Value
to a BoundLocal?
. Also could consider exposing a member on AnonymousTypePropertySymbol like int MemberIndex => _index
. #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.
Made the change. Thanks
@@ -1881,6 +1881,7 @@ protected static void VerifyFlowGraphForTest<TSyntaxNode>(CSharpCompilation comp | |||
{ | |||
var tree = compilation.SyntaxTrees[0]; | |||
SyntaxNode syntaxNode = GetSyntaxNodeOfTypeForBinding<TSyntaxNode>(GetSyntaxNodeList(tree)); | |||
Debug.Assert(syntaxNode is not null, "Did you forget to place /*<bind>*/ comments in your source?"); |
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.
Thank you for this. #Closed
Debug.Assert(left.MemberSymbol is not null); | ||
|
||
// We evaluate the values provided in source first | ||
BoundLocal valueTemp = _factory.StoreToTemp(assignment.Right, out BoundAssignmentOperator boundAssignmentToTemp); |
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.
nit: it feels like it's possible to avoid storing to temps when the assignments occur in the same order as the constructor parameters, similar to the way method call arguments are rewritten during lowering. However, I don't think it's necessary to implement this optimization in this PR. #WontFix
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.
Agreed. Also, the cases where this optimization could actually play out aren't the common case so it seems fine to punt.
/// There are different kinds of regions, <see cref="ControlFlowRegionKind"/>. | ||
/// - <see cref="ControlFlowGraphBuilder.SpillEvalStack"/> converts values on the stack into captures. | ||
/// - Error scenarios from initial binding need to be handled. | ||
/// </summary> |
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.
Thanks for this. It helped me review this PR and I'm sure it will help future readers. #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.
ide side lgmt.
/azp run |
Azure Pipelines successfully started running 3 pipeline(s). |
…ures/interpolated-string * upstream/main: (92 commits) Keep casts when necessary to prefer a constant pattern over a type pattern Remove SyntaxKind.DataKeyword (#53614) Display 'readonly' for record structs (#53634) Update Building, Debugging, and Testing on Windows.md (#53543) Update dependencies from https://github.com/dotnet/arcade build 20210521.3 (#53617) Introduce resx for BuildValidator and MS.CA.Rebuild to allow localization (#53447) Report obsoletion diagnostics for slice and indexer (#53463) Update BasicGenerateConstructorDialog.cs Add searchbox in generate overrides dialog Allow `with` on anonymous types (#53248) Report diagnostic on correct node (#53538) Fix NotNullIfNotNull delegate conversion (#53409) Verify quick info session in InvokeQuickInfo Remove unnecessary retry Ensure no navbar IO on the UI thread Enable nullable reference types Fix timeout behavior in GetQuickInfo Add a semantic model based GetQuickInfoAsync entry point into QuickInfoServiceWithProviders Move semantic model based quick info API up to CommonQuickInfoProvider type Fix dnceng build by forcing the use of xcopy msbuild ...
Test plan: #51199
Also includes:
with
on pointer typeswith
on structures