提交 aedb97b9 编写于 作者: C CyrusNajmabadi

Add support for adding usings to resolve a 'Deconstruct' extension method.

上级 d1e3d73d
......@@ -1067,5 +1067,47 @@ public T F<T>(T x)
}";
await TestAsync(initialText, expectedText);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddImport)]
public async Task TestDeconstructExtension()
{
await TestAsync(
@"
class Program
{
void M(Program p)
{
var (x, y) = [|p|];
}
}
namespace N
{
static class E
{
public static void Deconstruct(this Program p, out int x, out int y) { }
}
}",
@"
using N;
class Program
{
void M(Program p)
{
var (x, y) = [|p|];
}
}
namespace N
{
static class E
{
public static void Deconstruct(this Program p, out int x, out int y) { }
}
}",
parseOptions: null,
withScriptOption: false);
}
}
}
\ No newline at end of file
......@@ -117,6 +117,11 @@ internal static class AddImportDiagnosticIds
/// </summary>
public const string CS7036 = nameof(CS7036);
/// <summary>
/// o Deconstruct instance or extension method was found for type 'X', with N out parameters
/// </summary>
public const string CS8129 = nameof(CS8129);
public static ImmutableArray<string> FixableTypeIds =
ImmutableArray.Create(
CS0103,
......@@ -127,7 +132,8 @@ internal static class AddImportDiagnosticIds
CS0307,
CS0616,
CS1580,
CS1581);
CS1581,
CS8129);
public static ImmutableArray<string> FixableDiagnosticIds =
FixableTypeIds.Concat(ImmutableArray.Create(
......@@ -260,6 +266,9 @@ protected override bool CanAddImport(SyntaxNode node, CancellationToken cancella
return true;
}
protected override bool CanAddImportForDeconstruct(Diagnostic diagnostic, SyntaxNode node)
=> diagnostic.Id == CS8129;
protected override bool CanAddImportForNamespace(Diagnostic diagnostic, SyntaxNode node, out SimpleNameSyntax nameNode)
{
nameNode = null;
......@@ -346,6 +355,12 @@ private static SimpleNameSyntax GetLeftMostSimpleName(QualifiedNameSyntax qn)
return semanticModel.GetUsingNamespacesInScope(node);
}
protected override ITypeSymbol GetDeconstructInfo(
SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken)
{
return semanticModel.GetTypeInfo(node).Type;
}
protected override ITypeSymbol GetQueryClauseInfo(
SemanticModel semanticModel,
SyntaxNode node,
......
......@@ -40,10 +40,12 @@ internal abstract partial class AbstractAddImportCodeFixProvider<TSimpleNameSynt
protected abstract bool CanAddImport(SyntaxNode node, CancellationToken cancellationToken);
protected abstract bool CanAddImportForMethod(Diagnostic diagnostic, ISyntaxFactsService syntaxFacts, SyntaxNode node, out TSimpleNameSyntax nameNode);
protected abstract bool CanAddImportForNamespace(Diagnostic diagnostic, SyntaxNode node, out TSimpleNameSyntax nameNode);
protected abstract bool CanAddImportForDeconstruct(Diagnostic diagnostic, SyntaxNode node);
protected abstract bool CanAddImportForQuery(Diagnostic diagnostic, SyntaxNode node);
protected abstract bool CanAddImportForType(Diagnostic diagnostic, SyntaxNode node, out TSimpleNameSyntax nameNode);
protected abstract ISet<INamespaceSymbol> GetImportNamespacesInScope(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken);
protected abstract ITypeSymbol GetDeconstructInfo(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken);
protected abstract ITypeSymbol GetQueryClauseInfo(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken);
protected abstract bool IsViableExtensionMethod(IMethodSymbol method, SyntaxNode expression, SemanticModel semanticModel, ISyntaxFactsService syntaxFacts, CancellationToken cancellationToken);
......@@ -56,59 +58,64 @@ internal abstract partial class AbstractAddImportCodeFixProvider<TSimpleNameSynt
protected abstract (string description, bool hasExistingImport) GetDescription(Document document, INamespaceOrTypeSymbol symbol, SemanticModel semanticModel, SyntaxNode root, CancellationToken cancellationToken);
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
// We might have multiple different diagnostics covering the same span. Have to
// process them all as we might produce different fixes for each diagnostic.
var resultCount = 0;
foreach (var diagnostic in context.Diagnostics)
{
resultCount += await HandleDiagnosticAsync(context, diagnostic).ConfigureAwait(false);
if (resultCount >= MaxResults)
{
break;
}
}
}
private async Task<int> HandleDiagnosticAsync(CodeFixContext context, Diagnostic diagnostic)
{
var document = context.Document;
var span = context.Span;
var diagnostics = context.Diagnostics;
var cancellationToken = context.CancellationToken;
var project = document.Project;
var diagnostic = diagnostics.First();
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var ancestors = root.FindToken(span.Start, findInsideTrivia: true).GetAncestors<SyntaxNode>();
if (!ancestors.Any())
{
return;
}
var node = root.FindToken(span.Start, findInsideTrivia: true)
.GetAncestor(n => n.Span.Contains(span) && n != root);
var node = ancestors.FirstOrDefault(n => n.Span.Contains(span) && n != root);
if (node == null)
var count = 0;
if (node != null)
{
return;
}
var documentOptions = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var placeSystemNamespaceFirst = documentOptions.GetOption(
GenerationOptions.PlaceSystemNamespaceFirst);
var documentOptions = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var placeSystemNamespaceFirst = documentOptions.GetOption(
GenerationOptions.PlaceSystemNamespaceFirst);
using (Logger.LogBlock(FunctionId.Refactoring_AddImport, cancellationToken))
{
if (!cancellationToken.IsCancellationRequested)
using (Logger.LogBlock(FunctionId.Refactoring_AddImport, cancellationToken))
{
if (this.CanAddImport(node, cancellationToken))
if (!cancellationToken.IsCancellationRequested)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var allSymbolReferences = await FindResultsAsync(document, semanticModel, diagnostic, node, cancellationToken).ConfigureAwait(false);
// Nothing found at all. No need to proceed.
if (allSymbolReferences.Length == 0)
if (this.CanAddImport(node, cancellationToken))
{
return;
}
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var allSymbolReferences = await FindResultsAsync(document, semanticModel, diagnostic, node, cancellationToken).ConfigureAwait(false);
foreach (var reference in allSymbolReferences)
{
cancellationToken.ThrowIfCancellationRequested();
var codeAction = await reference.CreateCodeActionAsync(document, node, placeSystemNamespaceFirst, cancellationToken).ConfigureAwait(false);
if (codeAction != null)
// Nothing found at all. No need to proceed.
foreach (var reference in allSymbolReferences)
{
context.RegisterCodeFix(codeAction, diagnostic);
cancellationToken.ThrowIfCancellationRequested();
var codeAction = await reference.CreateCodeActionAsync(document, node, placeSystemNamespaceFirst, cancellationToken).ConfigureAwait(false);
if (codeAction != null)
{
context.RegisterCodeFix(codeAction, diagnostic);
count++;
}
}
}
}
}
}
return count;
}
private async Task<ImmutableArray<Reference>> FindResultsAsync(
......@@ -132,7 +139,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
// No exact matches found. Fall back to fuzzy searching.
// Only bother doing this for host workspaces. We don't want this for
// things like the Interactive workspace as this will cause us to
// create expensive bktrees which we won't even be able to save for
// create expensive bk-trees which we won't even be able to save for
// future use.
if (!IsHostOrTestWorkspace(project))
{
......@@ -265,7 +272,7 @@ private static bool IsHostOrTestWorkspace(Project project)
var compilation = referenceToCompilation.GetOrAdd(reference, r => CreateCompilation(project, r));
// Ignore netmodules. First, they're incredibly esoteric and barely used.
// Second, the SymbolFinder api doesn't even support searching them.
// Second, the SymbolFinder API doesn't even support searching them.
var assembly = compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol;
if (assembly != null)
{
......@@ -341,7 +348,7 @@ private bool IsInPackagesDirectory(PortableExecutableReference reference)
}
/// <summary>
/// Called when when we want to search a metadata reference. We create a dummy compilation
/// Called when we want to search a metadata reference. We create a dummy compilation
/// containing just that reference and we search that. That way we can get actual symbols
/// returned.
///
......@@ -377,7 +384,7 @@ private static HashSet<Project> GetViableUnreferencedProjects(Project project)
// Clearly we can't reference ourselves.
viableProjects.Remove(project);
// We can't reference any project that transitively depends on on us. Doing so would
// We can't reference any project that transitively depends on us. Doing so would
// cause a circular reference between projects.
var dependencyGraph = solution.GetProjectDependencyGraph();
var projectsThatTransitivelyDependOnThisProject = dependencyGraph.GetProjectsThatTransitivelyDependOnThisProject(project.Id);
......
......@@ -50,7 +50,7 @@ protected SearchScope(AbstractAddImportCodeFixProvider<TSimpleNameSyntax> provid
if (Exact)
{
// We did an exact, case insensitive, search. Case sensitive matches should
// be preffered though over insensitive ones.
// be preferred though over insensitive ones.
return symbols.SelectAsArray(s =>
SymbolResult.Create(s.Name, nameNode, s, weight: s.Name == name ? 0 : 1));
}
......@@ -78,9 +78,9 @@ private abstract class ProjectSearchScope : SearchScope
public ProjectSearchScope(
AbstractAddImportCodeFixProvider<TSimpleNameSyntax> provider,
Project project,
bool ignoreCase,
bool exact,
CancellationToken cancellationToken)
: base(provider, ignoreCase, cancellationToken)
: base(provider, exact, cancellationToken)
{
_project = project;
}
......@@ -102,9 +102,9 @@ private class AllSymbolsProjectSearchScope : ProjectSearchScope
public AllSymbolsProjectSearchScope(
AbstractAddImportCodeFixProvider<TSimpleNameSyntax> provider,
Project project,
bool ignoreCase,
bool exact,
CancellationToken cancellationToken)
: base(provider, project, ignoreCase, cancellationToken)
: base(provider, project, exact, cancellationToken)
{
}
......
......@@ -120,6 +120,7 @@ private async Task<ImmutableArray<SymbolReference>> DoAsync(SearchScope searchSc
{
tasks.Add(this.GetReferencesForCollectionInitializerMethodsAsync(searchScope));
tasks.Add(this.GetReferencesForQueryPatternsAsync(searchScope));
tasks.Add(this.GetReferencesForDeconstructAsync(searchScope));
}
await Task.WhenAll(tasks).ConfigureAwait(false);
......@@ -407,24 +408,64 @@ private async Task<ImmutableArray<SymbolReference>> GetReferencesForQueryPattern
if (type != null)
{
// find extension methods named "Select"
var symbols = await searchScope.FindDeclarationsAsync(
nameof(Enumerable.Select), nameNode: null, filter: SymbolFilter.Member).ConfigureAwait(false);
// Note: there is no "desiredName" when doing this. We're not going to do any
// renames of the user code. We're just looking for an extension method called
// "Select", but that name has no bearing on the code in question that we're
// trying to fix up.
var methodSymbols = OfType<IMethodSymbol>(symbols).SelectAsArray(s => s.WithDesiredName(null));
var viableExtensionMethods = GetViableExtensionMethods(methodSymbols, type, searchScope.CancellationToken);
var namespaceSymbols = viableExtensionMethods.SelectAsArray(s => s.WithSymbol(s.Symbol.ContainingNamespace));
return GetNamespaceSymbolReferences(searchScope, namespaceSymbols);
return await GetReferencesForExtensionMethodAsync(
searchScope, nameof(Enumerable.Select), type).ConfigureAwait(false);
}
}
return ImmutableArray<SymbolReference>.Empty;
}
/// <summary>
/// Searches for extension methods exactly called 'Deconstruct'. Returns
/// <see cref="SymbolReference"/>s to the <see cref="INamespaceSymbol"/>s that contain
/// the static classes that those extension methods are contained in.
/// </summary>
private async Task<ImmutableArray<SymbolReference>> GetReferencesForDeconstructAsync(SearchScope searchScope)
{
searchScope.CancellationToken.ThrowIfCancellationRequested();
if (_owner.CanAddImportForDeconstruct(_diagnostic, _node))
{
var type = _owner.GetDeconstructInfo(_semanticModel, _node, searchScope.CancellationToken);
if (type != null)
{
// Note: we could check that the extension methods have the right number of out-params.
// But that would involve figuring out what we're trying to deconstruct into. For now
// we'll just be permissive, with the assumption that there won't be that many matching
// 'Deconstruct' extension methods for the type of node that we're on.
return await GetReferencesForExtensionMethodAsync(
searchScope, "Deconstruct", type,
m => m.ReturnsVoid).ConfigureAwait(false);
}
}
return ImmutableArray<SymbolReference>.Empty;
}
private async Task<ImmutableArray<SymbolReference>> GetReferencesForExtensionMethodAsync(
SearchScope searchScope, string name, ITypeSymbol type, Func<IMethodSymbol, bool> predicate = null)
{
var symbols = await searchScope.FindDeclarationsAsync(
name, nameNode: null, filter: SymbolFilter.Member).ConfigureAwait(false);
// Note: there is no "desiredName" when doing this. We're not going to do any
// renames of the user code. We're just looking for an extension method called
// "Select", but that name has no bearing on the code in question that we're
// trying to fix up.
var methodSymbols = OfType<IMethodSymbol>(symbols).SelectAsArray(s => s.WithDesiredName(null));
var viableExtensionMethods = GetViableExtensionMethods(methodSymbols, type, searchScope.CancellationToken);
if (predicate != null)
{
viableExtensionMethods = viableExtensionMethods.WhereAsArray(s => predicate(s.Symbol));
}
var namespaceSymbols = viableExtensionMethods.SelectAsArray(s => s.WithSymbol(s.Symbol.ContainingNamespace));
return GetNamespaceSymbolReferences(searchScope, namespaceSymbols);
}
protected bool ExpressionBinds(
TSimpleNameSyntax nameNode, bool checkForExtensionMethods, CancellationToken cancellationToken)
{
......
......@@ -189,6 +189,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.AddImport
Return CanAddImportForTypeOrNamespaceCore(node, nameNode)
End Function
Protected Overrides Function CanAddImportForDeconstruct(diagnostic As Diagnostic, node As SyntaxNode) As Boolean
' Not supported yet.
Return False
End Function
Protected Overrides Function CanAddImportForQuery(diagnostic As Diagnostic, node As SyntaxNode) As Boolean
If diagnostic.Id <> BC36593 Then
Return False
......@@ -281,6 +286,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.AddImport
Return semanticModel.GetImportNamespacesInScope(node)
End Function
Protected Overrides Function GetDeconstructInfo(semanticModel As SemanticModel, node As SyntaxNode, cancellationToken As CancellationToken) As ITypeSymbol
Return Nothing
End Function
Protected Overrides Function GetQueryClauseInfo(
model As SemanticModel,
node As SyntaxNode,
......@@ -373,7 +382,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.AddImport
Dim importsStatement = GetImportsStatement(nameSyntax)
' Suppress diagnostics on the import we create. Because we only get here when we are
' adding a nuget package, it is certainly the case that in the preview this will not
' adding a NuGet package, it is certainly the case that in the preview this will not
' bind properly. It will look silly to show such an error, so we just suppress things.
importsStatement = importsStatement.WithAdditionalAnnotations(SuppressDiagnosticsAnnotation.Create())
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册