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

Update Extract Method to support top-level statements #44453

Merged
merged 2 commits into from
Sep 19, 2020

Conversation

sharwell
Copy link
Member

Fixes #44260

@sharwell sharwell requested a review from a team as a code owner May 20, 2020 23:13
var insertionIndex = compilationUnit.Members.LastIndexOf(memberDeclaration => memberDeclaration.IsKind(SyntaxKind.GlobalStatement)) + 1;
return Cast<TDeclarationNode>(compilationUnit.WithMembers(compilationUnit.Members.InsertRange(insertionIndex, StatementGenerator.GenerateStatements(statements).Select(generated => SyntaxFactory.GlobalStatement(generated)).ToArray())));
}
else if (destinationMember is StatementSyntax statement && statement.IsParentKind(SyntaxKind.GlobalStatement))
Copy link
Member

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
Member 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
Member

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
Member 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
Member

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
Member 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 changed the base branch from features/SimplePrograms to master June 9, 2020 15:43
@sharwell
Copy link
Member 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
Member 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
Member 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
Member Author

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

@@ -482,6 +482,21 @@ protected override TDeclarationNode AddMembers<TDeclarationNode>(TDeclarationNod
{
return (accessorDeclaration.Body == null) ? destinationMember : Cast<TDeclarationNode>(accessorDeclaration.AddBodyStatements(StatementGenerator.GenerateStatements(statements).ToArray()));
}
else if (destinationMember is CompilationUnitSyntax compilationUnit && options is null)
Copy link
Member

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
Member 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
Member 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
Member

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
Member 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
Member

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
Member 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
Member

@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