Skip to content
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

Fix inline rename in MAUI #65818

Merged
merged 6 commits into from
Dec 10, 2022
Merged

Fix inline rename in MAUI #65818

merged 6 commits into from
Dec 10, 2022

Conversation

ryzngard
Copy link
Contributor

@ryzngard ryzngard commented Dec 6, 2022

When applying our changes for rename we switch to a background thread to compute the text/syntax changes and apply them. Then we call TryOnBeforeGlobalSymbolRenamed to indicate we're about to rename the item. For MAUI, this causes InvisibleEditors opening/closing and causes a mutation in the workspace, which updates the Workspace.CurrentSolution.WorkspaceVersion making our calculated solution invalid to apply.

The fix here is to recompute the changes on the mutated workspace if needed. Adding telemetry here to make sure we understand the impact, but the guess is that this keeps most users on the non UI blocking expensive work, and some users having an expensive recompute.

Fixes #65397

@ryzngard ryzngard requested a review from a team as a code owner December 6, 2022 21:01

if (_trackedSession is not null)
{
_trackedSession.ReplacementTextChanged -= InlineRenameSession_ReplacementTextChanged;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This fix is unrelated. I noticed while debugging and it makes the code safer

@@ -897,6 +875,12 @@ private async Task CommitCoreAsync(IUIThreadOperationContext operationContext, b
if (!_renameInfo.TryOnBeforeGlobalSymbolRenamed(_workspace, changedDocumentIDs, this.ReplacementText))
return (NotificationSeverity.Error, EditorFeaturesResources.Rename_operation_was_cancelled_or_is_not_valid);

if (finalSolution.WorkspaceVersion != _workspace.CurrentSolution.WorkspaceVersion)
Copy link
Member

Choose a reason for hiding this comment

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

Rather than just checking this, move this handling into the if (!TryApplyChanges()) path and you could then call the TryApplyChanges a second time.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

if it fails for a different reason, do we want to call TryApplyChanges twice?

Copy link
Member

Choose a reason for hiding this comment

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

TryApplyChanges will return false if you hit the version issue, and will throw for anything else.

Copy link
Member

Choose a reason for hiding this comment

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

note: We special case tryApplyChanges returning false in ApplyChangesOperation (https://github.com/dotnet/roslyn/blob/main/src/Workspaces/Core/Portable/CodeActions/Operations/ApplyChangesOperation.cs#L50). Not sure if that would be helpful here in any way.

The general idea though is that we still attempt to make the change, as long as it's just text changes to files taht themselves haven't been changed.

@@ -97,6 +97,12 @@ public void Disconnect()
this.RedoStack.Clear();
this.initialState = null;
this.currentState = null;

if (_trackedSession is not null)
Copy link
Member

Choose a reason for hiding this comment

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

This part seems a bit strange to me: I would have expected the only thing that could possibly hold this type would have been the session -- once the session has been let go, this would have fallen away too. Did you see something being held being fixed by this, or you generally noticed this wasn't being updated? Because honestly I'd actually just delete this Disconnect method -- if anything leaks this type directly, we should know that up front it seems?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

_trackedSession only is used to hook up to ReplacementTextChanged, which then checks the currentState. If Disconnect is called prior to the last ReplacementTextChanged event we would nullref in InlineRenameSession_ReplacementTextChanged.

I agree lifetime here is a bit weird. Can pull this out. It was just something I noticed while debugging, did hit a nullref in InlineRenameSession_ReplacementTextChanged, so I fixed it. If we're resetting initial and current state, we no longer care about handling ReplacementTextChanged. Logically that makes sense to me.

Copy link
Member

Choose a reason for hiding this comment

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

(there is a pattern that some parts of Roslyn tried doing which was "if I'm leaked as an object, don't leak other stuff", but it's not a well-liked pattern generally)

Copy link
Member

Choose a reason for hiding this comment

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

(that second comment I posted after a call, but GitHub helpfully didn't show your reply yet)

Copy link
Member

Choose a reason for hiding this comment

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

did hit a nullref in InlineRenameSession_ReplacementTextChanged

Is the null ref just because we're nulling everything else out there? Because that'd be a stronger argument for "just delete" if that's practical.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is, and we could do that. I'm happy to do a separate change for that instead of piling into here

Copy link
Member

Choose a reason for hiding this comment

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

@ryzngard Sure, that seem good.

Comment on lines 878 to +887
if (!_workspace.TryApplyChanges(finalSolution))
return (NotificationSeverity.Error, EditorFeaturesResources.Rename_operation_could_not_complete_due_to_external_change_to_workspace);
{
// If the workspace changed in TryOnBeforeGlobalSymbolRenamed retry, this prevents rename from failing for cases
// where text changes to other files or workspace state change doesn't impact the text changes being applied.
Logger.Log(FunctionId.Rename_TryApplyRename_WorkspaceChanged, message: null, LogLevel.Information);
finalSolution = CalculateFinalSolutionSynchronously(newSolution, _workspace, changedDocumentIDs, cancellationToken);

if (!_workspace.TryApplyChanges(finalSolution))
return (NotificationSeverity.Error, EditorFeaturesResources.Rename_operation_could_not_complete_due_to_external_change_to_workspace);
}
Copy link
Member

Choose a reason for hiding this comment

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

💭 Can we not check _workspace.CurrentSolution explicitly to avoid having two calls to TryApplyChanges?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From my understanding this is preferred, and TryApplyChanges will throw for all cases except workspace versions being different

@ryzngard ryzngard enabled auto-merge (squash) December 9, 2022 23:16
@ryzngard ryzngard merged commit 44bd002 into dotnet:main Dec 10, 2022
@ghost ghost added this to the Next milestone Dec 10, 2022
@Cosifne Cosifne modified the milestones: Next, 17.5 P3 Jan 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Rename constantly failing in MAUI Apps
5 participants