Skip to content

Update Extract Method to support top-level statements#44453

Merged
sharwell merged 2 commits intodotnet:masterfrom
sharwell:top-extract-method
Sep 19, 2020
Merged

Update Extract Method to support top-level statements#44453
sharwell merged 2 commits intodotnet:masterfrom
sharwell:top-extract-method

Conversation

@sharwell
Copy link
Contributor

Fixes #44260

@sharwell sharwell requested a review from a team as a code owner May 20, 2020 23:13
Copy link
Contributor

Choose a reason for hiding this comment

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

this block i don't quite understand. what's the case where the destinatino member is a statement itself? who is adding statements to a statement itself?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Top-level statements of a script are not part of a statement container. In this case, we have to create the container and move the original statement into it along with the new statements.

Copy link
Contributor

Choose a reason for hiding this comment

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

can you give a concrete example of the type of statement we are adding, and the statement it is being added to? it's not clear to me.

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 behavior supports the following test:

[WorkItem(44260, "https://github.com/dotnet/roslyn/issues/44260")]
[Fact, Trait(Traits.Feature, Traits.Features.ExtractMethod)]
public async Task TopLevelStatement_ArgumentInInvocation_InInteractive()
{
var code = @"
System.Console.WriteLine([|""string""|]);
";
var expected =
@"{
System.Console.WriteLine({|Rename:NewMethod|}());
static string NewMethod()
{
return ""string"";
}
}";
await TestAsync(code, expected, TestOptions.Script.WithLanguageVersion(LanguageVersionExtensions.CSharp9), index: CodeActionIndex);
}

If we didn't wrap the generated code in a block, the generated function would be a method declaration as opposed to a local function declaration.

Copy link
Contributor

Choose a reason for hiding this comment

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

would it make sense to disable extract-local-function here instead? this really doesn't seem like a desirable change to me. Plus, the addition of the block seems like it can actually screw things up. For example, what if we had var v = [|1 + 1|];. Would we make a block that only contained the variable and the local-func, causing references to 'v' to not work?

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 would make sense to disable, but that was a bit more complicated and only impacts script scenarios. I'll file a follow-up issue for it.

@sharwell sharwell force-pushed the top-extract-method branch from df672de to 6573bd1 Compare May 21, 2020 00:27
@sharwell sharwell force-pushed the top-extract-method branch from 6573bd1 to 99397da Compare May 21, 2020 19:13
@sharwell sharwell changed the base branch from features/SimplePrograms to master June 9, 2020 15:43
@sharwell
Copy link
Contributor Author

@CyrusNajmabadi for review

}";
}

local = NewMethod();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did this ordering change? Should we offer 2 fixes here: 1. to insert in-place and 2. to insert at end of the current block?

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 scenario is only valid for C# scripting, so I'm not sure it matters. It would be covered by https://github.com/dotnet/roslyn/pull/44453/files#r428859532.

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's going to be much easier to deal with these details when the tests are converted to the new test library.

@sharwell
Copy link
Contributor Author

@CyrusNajmabadi let me know if you think this one is good to merge

{
return (accessorDeclaration.Body == null) ? destinationMember : Cast<TDeclarationNode>(accessorDeclaration.AddBodyStatements(StatementGenerator.GenerateStatements(statements).ToArray()));
}
else if (destinationMember is CompilationUnitSyntax compilationUnit && options is null)
Copy link
Contributor

Choose a reason for hiding this comment

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

why the check for options is null?

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 method had an existing caller that passed in CompilationUnitSyntax with options, and relied on the AddStatementsWorker fallback to handle those options. Right now I forget what caller that was.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

➡️ Will submit a follow-up to clarify the condition.

else if (destinationMember is CompilationUnitSyntax compilationUnit && options is null)
{
// Insert the new global statement(s) at the end of any current global statements
var insertionIndex = compilationUnit.Members.LastIndexOf(memberDeclaration => memberDeclaration.IsKind(SyntaxKind.GlobalStatement)) + 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

i would love it we doc'ed that LastIndexOf returns -1 on failure. that would make me feel much safer about this code :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

➡️ Will submit a follow-up to clarify.

{
// Insert the new global statement(s) at the end of any current global statements
var insertionIndex = compilationUnit.Members.LastIndexOf(memberDeclaration => memberDeclaration.IsKind(SyntaxKind.GlobalStatement)) + 1;
var wrappedStatements = StatementGenerator.GenerateStatements(statements).Select(generated => SyntaxFactory.GlobalStatement(generated)).ToArray();
Copy link
Contributor

Choose a reason for hiding this comment

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

.ToArray seems unnecessary. though it looks like there's a lot of that in this mtehod :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Partially pattern matching with the rest of the method, but also easier to deal with when reading and debugging through.

Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi left a comment

Choose a reason for hiding this comment

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

Overall i'm ok with this. A couple of tiny clarity questions. but they're not blocking (unless hte answers are very bizarre :))

@sharwell sharwell merged commit 0457da1 into dotnet:master Sep 19, 2020
@ghost ghost added this to the Next milestone Sep 19, 2020
@sharwell sharwell deleted the top-extract-method branch September 19, 2020 20:21
sharwell added a commit to sharwell/roslyn that referenced this pull request Sep 20, 2020
@dibarbet dibarbet modified the milestones: Next, 16.8.P4 Sep 21, 2020
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.

ExtractMethodCodeRefactoringProvider does not work with top-level statements

4 participants