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

Defer extract method mutation computation till the point the code action is invoked. #70477

Merged
merged 10 commits into from
Oct 20, 2023

Conversation

CyrusNajmabadi
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi commented Oct 20, 2023

Followup to #70466.

Follow-up to #57838. The earlier work deferred only the final formatting and simplification work. This pull request defers additional work on computing the extracted method contents itself.

@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner October 20, 2023 01:37
@dotnet-issue-labeler dotnet-issue-labeler bot added Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead labels Oct 20, 2023
@CyrusNajmabadi CyrusNajmabadi changed the title WIP: Defer extract method computation. Defer extract method mutation computation till the point the code action is invoked. Oct 20, 2023
var codeGenerator = Create(insertionPoint, selectionResult, analyzerResult, options, localFunction);
return codeGenerator.GenerateAsync(cancellationToken);
var codeGenerator = Create(selectionResult, analyzerResult, options, localFunction);
return codeGenerator.GenerateAsync(insertionPoint, cancellationToken);
Copy link
Member Author

Choose a reason for hiding this comment

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

insertionpoint is expensive (it forks the document). so it is now passed in as an arg, and only in the "actually create the new method" codepath.

var statements = CreateMethodBody(insertionPointNode, cancellationToken);
var status = CheckActiveStatements(statements);
return status.With(statements.CastArray<SyntaxNode>());
}
Copy link
Member Author

Choose a reason for hiding this comment

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

code refactoring has to still make the new statements, to do some final applicability checks.

we migth be able to optimize this if it's still expensive to do less here in the codepath where we're just querying for applicability.

}

protected override async Task<SyntaxNode> GenerateBodyForCallSiteContainerAsync(
SyntaxNode context,
Copy link
Member Author

Choose a reason for hiding this comment

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

this is the same as SyntaxNode insertionPointNode. but i acll it context in some places because that's the original name in the code. i'll fix up to make this all consistent in followup.

Copy link
Member Author

Choose a reason for hiding this comment

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

renamed in mop up.


if (statements.Skip(1).Any())
if (statements.Length != 1)
Copy link
Member Author

Choose a reason for hiding this comment

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

this is intentionally not the same as before. the 0 and >=2 cases were handled the same way in the prior code in two separate branches. i merged them into one branch here.

@@ -673,28 +666,26 @@ protected override StatementSyntax CreateAssignmentExpressionStatement(SyntaxTok
.WithUsingKeyword(usingKeyword);
}

protected override async Task<GeneratedCode> CreateGeneratedCodeAsync(OperationStatus status, SemanticDocument newDocument, CancellationToken cancellationToken)
protected override async Task<GeneratedCode> CreateGeneratedCodeAsync(
Copy link
Member Author

Choose a reason for hiding this comment

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

actual generation of code no long involves 'status' as there's nothing to report once we've decided to generte.


var actions = await GetCodeActionsAsync(document, textSpan, extractOptions, cleanupOptions, cancellationToken).ConfigureAwait(false);
var actions = await GetCodeActionsAsync(document, textSpan, extractOptions, cancellationToken).ConfigureAwait(false);
Copy link
Member Author

Choose a reason for hiding this comment

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

the cleanupoptinos are part of the extractoptions now.


namespace Microsoft.CodeAnalysis.ExtractMethod
{
internal abstract class ExtractMethodResult
internal sealed class ExtractMethodResult
Copy link
Member Author

Choose a reason for hiding this comment

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

no subclassing here. just a simple result type.

/// The name token for the invocation node that replaces the extracted code.
/// </summary>
public SyntaxToken? InvocationNameToken { get; }
private readonly AsyncLazy<(Document document, SyntaxToken? invocationNameToken)>? _lazyData;
Copy link
Member Author

Choose a reason for hiding this comment

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

computation fo the final doc, and token to rename, is done lazily and cached here.


var annotation = new SyntaxAnnotation();

var root = await DocumentWithoutFinalFormatting.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
Copy link
Member Author

Choose a reason for hiding this comment

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

all this logic moved inside MethodExtractor as the last thing it does in the lazy async callback it passes to the result object.

cancellationToken.ThrowIfCancellationRequested();
return ExtractMethodResult.Success(
operationStatus,
async cancellationToken =>
Copy link
Member Author

Choose a reason for hiding this comment

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

this is the crux of hte change. first, we do frontload some statement checks, since tehy are required to know if we're showing the EM option or not. but everything after that moves into this lazy computation in the success object.

GetFormattingRules(documentWithoutFinalFormatting),
generatedCode.MethodNameAnnotation,
generatedCode.MethodDefinitionAnnotation,
cancellationToken).ConfigureAwait(false);
Copy link
Member Author

Choose a reason for hiding this comment

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

inlined this method.

Document document,
SyntaxToken? invocationNameToken,
CancellationToken cancellationToken)
{
Copy link
Member Author

Choose a reason for hiding this comment

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

this was the code that used to be in teh result type.

ExtractOptions = globalOptions.GetExtractMethodOptions(languageServices.Language),
AddImportOptions = globalOptions.GetAddImportPlacementOptions(languageServices),
LineFormattingOptions = globalOptions.GetLineFormattingOptions(languageServices.Language)
Copy link
Member Author

Choose a reason for hiding this comment

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

the last two options are redundant. CodeCleanupOptions exposes it.

@CyrusNajmabadi
Copy link
Member Author

@ToddGrun this is ready for review. A mop-up PR is coming after this with more code sharing, and fixing of nits.

var (analyzedDocument, insertionPoint) = await GetAnnotatedDocumentAndInsertionPointAsync(
originalSemanticDocument, analyzeResult, insertionPointNode, cancellationToken).ConfigureAwait(false);
var statements = codeGenerator.GetNewMethodStatements(insertionPointNode, cancellationToken);
if (statements.Status.Failed())
Copy link
Contributor

Choose a reason for hiding this comment

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

if (statements.Status.Failed())

General question: Does the HasRefactoringsAsync call into this still need to call in for both local/method or are they going to give the same result?

Copy link
Member Author

Choose a reason for hiding this comment

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

they're different. you can be offered one, or the other, or both.

HasRefactoring would still be good though as we could stop once we know about one.

@@ -776,7 +767,7 @@ static bool ReturnOperationBelongsToMethod(SyntaxNode returnOperationSyntax, Syn

SemanticDocument CheckReturnOperations(
SyntaxNode node,
OperationStatus<IMethodSymbol> methodSymbolResult,
IMethodSymbol methodSymbol,
Copy link
Contributor

Choose a reason for hiding this comment

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

IMethodSymbol methodSymbol,

not seeing this used

Copy link
Member Author

Choose a reason for hiding this comment

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

can remove if that's the case :)

Copy link
Member Author

Choose a reason for hiding this comment

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

intresting. thsi wasn't used. @mavasani a potential bug with the unused parameter analyzer? note: this is in a local function.

Copy link
Contributor

@ToddGrun ToddGrun left a comment

Choose a reason for hiding this comment

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

:shipit:

@CyrusNajmabadi CyrusNajmabadi merged commit d4a970d into dotnet:main Oct 20, 2023
23 of 24 checks passed
@ghost ghost added this to the Next milestone Oct 20, 2023
@CyrusNajmabadi CyrusNajmabadi deleted the emPerf3 branch October 20, 2023 16:33
@jjonescz jjonescz modified the milestones: Next, 17.9 P1 Oct 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants