提交 64be2a31 编写于 作者: D David Barbet

Fix declaration name completion when local functions present.

上级 8911c9fe
......@@ -4182,5 +4182,93 @@ IEnumerable<int> M(IEnumerable<int> nums)
}
#endregion
#region Local Functions
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertQueryToForEach)]
public async Task EnumerableFunctionDoesNotUseLocalFunctionName()
{
string source = @"
using System;
using System.Collections.Generic;
using System.Linq;
class Query
{
public static void Main(string[] args)
{
List<int> c = new List<int>{ 1, 2, 3, 4, 5, 6, 7 };
var r = [|from i in c select i+1|];
void enumerable() { }
}
}";
string output = @"
using System;
using System.Collections.Generic;
using System.Linq;
class Query
{
public static void Main(string[] args)
{
List<int> c = new List<int>{ 1, 2, 3, 4, 5, 6, 7 };
IEnumerable<int> enumerable1()
{
foreach (var i in c)
{
yield return i + 1;
}
}
var r = enumerable1();
void enumerable() { }
}
}";
await TestInRegularAndScriptAsync(source, output);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertQueryToForEach)]
public async Task EnumerableFunctionCanUseLocalFunctionParameterName()
{
string source = @"
using System;
using System.Collections.Generic;
using System.Linq;
class Query
{
public static void Main(string[] args)
{
List<int> c = new List<int>{ 1, 2, 3, 4, 5, 6, 7 };
var r = [|from i in c select i+1|];
void M(IEnumerable<int> enumerable) { }
}
}";
string output = @"
using System;
using System.Collections.Generic;
using System.Linq;
class Query
{
public static void Main(string[] args)
{
List<int> c = new List<int>{ 1, 2, 3, 4, 5, 6, 7 };
IEnumerable<int> enumerable()
{
foreach (var i in c)
{
yield return i + 1;
}
}
var r = enumerable();
void M(IEnumerable<int> enumerable) { }
}
}";
await TestInRegularAndScriptAsync(source, output);
}
#endregion
}
}
......@@ -1718,6 +1718,180 @@ void M2()
expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name);
}
[WorkItem(35891, "https://github.com/dotnet/roslyn/issues/35891")]
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task TestCompletionDoesNotUseLocalAsLocalFunctionParameter()
{
var markup = @"
class ClassA
{
class ClassB { }
void M()
{
ClassB classB = new ClassB();
void LocalM1(ClassB $$) { }
}
}
";
await VerifyItemIsAbsentAsync(markup, "classB");
}
[WorkItem(35891, "https://github.com/dotnet/roslyn/issues/35891")]
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task TestCompletionDoesNotUseLocalAsLocalFunctionVariable()
{
var markup = @"
class ClassA
{
class ClassB { }
void M()
{
ClassB classB = new ClassB();
void LocalM1()
{
ClassB $$
}
}
}
";
await VerifyItemIsAbsentAsync(markup, "classB");
}
[WorkItem(35891, "https://github.com/dotnet/roslyn/issues/35891")]
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task TestCompletionDoesNotUseLocalInNestedLocalFunction()
{
var markup = @"
class ClassA
{
class ClassB { }
void M()
{
ClassB classB = new ClassB();
void LocalM1()
{
void LocalM2()
{
ClassB $$
}
}
}
}
";
await VerifyItemIsAbsentAsync(markup, "classB");
}
[WorkItem(35891, "https://github.com/dotnet/roslyn/issues/35891")]
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task TestCompletionDoesNotUseLocalFunctionParameterInNestedLocalFunction()
{
var markup = @"
class ClassA
{
class ClassB { }
void M()
{
void LocalM1(ClassB classB)
{
void LocalM2()
{
ClassB $$
}
}
}
}
";
await VerifyItemIsAbsentAsync(markup, "classB");
}
[WorkItem(35891, "https://github.com/dotnet/roslyn/issues/35891")]
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task TestCompletionCanUseLocalFunctionParameterAsParameter()
{
var markup = @"
class ClassA
{
class ClassB { }
void M()
{
void LocalM1(ClassB classB) { }
void LocalM2(ClassB $$) { }
}
}
";
await VerifyItemExistsAsync(markup, "classB", glyph: (int)Glyph.Parameter,
expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name);
}
[WorkItem(35891, "https://github.com/dotnet/roslyn/issues/35891")]
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task TestCompletionCanUseLocalFunctionVariableAsParameter()
{
var markup = @"
class ClassA
{
class ClassB { }
void M()
{
void LocalM1()
{
ClassB classB
}
void LocalM2(ClassB $$) { }
}
}
";
await VerifyItemExistsAsync(markup, "classB", glyph: (int)Glyph.Parameter,
expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name);
}
[WorkItem(35891, "https://github.com/dotnet/roslyn/issues/35891")]
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task TestCompletionCanUseLocalFunctionParameterAsVariable()
{
var markup = @"
class ClassA
{
class ClassB { }
void M()
{
void LocalM1(ClassB classB) { }
void LocalM2()
{
ClassB $$
}
}
}
";
await VerifyItemExistsAsync(markup, "classB", glyph: (int)Glyph.Local,
expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name);
}
[WorkItem(35891, "https://github.com/dotnet/roslyn/issues/35891")]
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task TestCompletionCanUseLocalFunctionVariableAsVariable()
{
var markup = @"
class ClassA
{
class ClassB { }
void M()
{
void LocalM1()
{
ClassB classB
}
void LocalM2()
{
ClassB $$
}
}
}
";
await VerifyItemExistsAsync(markup, "classB", glyph: (int)Glyph.Local,
expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name);
}
private static NamingStylePreferences NamesEndWithSuffixPreferences()
{
var specificationStyles = new[]
......
......@@ -1364,6 +1364,78 @@ void Test(string[][] array)
Console.WriteLine(array[i][i]);
}
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertForToForEach)]
public async Task TestDoesNotUseLocalFunctionName()
{
await TestInRegularAndScript1Async(
@"using System;
class C
{
void Test(string[] array)
{
[||]for (int i = 0; i < array.Length; i++)
{
Console.WriteLine(array[i]);
}
void v() { }
}
}",
@"using System;
class C
{
void Test(string[] array)
{
foreach (string {|Rename:v1|} in array)
{
Console.WriteLine(v1);
}
void v() { }
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertForToForEach)]
public async Task TestUsesLocalFunctionParameterName()
{
await TestInRegularAndScript1Async(
@"using System;
class C
{
void Test(string[] array)
{
[||]for (int i = 0; i < array.Length; i++)
{
Console.WriteLine(array[i]);
}
void M(string v)
{
}
}
}",
@"using System;
class C
{
void Test(string[] array)
{
foreach (string {|Rename:v|} in array)
{
Console.WriteLine(v);
}
void M(string v)
{
}
}
}");
}
}
......
......@@ -6863,5 +6863,69 @@ public static void Test()
}
}", optionName);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedValues)]
public async Task DoesNotUseLocalFunctionName_PreferUnused()
{
await TestInRegularAndScriptAsync(
@"class C
{
int M()
{
int [|x|] = M2();
x = 2;
return x;
void unused() { }
}
int M2() => 0;
}",
@"class C
{
int M()
{
int unused1 = M2();
int x = 2;
return x;
void unused() { }
}
int M2() => 0;
}", options: PreferUnusedLocal);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedValues)]
public async Task CanUseLocalFunctionParameterName_PreferUnused()
{
await TestInRegularAndScriptAsync(
@"class C
{
int M()
{
int [|x|] = M2();
x = 2;
return x;
void MLocal(int unused) { }
}
int M2() => 0;
}",
@"class C
{
int M()
{
int unused = M2();
int x = 2;
return x;
void MLocal(int unused) { }
}
int M2() => 0;
}", options: PreferUnusedLocal);
}
}
}
......@@ -221,6 +221,7 @@ private Glyph GetGlyph(SymbolKind kind, Accessibility? declaredAccessibility)
var rules = await document.GetNamingRulesAsync(FallbackNamingRules.CompletionOfferingRules, cancellationToken).ConfigureAwait(false);
var result = new Dictionary<string, SymbolKind>();
var semanticFactsService = context.GetLanguageService<ISemanticFactsService>();
var syntaxFactsService = context.GetLanguageService<ISyntaxFactsService>();
foreach (var kind in declarationInfo.PossibleSymbolKinds)
{
......@@ -244,7 +245,7 @@ private Glyph GetGlyph(SymbolKind kind, Accessibility? declaredAccessibility)
var targetToken = context.TargetToken;
var uniqueName = semanticFactsService.GenerateUniqueName(
context.SemanticModel,
context.TargetToken.Parent,
targetToken.Parent,
containerOpt: null,
baseName: name,
filter: IsRelevantSymbolKind,
......
......@@ -26,6 +26,23 @@ private CSharpSemanticFactsService()
{
}
protected override IEnumerable<ISymbol> GetUsedSymbols(SemanticModel semanticModel, SyntaxNode location, SyntaxNode container, CancellationToken cancellationToken)
{
// Get all the symbols visible to the current location.
var visibleSymbols = semanticModel.LookupSymbols(location.SpanStart);
// Some symbols in the enclosing block could cause conflicts even if they are not available at the location.
// E.g. symbols inside if statements / try catch statements.
// Walk through the enclosing block to find them, but make sure to exclude local functions because
// a) Visible local function symbols would be returned from LookupSymbols (e.g. location is inside a local function).
// b) Local function symbols are only in scope inside the local function.
// Relevant ones (e.g. method name) would be returned by a).
var symbolsInBlock = semanticModel.GetExistingSymbols(container, cancellationToken,
filter: (SyntaxNode n) => !n.IsKind(SyntaxKind.LocalFunctionStatement));
return symbolsInBlock.Concat(visibleSymbols);
}
public bool SupportsImplicitInterfaceImplementation => true;
public bool ExposesAnonymousFunctionParameterNames => false;
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
......@@ -59,20 +60,20 @@ internal abstract class AbstractSemanticFactsService
var container = containerOpt ?? location.AncestorsAndSelf().FirstOrDefault(
a => syntaxFacts.IsExecutableBlock(a) || syntaxFacts.IsMethodBody(a));
var candidates = semanticModel.LookupSymbols(location.SpanStart).Concat(
semanticModel.GetExistingSymbols(container, cancellationToken));
var candidates = GetUsedSymbols(semanticModel, location, container, cancellationToken);
var filteredCandidates = filter != null ? candidates.Where(filter) : candidates;
return GenerateUniqueName(
semanticModel, location, containerOpt, baseName, filter != null ? candidates.Where(filter) : candidates, usedNames, cancellationToken);
return GenerateUniqueName(baseName, filteredCandidates.Select(s => s.Name).Concat(usedNames));
}
private SyntaxToken GenerateUniqueName(
SemanticModel semanticModel, SyntaxNode location, SyntaxNode containerOpt,
string baseName, IEnumerable<ISymbol> candidates, IEnumerable<string> usedNames, CancellationToken cancellationToken)
protected virtual IEnumerable<ISymbol> GetUsedSymbols(SemanticModel semanticModel, SyntaxNode location, SyntaxNode container, CancellationToken cancellationToken)
=> semanticModel.LookupSymbols(location.SpanStart).Concat(semanticModel.GetExistingSymbols(container, cancellationToken));
private SyntaxToken GenerateUniqueName(string baseName, IEnumerable<string> usedNames)
{
return this.SyntaxFactsService.ToIdentifierToken(
NameGenerator.EnsureUniqueness(
baseName, candidates.Select(s => s.Name).Concat(usedNames), this.SyntaxFactsService.IsCaseSensitive));
baseName, usedNames, this.SyntaxFactsService.IsCaseSensitive));
}
}
}
......@@ -112,9 +112,8 @@ internal interface ISemanticFactsService : ILanguageService
SemanticModel semanticModel, SyntaxNode location,
SyntaxNode containerOpt, string baseName, IEnumerable<string> usedNames, CancellationToken cancellationToken);
SyntaxToken GenerateUniqueName(
SemanticModel semanticModel, SyntaxNode location,
SyntaxNode containerOpt, string baseName, Func<ISymbol, bool> filter, IEnumerable<string> usedNames, CancellationToken cancellationToken);
SyntaxToken GenerateUniqueName(SemanticModel semanticModel, SyntaxNode location, SyntaxNode containerOpt, string baseName,
Func<ISymbol, bool> filter, IEnumerable<string> usedNames, CancellationToken cancellationToken);
SyntaxToken GenerateUniqueLocalName(
SemanticModel semanticModel, SyntaxNode location,
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
......@@ -221,29 +222,29 @@ public static SemanticModel GetOriginalSemanticModel(this SemanticModel semantic
}
public static HashSet<ISymbol> GetAllDeclaredSymbols(
this SemanticModel semanticModel, SyntaxNode container, CancellationToken cancellationToken)
this SemanticModel semanticModel, SyntaxNode container, CancellationToken cancellationToken, Func<SyntaxNode, bool> filter = null)
{
var symbols = new HashSet<ISymbol>();
if (container != null)
{
GetAllDeclaredSymbols(semanticModel, container, symbols, cancellationToken);
GetAllDeclaredSymbols(semanticModel, container, symbols, cancellationToken, filter);
}
return symbols;
}
public static IEnumerable<ISymbol> GetExistingSymbols(
this SemanticModel semanticModel, SyntaxNode container, CancellationToken cancellationToken)
this SemanticModel semanticModel, SyntaxNode container, CancellationToken cancellationToken, Func<SyntaxNode, bool> filter = null)
{
// Ignore an anonymous type property or tuple field. It's ok if they have a name that
// matches the name of the local we're introducing.
return semanticModel.GetAllDeclaredSymbols(container, cancellationToken)
return semanticModel.GetAllDeclaredSymbols(container, cancellationToken, filter)
.Where(s => !s.IsAnonymousTypeProperty() && !s.IsTupleField());
}
private static void GetAllDeclaredSymbols(
SemanticModel semanticModel, SyntaxNode node,
HashSet<ISymbol> symbols, CancellationToken cancellationToken)
HashSet<ISymbol> symbols, CancellationToken cancellationToken, Func<SyntaxNode, bool> filter = null)
{
var symbol = semanticModel.GetDeclaredSymbol(node, cancellationToken);
......@@ -256,9 +257,16 @@ public static SemanticModel GetOriginalSemanticModel(this SemanticModel semantic
{
if (child.IsNode)
{
GetAllDeclaredSymbols(semanticModel, child.AsNode(), symbols, cancellationToken);
var childNode = child.AsNode();
if (ApplyFilter(childNode, filter))
{
GetAllDeclaredSymbols(semanticModel, child.AsNode(), symbols, cancellationToken, filter);
}
}
}
static bool ApplyFilter(SyntaxNode node, Func<SyntaxNode, bool> filter)
=> filter != null ? filter(node) : true;
}
}
}
......@@ -328,8 +328,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return node.FirstAncestorOrSelf(Of NameOfExpressionSyntax) IsNot Nothing
End Function
Private Function ISemanticFactsService_GenerateUniqueName1(
semanticModel As SemanticModel, location As SyntaxNode, containerOpt As SyntaxNode, baseName As String, filter As Func(Of ISymbol, Boolean), usedNames As IEnumerable(Of String), cancellationToken As CancellationToken) As SyntaxToken Implements ISemanticFactsService.GenerateUniqueName
Private Function ISemanticFactsService_GenerateUniqueName(semanticModel As SemanticModel, location As SyntaxNode, containerOpt As SyntaxNode, baseName As String, filter As Func(Of ISymbol, Boolean), usedNames As IEnumerable(Of String), cancellationToken As CancellationToken) As SyntaxToken Implements ISemanticFactsService.GenerateUniqueName
Return MyBase.GenerateUniqueName(semanticModel, location, containerOpt, baseName, filter, usedNames, cancellationToken)
End Function
End Class
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册