Skip to content

Fix inline rename in MAUI#65818

Merged
ryzngard merged 6 commits intodotnet:mainfrom
ryzngard:rename/maui
Dec 10, 2022
Merged

Fix inline rename in MAUI#65818
ryzngard merged 6 commits intodotnet:mainfrom
ryzngard:rename/maui

Conversation

@ryzngard
Copy link
Copy Markdown
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
@ghost ghost added the Area-IDE label Dec 6, 2022

if (_trackedSession is not null)
{
_trackedSession.ReplacementTextChanged -= InlineRenameSession_ReplacementTextChanged;
Copy link
Copy Markdown
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

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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Contributor

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.

this.initialState = null;
this.currentState = null;

if (_trackedSession is not null)
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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 thread src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs Outdated
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
Copy Markdown
Contributor

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
Copy Markdown
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