-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Support implicit indexer access in object initializers #70649
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
Changes from all commits
acfbe40
73ec15e
e4c79c2
d7b7a7a
b3d83e9
0be63ec
a05a62a
403a895
1434496
ac4f099
ae28ef3
b4e5f55
f133848
dc67a10
6ca0f90
5ab3948
cb65f3f
87eb9a1
80895f9
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 |
|---|---|---|
|
|
@@ -2,15 +2,11 @@ | |
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| using System; | ||
| using System.Collections.Immutable; | ||
| using System.Diagnostics; | ||
| using System.Linq; | ||
| using Microsoft.CodeAnalysis.CSharp.CodeGen; | ||
| using Microsoft.CodeAnalysis.CSharp.Symbols; | ||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
| using Microsoft.CodeAnalysis.PooledObjects; | ||
| using Microsoft.CodeAnalysis.Text; | ||
| using Roslyn.Utilities; | ||
|
|
||
| namespace Microsoft.CodeAnalysis.CSharp | ||
|
|
@@ -429,6 +425,7 @@ private BoundExpression VisitIndexPatternIndexerAccess(BoundImplicitIndexerAcces | |
| BoundExpression rewrittenIndexerAccess = GetUnderlyingIndexerOrSliceAccess( | ||
| node, isLeftOfAssignment, | ||
| isRegularAssignmentOrRegularCompoundAssignment: isLeftOfAssignment, | ||
| cacheAllArgumentsOnly: false, | ||
| sideeffects, locals); | ||
|
|
||
| return _factory.Sequence( | ||
|
|
@@ -441,6 +438,7 @@ private BoundExpression GetUnderlyingIndexerOrSliceAccess( | |
| BoundImplicitIndexerAccess node, | ||
| bool isLeftOfAssignment, | ||
| bool isRegularAssignmentOrRegularCompoundAssignment, | ||
| bool cacheAllArgumentsOnly, | ||
| ArrayBuilder<BoundExpression> sideeffects, | ||
| ArrayBuilder<LocalSymbol> locals) | ||
| { | ||
|
|
@@ -457,37 +455,41 @@ private BoundExpression GetUnderlyingIndexerOrSliceAccess( | |
|
|
||
| var receiver = VisitExpression(node.Receiver); | ||
|
|
||
| // Do not capture receiver if it is a local or parameter and we are evaluating a pattern | ||
| // If length access is a local, then we are evaluating a pattern | ||
| if (node.LengthOrCountAccess.Kind is not BoundKind.Local || receiver.Kind is not (BoundKind.Local or BoundKind.Parameter)) | ||
| // Do not capture receiver if we're in an initializer | ||
| if (!cacheAllArgumentsOnly) | ||
| { | ||
| Debug.Assert(receiver.Type is { }); | ||
|
|
||
| var receiverLocal = F.StoreToTemp( | ||
| receiver, | ||
| out var receiverStore, | ||
| // Store the receiver as a ref local if it's a value type to ensure side effects are propagated | ||
| receiver.Type.IsReferenceType ? RefKind.None : RefKind.Ref); | ||
| locals.Add(receiverLocal.LocalSymbol); | ||
|
|
||
| if (receiverLocal.LocalSymbol.IsRef && | ||
| CodeGenerator.IsPossibleReferenceTypeReceiverOfConstrainedCall(receiverLocal) && | ||
| !CodeGenerator.ReceiverIsKnownToReferToTempIfReferenceType(receiverLocal) && | ||
| ((isLeftOfAssignment && !isRegularAssignmentOrRegularCompoundAssignment) || | ||
| !CodeGenerator.IsSafeToDereferenceReceiverRefAfterEvaluatingArguments(ImmutableArray.Create(makeOffsetInput)))) | ||
| // Do not capture receiver if it is a local or parameter and we are evaluating a pattern | ||
| // If length access is a local, then we are evaluating a pattern | ||
| if (node.LengthOrCountAccess.Kind is not BoundKind.Local || receiver.Kind is not (BoundKind.Local or BoundKind.Parameter)) | ||
| { | ||
| BoundAssignmentOperator? extraRefInitialization; | ||
| ReferToTempIfReferenceTypeReceiver(receiverLocal, ref receiverStore, out extraRefInitialization, locals); | ||
|
|
||
| if (extraRefInitialization is object) | ||
| Debug.Assert(receiver.Type is { }); | ||
|
|
||
| var receiverLocal = F.StoreToTemp( | ||
| receiver, | ||
| out var receiverStore, | ||
| // Store the receiver as a ref local if it's a value type to ensure side effects are propagated | ||
| receiver.Type.IsReferenceType ? RefKind.None : RefKind.Ref); | ||
| locals.Add(receiverLocal.LocalSymbol); | ||
|
|
||
| if (receiverLocal.LocalSymbol.IsRef && | ||
| CodeGenerator.IsPossibleReferenceTypeReceiverOfConstrainedCall(receiverLocal) && | ||
| !CodeGenerator.ReceiverIsKnownToReferToTempIfReferenceType(receiverLocal) && | ||
| ((isLeftOfAssignment && !isRegularAssignmentOrRegularCompoundAssignment) || | ||
| !CodeGenerator.IsSafeToDereferenceReceiverRefAfterEvaluatingArguments(ImmutableArray.Create(makeOffsetInput)))) | ||
| { | ||
| sideeffects.Add(extraRefInitialization); | ||
| BoundAssignmentOperator? extraRefInitialization; | ||
| ReferToTempIfReferenceTypeReceiver(receiverLocal, ref receiverStore, out extraRefInitialization, locals); | ||
|
|
||
| if (extraRefInitialization is object) | ||
| { | ||
| sideeffects.Add(extraRefInitialization); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| sideeffects.Add(receiverStore); | ||
| sideeffects.Add(receiverStore); | ||
|
|
||
| receiver = receiverLocal; | ||
| receiver = receiverLocal; | ||
| } | ||
| } | ||
|
|
||
| AddPlaceholderReplacement(node.ReceiverPlaceholder, receiver); | ||
|
|
@@ -524,15 +526,19 @@ private BoundExpression GetUnderlyingIndexerOrSliceAccess( | |
|
|
||
| Debug.Assert(node.ArgumentPlaceholders.Length == 1); | ||
| var argumentPlaceholder = node.ArgumentPlaceholders[0]; | ||
| AddPlaceholderReplacement(argumentPlaceholder, integerArgument); | ||
| Debug.Assert(integerArgument.Type!.SpecialType == SpecialType.System_Int32); | ||
|
|
||
| BoundExpression rewrittenIndexerAccess; | ||
|
|
||
| if (node.IndexerOrSliceAccess is BoundIndexerAccess indexerAccess) | ||
| { | ||
|
Contributor
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. |
||
| Debug.Assert(indexerAccess.Arguments.Length == 1); | ||
| if (isLeftOfAssignment && indexerAccess.GetRefKind() == RefKind.None) | ||
| { | ||
| // Note: we currently don't honor cacheAllArgumentsOnly in this branch, and let | ||
| // callers do the caching instead | ||
| // Tracked by https://github.com/dotnet/roslyn/issues/71056 | ||
| AddPlaceholderReplacement(argumentPlaceholder, integerArgument); | ||
|
Contributor
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.
What feels strange or confusing about this method now, is the fact that we do not respect |
||
| ImmutableArray<BoundExpression> rewrittenArguments = VisitArgumentsAndCaptureReceiverIfNeeded( | ||
| ref receiver, | ||
| captureReceiverMode: ReceiverCaptureMode.Default, | ||
|
|
@@ -555,12 +561,30 @@ private BoundExpression GetUnderlyingIndexerOrSliceAccess( | |
| } | ||
| else | ||
| { | ||
| if (cacheAllArgumentsOnly) | ||
| { | ||
| var integerTemp = F.StoreToTemp(integerArgument, out BoundAssignmentOperator integerStore); | ||
| locals.Add(integerTemp.LocalSymbol); | ||
| sideeffects.Add(integerStore); | ||
| integerArgument = integerTemp; | ||
| } | ||
|
|
||
| AddPlaceholderReplacement(argumentPlaceholder, integerArgument); | ||
| rewrittenIndexerAccess = VisitIndexerAccess(indexerAccess, isLeftOfAssignment); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| rewrittenIndexerAccess = (BoundExpression)VisitArrayAccess(((BoundArrayAccess)node.IndexerOrSliceAccess)); | ||
| if (cacheAllArgumentsOnly) | ||
| { | ||
| var integerTemp = F.StoreToTemp(integerArgument, out BoundAssignmentOperator integerStore); | ||
| locals.Add(integerTemp.LocalSymbol); | ||
| sideeffects.Add(integerStore); | ||
| integerArgument = integerTemp; | ||
| } | ||
|
|
||
| AddPlaceholderReplacement(argumentPlaceholder, integerArgument); | ||
| rewrittenIndexerAccess = (BoundExpression)VisitArrayAccess((BoundArrayAccess)node.IndexerOrSliceAccess); | ||
| } | ||
|
|
||
| RemovePlaceholderReplacement(argumentPlaceholder); | ||
|
|
@@ -694,6 +718,20 @@ private BoundExpression DetermineMakePatternIndexOffsetExpressionStrategy( | |
| } | ||
|
|
||
| private BoundExpression VisitRangePatternIndexerAccess(BoundImplicitIndexerAccess node) | ||
| { | ||
| var F = _factory; | ||
| var localsBuilder = ArrayBuilder<LocalSymbol>.GetInstance(); | ||
| var sideEffectsBuilder = ArrayBuilder<BoundExpression>.GetInstance(); | ||
|
|
||
| var rewrittenIndexerAccess = VisitRangePatternIndexerAccess(node, localsBuilder, sideEffectsBuilder, cacheAllArgumentsOnly: false); | ||
|
|
||
| return F.Sequence( | ||
| localsBuilder.ToImmutableAndFree(), | ||
| sideEffectsBuilder.ToImmutableAndFree(), | ||
| rewrittenIndexerAccess); | ||
| } | ||
|
|
||
| private BoundExpression VisitRangePatternIndexerAccess(BoundImplicitIndexerAccess node, ArrayBuilder<LocalSymbol> localsBuilder, ArrayBuilder<BoundExpression> sideEffectsBuilder, bool cacheAllArgumentsOnly) | ||
| { | ||
| Debug.Assert(node.ArgumentPlaceholders.Length == 2); | ||
| Debug.Assert(node.IndexerOrSliceAccess is BoundCall); | ||
|
|
@@ -721,9 +759,6 @@ private BoundExpression VisitRangePatternIndexerAccess(BoundImplicitIndexerAcces | |
| PatternIndexOffsetLoweringStrategy startStrategy, endStrategy; | ||
| RewriteRangeParts(rangeArg, out rangeExpr, out startMakeOffsetInput, out startStrategy, out endMakeOffsetInput, out endStrategy, out rewrittenRangeArg); | ||
|
|
||
| var localsBuilder = ArrayBuilder<LocalSymbol>.GetInstance(); | ||
| var sideEffectsBuilder = ArrayBuilder<BoundExpression>.GetInstance(); | ||
|
|
||
| // Do not capture receiver if it is a local or parameter and we are evaluating a pattern | ||
| // If length access is a local, then we are evaluating a pattern | ||
| if (node.LengthOrCountAccess.Kind is not BoundKind.Local || receiver.Kind is not (BoundKind.Local or BoundKind.Parameter)) | ||
|
|
@@ -889,6 +924,19 @@ private BoundExpression VisitRangePatternIndexerAccess(BoundImplicitIndexerAcces | |
| startExpr = MakePatternIndexOffsetExpression(startMakeOffsetInput, lengthAccess, startStrategy); | ||
| BoundExpression endExpr = MakePatternIndexOffsetExpression(endMakeOffsetInput, lengthAccess, endStrategy); | ||
| rangeSizeExpr = MakeRangeSize(ref startExpr, endExpr, localsBuilder, sideEffectsBuilder); | ||
|
|
||
| if (cacheAllArgumentsOnly) | ||
| { | ||
| var startLocal = F.StoreToTemp(startExpr, out var startStore); | ||
| localsBuilder.Add(startLocal.LocalSymbol); | ||
| sideEffectsBuilder.Add(startStore); | ||
| startExpr = startLocal; | ||
|
|
||
| var rangeSizeLocal = F.StoreToTemp(rangeSizeExpr, out var rangeSizeStore); | ||
| localsBuilder.Add(rangeSizeLocal.LocalSymbol); | ||
| sideEffectsBuilder.Add(rangeSizeStore); | ||
| rangeSizeExpr = startLocal; | ||
| } | ||
| } | ||
| else | ||
| { | ||
|
|
@@ -907,10 +955,7 @@ private BoundExpression VisitRangePatternIndexerAccess(BoundImplicitIndexerAcces | |
| RemovePlaceholderReplacement(node.ArgumentPlaceholders[1]); | ||
| RemovePlaceholderReplacement(node.ReceiverPlaceholder); | ||
|
|
||
| return F.Sequence( | ||
| localsBuilder.ToImmutableAndFree(), | ||
| sideEffectsBuilder.ToImmutableAndFree(), | ||
| rewrittenIndexerAccess); | ||
| return rewrittenIndexerAccess; | ||
| } | ||
|
|
||
| private BoundExpression MakeRangeSize(ref BoundExpression startExpr, BoundExpression endExpr, ArrayBuilder<LocalSymbol> localsBuilder, ArrayBuilder<BoundExpression> sideEffectsBuilder) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
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 is not obvious why this is necessary and what effect is this going to have. For example, it looks like we are not doing this for a regular indexer. #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.
The regular indexer case (
BoundKind.IndexerAccess) does this after theswitch(line 5429 below).The effect is shown in tests like
InObjectInitializer_ReadonlyandInObjectInitializer_Readonly.