提交 60a2369d 编写于 作者: A Andrew Hall (METAL)

Fix symbol resolution by applying groups at a time

上级 443784e3
......@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.InlineDeclaration
......@@ -78,6 +79,7 @@ void M1()
public async Task FixAllInDocument3()
{
await TestInRegularAndScriptAsync(
@"class C
{
......@@ -94,6 +96,51 @@ void M()
{
GetExeAndArguments(useCmdShell, executable, arguments, out string finalExecutable, out string finalArguments);
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineDeclaration)]
[WorkItem(29935, "https://github.com/dotnet/roslyn/issues/29935")]
public async Task FixAllInDocumentSymbolResolution()
{
await TestInRegularAndScriptAsync(
@"class C
{
void M()
{
string {|FixAllInDocument:s|};
bool b;
A(out s, out b);
}
void A(out string s, out bool b)
{
s = string.Empty;
b = false;
}
void A(out string s, out string s2)
{
s = s2 = string.Empty;
}
}",
@"class C
{
void M()
{
A(out string s, out bool b);
}
void A(out string s, out bool b)
{
s = string.Empty;
b = false;
}
void A(out string s, out string s2)
{
s = s2 = string.Empty;
}
}");
}
}
......
......@@ -49,133 +49,151 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
// of the local. This is necessary in some cases (for example, when the
// type of the out-var-decl affects overload resolution or generic instantiation).
foreach (var diagnostic in diagnostics)
// Group by the ExpressionSyntax that the modified expression targets
var diagnosticGroups = diagnostics.GroupBy(d => d.AdditionalLocations[2]);
foreach (var diagnosticGroup in diagnosticGroups)
{
cancellationToken.ThrowIfCancellationRequested();
await AddEditsAsync(
document, editor, diagnostic,
document, editor, diagnosticGroup,
options, cancellationToken).ConfigureAwait(false);
}
}
private async Task AddEditsAsync(
Document document, SyntaxEditor editor, Diagnostic diagnostic,
Document document, SyntaxEditor editor, IGrouping<Location, Diagnostic> diagnosticGroup,
OptionSet options, CancellationToken cancellationToken)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
// Recover the nodes we care about.
var declaratorLocation = diagnostic.AdditionalLocations[0];
var identifierLocation = diagnostic.AdditionalLocations[1];
var invocationOrCreationLocation = diagnostic.AdditionalLocations[2];
var outArgumentContainingStatementLocation = diagnostic.AdditionalLocations[3];
var root = declaratorLocation.SourceTree.GetRoot(cancellationToken);
var declarationsToAdd = new List<(ILocalSymbol, DeclarationExpressionSyntax, IdentifierNameSyntax)>();
var declarator = (VariableDeclaratorSyntax)declaratorLocation.FindNode(cancellationToken);
var identifier = (IdentifierNameSyntax)identifierLocation.FindNode(cancellationToken);
var invocationOrCreationLocation = diagnosticGroup.Key;
var invocationOrCreation = (ExpressionSyntax)invocationOrCreationLocation.FindNode(
getInnermostNodeForTie: true, cancellationToken: cancellationToken);
var outArgumentContainingStatement = (StatementSyntax)outArgumentContainingStatementLocation.FindNode(cancellationToken);
var declaration = (VariableDeclarationSyntax)declarator.Parent;
var singleDeclarator = declaration.Variables.Count == 1;
getInnermostNodeForTie: true, cancellationToken: cancellationToken);
if (singleDeclarator)
foreach (var diagnostic in diagnosticGroup)
{
// This was a local statement with a single variable in it. Just Remove
// the entire local declaration statement. Note that comments belonging to
// this local statement will be moved to be above the statement containing
// the out-var.
var localDeclarationStatement = (LocalDeclarationStatementSyntax)declaration.Parent;
var block = (BlockSyntax)localDeclarationStatement.Parent;
var declarationIndex = block.Statements.IndexOf(localDeclarationStatement);
if (declarationIndex > 0 &&
sourceText.AreOnSameLine(block.Statements[declarationIndex - 1].GetLastToken(), localDeclarationStatement.GetFirstToken()))
// Recover the nodes we care about.
var declaratorLocation = diagnostic.AdditionalLocations[0];
var identifierLocation = diagnostic.AdditionalLocations[1];
var root = declaratorLocation.SourceTree.GetRoot(cancellationToken);
var declarator = (VariableDeclaratorSyntax)declaratorLocation.FindNode(cancellationToken);
var identifier = (IdentifierNameSyntax)identifierLocation.FindNode(cancellationToken);
var declaration = (VariableDeclarationSyntax)declarator.Parent;
var singleDeclarator = declaration.Variables.Count == 1;
if (singleDeclarator)
{
// There's another statement on the same line as this declaration statement.
// i.e. int a; int b;
//
// Just move all trivia from our statement to be trailing trivia of the previous
// statement
editor.ReplaceNode(
block.Statements[declarationIndex - 1],
(s, g) => s.WithAppendedTrailingTrivia(localDeclarationStatement.GetTrailingTrivia()));
// This was a local statement with a single variable in it. Just Remove
// the entire local declaration statement. Note that comments belonging to
// this local statement will be moved to be above the statement containing
// the out-var.
var localDeclarationStatement = (LocalDeclarationStatementSyntax)declaration.Parent;
var block = (BlockSyntax)localDeclarationStatement.Parent;
var declarationIndex = block.Statements.IndexOf(localDeclarationStatement);
if (declarationIndex > 0 &&
sourceText.AreOnSameLine(block.Statements[declarationIndex - 1].GetLastToken(), localDeclarationStatement.GetFirstToken()))
{
// There's another statement on the same line as this declaration statement.
// i.e. int a; int b;
//
// Just move all trivia from our statement to be trailing trivia of the previous
// statement
editor.ReplaceNode(
block.Statements[declarationIndex - 1],
(s, g) => s.WithAppendedTrailingTrivia(localDeclarationStatement.GetTrailingTrivia()));
}
else
{
// Trivia on the local declaration will move to the next statement.
// use the callback form as the next statement may be the place where we're
// inlining the declaration, and thus need to see the effects of that change.
editor.ReplaceNode(
block.Statements[declarationIndex + 1],
(s, g) => s.WithPrependedNonIndentationTriviaFrom(localDeclarationStatement));
}
editor.RemoveNode(localDeclarationStatement, SyntaxRemoveOptions.KeepUnbalancedDirectives);
}
else
{
// Trivia on the local declaration will move to the next statement.
// use the callback form as the next statement may be the place where we're
// inlining the declaration, and thus need to see the effects of that change.
editor.ReplaceNode(
block.Statements[declarationIndex + 1],
(s, g) => s.WithPrependedNonIndentationTriviaFrom(localDeclarationStatement));
}
editor.RemoveNode(localDeclarationStatement, SyntaxRemoveOptions.KeepUnbalancedDirectives);
}
else
{
// Otherwise, just remove the single declarator. Note: we'll move the comments
// 'on' the declarator to the out-var location. This is a little bit trickier
// than normal due to how our comment-association rules work. i.e. if you have:
//
// var /*c1*/ i /*c2*/, /*c3*/ j /*c4*/;
//
// In this case 'c1' is owned by the 'var' token, not 'i', and 'c3' is owned by
// the comment token not 'j'.
editor.RemoveNode(declarator);
if (declarator == declaration.Variables[0])
{
// If we're removing the first declarator, and it's on the same line
// as the previous token, then we want to remove all the trivia belonging
// to the previous token. We're going to move it along with this declarator.
// If we don't, then the comment will stay with the previous token.
// Otherwise, just remove the single declarator. Note: we'll move the comments
// 'on' the declarator to the out-var location. This is a little bit trickier
// than normal due to how our comment-association rules work. i.e. if you have:
//
// Note that the moving of the comment happens later on when we make the
// declaration expression.
if (sourceText.AreOnSameLine(declarator.GetFirstToken(), declarator.GetFirstToken().GetPreviousToken(includeSkipped: true)))
// var /*c1*/ i /*c2*/, /*c3*/ j /*c4*/;
//
// In this case 'c1' is owned by the 'var' token, not 'i', and 'c3' is owned by
// the comment token not 'j'.
editor.RemoveNode(declarator);
if (declarator == declaration.Variables[0])
{
editor.ReplaceNode(
declaration.Type,
(t, g) => t.WithTrailingTrivia(SyntaxFactory.ElasticSpace).WithoutAnnotations(Formatter.Annotation));
// If we're removing the first declarator, and it's on the same line
// as the previous token, then we want to remove all the trivia belonging
// to the previous token. We're going to move it along with this declarator.
// If we don't, then the comment will stay with the previous token.
//
// Note that the moving of the comment happens later on when we make the
// declaration expression.
if (sourceText.AreOnSameLine(declarator.GetFirstToken(), declarator.GetFirstToken().GetPreviousToken(includeSkipped: true)))
{
editor.ReplaceNode(
declaration.Type,
(t, g) => t.WithTrailingTrivia(SyntaxFactory.ElasticSpace).WithoutAnnotations(Formatter.Annotation));
}
}
}
}
// get the type that we want to put in the out-var-decl based on the user's options.
// i.e. prefer 'out var' if that is what the user wants. Note: if we have:
//
// Method(out var x)
//
// Then the type is not-apparent, and we should not use var if the user only wants
// it for apparent types
// get the type that we want to put in the out-var-decl based on the user's options.
// i.e. prefer 'out var' if that is what the user wants. Note: if we have:
//
// Method(out var x)
//
// Then the type is not-apparent, and we should not use var if the user only wants
// it for apparent types
var local = (ILocalSymbol)semanticModel.GetDeclaredSymbol(declarator);
var newType = GenerateTypeSyntaxOrVar(local.Type, options);
var local = (ILocalSymbol)semanticModel.GetDeclaredSymbol(declarator);
var newType = GenerateTypeSyntaxOrVar(local.Type, options);
var declarationExpression = GetDeclarationExpression(
sourceText, identifier, newType, singleDeclarator ? null : declarator);
var declarationExpression = GetDeclarationExpression(
sourceText, identifier, newType, singleDeclarator ? null : declarator);
declarationsToAdd.Add((local, declarationExpression, identifier));
}
// Check if using out-var changed problem semantics.
var semanticsChanged = await SemanticsChangedAsync(
document, declaration, invocationOrCreation, newType,
identifier, declarationExpression, cancellationToken).ConfigureAwait(false);
if (semanticsChanged && newType.IsVar)
document, declarationsToAdd,
invocationOrCreation, cancellationToken).ConfigureAwait(false);
if (semanticsChanged)
{
// Switching to 'var' changed semantics. Just use the original type of the local.
// If the user originally wrote it something other than 'var', then use what they
// wrote. Otherwise, synthesize the actual type of the local.
var explicitType = declaration.Type.IsVar ? local.Type?.GenerateTypeSyntax() : declaration.Type;
declarationExpression = GetDeclarationExpression(
sourceText, identifier, explicitType, singleDeclarator ? null : declarator);
}
foreach ((var local, var declaration, var identifier) in declarationsToAdd)
{
var explicitType = declaration.Type.IsVar ? local.Type?.GenerateTypeSyntax() : declaration.Type;
var newDeclaration = SyntaxFactory.DeclarationExpression(explicitType, declaration.Designation);
editor.ReplaceNode(identifier, declarationExpression);
editor.ReplaceNode(identifier, newDeclaration);
}
}
else
{
foreach ((var _, var declaration, var identifier) in declarationsToAdd)
{
editor.ReplaceNode(identifier, declaration);
}
}
}
public static TypeSyntax GenerateTypeSyntaxOrVar(
......@@ -253,15 +271,14 @@ private static IEnumerable<SyntaxTrivia> MassageTrivia(IEnumerable<SyntaxTrivia>
}
private async Task<bool> SemanticsChangedAsync(
Document document,
VariableDeclarationSyntax declaration,
ExpressionSyntax invocationOrCreation,
TypeSyntax newType,
IdentifierNameSyntax identifier,
DeclarationExpressionSyntax declarationExpression,
Document document,
List<(ILocalSymbol, DeclarationExpressionSyntax, IdentifierNameSyntax)> declarationsToAdd,
ExpressionSyntax invocationOrCreation,
CancellationToken cancellationToken)
{
if (newType.IsVar)
var anyVar = declarationsToAdd.Any(t => t.Item2.Type.IsVar);
if (anyVar)
{
// Options want us to use 'var' if we can. Make sure we didn't change
// the semantics of the call by doing this.
......@@ -282,10 +299,15 @@ private static IEnumerable<SyntaxTrivia> MassageTrivia(IEnumerable<SyntaxTrivia>
return true;
}
var updatedTopmostContainer = topmostContainer;
var annotation = new SyntaxAnnotation();
var updatedTopmostContainer = topmostContainer.ReplaceNode(
invocationOrCreation, invocationOrCreation.ReplaceNode(identifier, declarationExpression)
foreach ((var _, var declaration, var identifier) in declarationsToAdd)
{
updatedTopmostContainer = updatedTopmostContainer.ReplaceNode(
invocationOrCreation, invocationOrCreation.ReplaceNode(identifier, declaration)
.WithAdditionalAnnotations(annotation));
}
if (!TryGetSpeculativeSemanticModel(semanticModel,
topmostContainer.SpanStart, updatedTopmostContainer, out var speculativeModel))
......@@ -307,7 +329,7 @@ private static IEnumerable<SyntaxTrivia> MassageTrivia(IEnumerable<SyntaxTrivia>
return false;
}
private SyntaxNode GetTopmostContainer(ExpressionSyntax expression)
private SyntaxNode GetTopmostContainer(CSharpSyntaxNode expression)
{
return expression.GetAncestorsOrThis(
a => a is StatementSyntax ||
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册