Skip to content

Conversation

@AlekseyTs
Copy link
Contributor

Fixes #79379
Fixes #79436

@AlekseyTs AlekseyTs requested review from jcouv and jjonescz July 18, 2025 19:57
@AlekseyTs AlekseyTs requested a review from a team as a code owner July 18, 2025 19:57
@AlekseyTs AlekseyTs added the Feature - Extension Everything The extension everything feature label Jul 18, 2025
@jcouv jcouv self-assigned this Jul 18, 2025
@AlekseyTs
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@AlekseyTs
Copy link
Contributor Author

@jcouv, @jjonescz, @dotnet/roslyn-compiler Please review

@AlekseyTs
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

}

receiverTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp, refKind);
receiverTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp, (refKind is RefKind.RefReadOnlyParameter or RefKind.In) ? RefKindExtensions.StrictIn : refKind);
Copy link
Member

@jcouv jcouv Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(refKind is RefKind.RefReadOnlyParameter or RefKind.In) ? RefKindExtensions.StrictIn : refKind

I didn't follow why we need this change. When I revert it, it doesn't seem to affect any extensions test.
If the change is desirable, would we also want it in TransformPropertyOrEventReceiver when computing the refKind for the temp for the receiver? #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I revert it, it doesn't seem to affect any extensions test.

Added InterpolationTests.StructReceiver_Lvalue_08 which crashes compiler without the change.

Adding similar change to TransformPropertyOrEventReceiver for consistency and future proofing


if (IsPropertyWithByValPossiblyStructReceiverWhichHasLocationAndCanChangeValueBetweenReads(rewrittenReceiver, indexer))
{
// The receiever has location, but extension indexer takes receiver by value.
Copy link
Member

@jcouv jcouv Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo:

Suggested change
// The receiever has location, but extension indexer takes receiver by value.
// The receiver has location, but extension indexer takes receiver by value.
``` #Closed

IsPropertyWithByValPossiblyStructReceiverWhichHasLocationAndCanChangeValueBetweenReads(rewrittenReceiver, property) &&
(arguments.Length != 0 || !IsSafeForReordering(rewrittenRight, RefKind.None)))
{
// The receiever has location, but extension property/indexer takes receiver by value.
Copy link
Member

@jcouv jcouv Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo:

Suggested change
// The receiever has location, but extension property/indexer takes receiver by value.
// The receiver has location, but extension property/indexer takes receiver by value.
``` #Closed

}
}

private bool IsPropertyWithByValPossiblyStructReceiverWhichHasLocationAndCanChangeValueBetweenReads(BoundExpression rewrittenReceiver, PropertySymbol property)
Copy link
Member

@jcouv jcouv Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsPropertyWithByValPossiblyStructReceiverWhichHasLocationAndCanChangeValueBetweenReads

Consider renaming to IsExtensionPropertyWithByValPossiblyStructReceiverWhichHasHomeAndCanChangeValueBetweenReads as only extension properties qualify and to avoid multiplying concepts ("home" versus "location"). #Closed

ArrayBuilder<BoundExpression>? storesOpt = null;

if (IsPropertyWithByValPossiblyStructReceiverWhichHasLocationAndCanChangeValueBetweenReads(rewrittenReceiver, indexer))
{
Copy link
Member

@jcouv jcouv Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider leaving follow-up comment to cover this logic when implementing extension indexers Never mind, I see you added skipped tests #Closed

@jcouv
Copy link
Member

jcouv commented Jul 21, 2025

public void PropertyAccess_Set_01()

nit: consider adding tests for disambiguation invocation syntax (E.set_P1(this, Program.Get1()), where the setter will see the old value of the struct) and instance invocation scenarios (S1 implements P1, where the setter sees the updated value of the struct) #Closed


Refers to: src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs:5570 in 814baf6. [](commit_id = 814baf6, deletion_comment = False)

@jcouv
Copy link
Member

jcouv commented Jul 21, 2025

public void IndexerAccess_CompoundAssignment_01()

Thank you!


Refers to: src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs:11763 in 814baf6. [](commit_id = 814baf6, deletion_comment = False)

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM Thanks (commit 7) with some nits to consider

@AlekseyTs
Copy link
Contributor Author

@jjonescz, @dotnet/roslyn-compiler For a second review

ArrayBuilder<BoundExpression>? storesOpt = null;

if (rewrittenReceiver is not null &&
assignmentKind is not (AssignmentKind.CompoundAssignment or AssignmentKind.NullCoalescingAssignment or AssignmentKind.Deconstruction or AssignmentKind.IncrementDecrement) &&
Copy link
Member

@jjonescz jjonescz Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check that assignmentKind is AssignmentKind.SimpleAssignment instead? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am checking for assignments that I know don't need special treatment. Doing an opposite check is not robust in the long run, if new kind is added.

(arguments.Length != 0 || !IsSafeForReordering(rewrittenRight, RefKind.None)))
{
// The receiever has location, but extension property/indexer takes receiver by value.
// This means that we need to ensure that the the receiver value is read after
Copy link
Member

@jjonescz jjonescz Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// This means that we need to ensure that the the receiver value is read after
// This means that we need to ensure that the receiver value is read after
``` #Resolved

// The receiever has location, but extension property/indexer takes receiver by value.
// This means that we need to ensure that the the receiver value is read after
// any side-effecting arguments and right hand side are evaluated, so that the
// the setter receives the last value of the receiver, not the value before the
Copy link
Member

@jjonescz jjonescz Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// the setter receives the last value of the receiver, not the value before the
// setter receives the last value of the receiver, not the value before the
``` #Closed

// stores in storesToTemps and make the actual argument a reference to the temp.
arguments = ExtractSideEffectsFromArguments(arguments, property, expanded, argsToParamsOpt, ref argumentRefKindsOpt, storesOpt, argTempsBuilder);

if (!IsSafeForReordering(rewrittenRight, RefKind.None))
Copy link
Member

@jjonescz jjonescz Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider caching and reusing the result of IsSafeForReordering(rewrittenRight, RefKind.None) from above. I guess it might not be always computed, but perhaps having a bool? and recomputing it only if null would still be worth it. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider caching and reusing the result of

I prefer to not complicate code trying to optimize something that is not yet proven to be a problem


if (rewrittenReceiver is not null &&
assignmentKind is not (AssignmentKind.CompoundAssignment or AssignmentKind.NullCoalescingAssignment or AssignmentKind.Deconstruction or AssignmentKind.IncrementDecrement) &&
IsPropertyWithByValPossiblyStructReceiverWhichHasLocationAndCanChangeValueBetweenReads(rewrittenReceiver, property) &&
Copy link
Member

@jjonescz jjonescz Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you now get some trophy for the longest method name in the codebase? :D #Resolved

/// the sideeffects of capturing are the sifeeffects of the sequence and its result is the captured value.
///
/// All temps introduced by this function for capturing purposes (including the temp capturing the receiver) are appended
/// to <paramref name="tempsOpt"/>, which is allocated, if 'null' on input.
Copy link
Member

@jjonescz jjonescz Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// to <paramref name="tempsOpt"/>, which is allocated, if 'null' on input.
/// to <paramref name="tempsOpt"/>, which is allocated if 'null' on input.
``` #Resolved

// The receiever has location, but extension indexer takes receiver by value.
// This means that we need to ensure that the the receiver value is read after
// any side-effecting arguments are evaluated, so that the
// the setter receives the last value of the receiver, not the value before the
Copy link
Member

@jjonescz jjonescz Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// the setter receives the last value of the receiver, not the value before the
// setter receives the last value of the receiver, not the value before the
``` #Closed

if (IsPropertyWithByValPossiblyStructReceiverWhichHasLocationAndCanChangeValueBetweenReads(rewrittenReceiver, indexer))
{
// The receiever has location, but extension indexer takes receiver by value.
// This means that we need to ensure that the the receiver value is read after
Copy link
Member

@jjonescz jjonescz Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// This means that we need to ensure that the the receiver value is read after
// This means that we need to ensure that the receiver value is read after
``` #Resolved

[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/78137")]
[CombinatorialData]
public void InterpolationHandler_ReceiverParameter_ByIn_WithConstantReceiver(bool useMetadataRef)
public void InterpolationHandler_ReceiverParameter_Generic_ByRef(bool useMetadataRef)
Copy link
Member

@jjonescz jjonescz Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider referencing #79379 in the modified/added tests #Resolved

// any side-effecting arguments and right hand side are evaluated, so that the
// the setter receives the last value of the receiver, not the value before the
// arguments/rhs were evaluated. Receiver sideeffects should be evaluated at
// the very beginning, of course.
Copy link
Member

@jjonescz jjonescz Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when the receiver does not have a home; is that always an error scenario? #Resolved

Copy link
Contributor Author

@AlekseyTs AlekseyTs Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when the receiver does not have a home; is that always an error scenario?

Not necessary. If the receiver doesn't have home, no one can change its value after we read it. Therefore, we don't need to worry about capturing it by reference and dereferencing it right before invoking the accessor. If we try to do so, the value will be captured in a temp anyway.

@AlekseyTs AlekseyTs enabled auto-merge (squash) July 25, 2025 17:37
Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM Thanks (commit 8)

@AlekseyTs AlekseyTs changed the base branch from main to features/extensions July 25, 2025 19:45
@AlekseyTs AlekseyTs merged commit 1d9820f into dotnet:features/extensions Jul 25, 2025
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-Compilers Feature - Extension Everything The extension everything feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid order of evaluation for compound assignment involving expanded param array form Inconsistent capturing for interpolated string handlers

3 participants