未验证 提交 5774395a 编写于 作者: D David 提交者: GitHub

Merge pull request #35914 from dibarbet/fix_completion_in_local_functions

Fix declaration name completion when local functions present.
// 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.Threading.Tasks;
using ICSharpCode.Decompiler.CSharp;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
......@@ -4182,5 +4184,177 @@ IEnumerable<int> M(IEnumerable<int> nums)
}
#endregion
#region Name Generation
[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);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertQueryToForEach)]
public async Task EnumerableFunctionDoesNotUseLambdaParameterNameWithCSharpLessThan8()
{
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|];
Action<int> myLambda = 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();
Action<int> myLambda = enumerable => { };
}
}";
await TestInRegularAndScriptAsync(source, output, parseOptions: new CSharpParseOptions(CodeAnalysis.CSharp.LanguageVersion.CSharp7_3));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertQueryToForEach)]
public async Task EnumerableFunctionCanUseLambdaParameterNameInCSharp8()
{
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|];
Action<int> myLambda = 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();
Action<int> myLambda = enumerable => { };
}
}";
await TestInRegularAndScriptAsync(source, output, parseOptions: new CSharpParseOptions(CodeAnalysis.CSharp.LanguageVersion.CSharp8));
}
#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[]
......
......@@ -4,6 +4,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.ConvertForToForEach;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings;
......@@ -1366,5 +1367,145 @@ void Test(string[][] array)
}
}");
}
[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)
{
}
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertForToForEach)]
public async Task TestDoesNotUseLambdaParameterWithCSharpLessThan8()
{
await TestInRegularAndScript1Async(
@"using System;
class C
{
void Test(string[] array)
{
[||]for (int i = 0; i < array.Length; i++)
{
Console.WriteLine(array[i]);
}
Action<int> myLambda = v => { };
}
}",
@"using System;
class C
{
void Test(string[] array)
{
foreach (string {|Rename:v1|} in array)
{
Console.WriteLine(v1);
}
Action<int> myLambda = v => { };
}
}", parameters: new TestParameters(new CSharpParseOptions(LanguageVersion.CSharp7_3)));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertForToForEach)]
public async Task TestUsesLambdaParameterNameInCSharp8()
{
await TestInRegularAndScript1Async(
@"using System;
class C
{
void Test(string[] array)
{
[||]for (int i = 0; i < array.Length; i++)
{
Console.WriteLine(array[i]);
}
Action<int> myLambda = v => { };
}
}",
@"using System;
class C
{
void Test(string[] array)
{
foreach (string {|Rename:v|} in array)
{
Console.WriteLine(v);
}
Action<int> myLambda = v => { };
}
}", parameters: new TestParameters(new CSharpParseOptions(LanguageVersion.CSharp8)));
}
}
}
......@@ -6863,5 +6863,141 @@ 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);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedValues)]
public async Task DoesNotUseLambdaFunctionParameterNameWithCSharpLessThan8_PreferUnused()
{
await TestInRegularAndScriptAsync(
@"
using System;
class C
{
int M()
{
int [|x|] = M2();
x = 2;
Action<int> myLambda = unused => { };
return x;
}
int M2() => 0;
}",
@"
using System;
class C
{
int M()
{
int unused1 = M2();
int x = 2;
Action<int> myLambda = unused => { };
return x;
}
int M2() => 0;
}", options: PreferUnusedLocal, parseOptions: new CSharpParseOptions(LanguageVersion.CSharp7_3));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedValues)]
public async Task CanUseLambdaFunctionParameterNameWithCSharp8_PreferUnused()
{
await TestInRegularAndScriptAsync(
@"
using System;
class C
{
int M()
{
int [|x|] = M2();
x = 2;
Action<int> myLambda = unused => { };
return x;
}
int M2() => 0;
}",
@"
using System;
class C
{
int M()
{
int unused = M2();
int x = 2;
Action<int> myLambda = unused => { };
return x;
}
int M2() => 0;
}", options: PreferUnusedLocal, parseOptions: new CSharpParseOptions(LanguageVersion.CSharp8));
}
}
}
......@@ -9,6 +9,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.LanguageServices;
......
......@@ -26,6 +26,30 @@ private CSharpSemanticFactsService()
{
}
protected override IEnumerable<ISymbol> GetCollidableSymbols(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.
var symbolsInBlock = semanticModel.GetExistingSymbols(container, cancellationToken,
descendInto: n => ShouldDescendInto(n));
return symbolsInBlock.Concat(visibleSymbols);
// Walk through the enclosing block symbols, but avoid exploring local functions
// a) Visible symbols from the local function would be returned by LookupSymbols
// (e.g. location is inside a local function, the local function method name).
// b) Symbols declared inside the local function do not cause collisions with symbols declared outside them, so avoid considering those symbols.
// Exclude lambdas as well when the language version is C# 8 or higher because symbols declared inside no longer collide with outer variables.
bool ShouldDescendInto(SyntaxNode node)
{
var isLanguageVersionGreaterOrEqualToCSharp8 = (semanticModel.Compilation as CSharpCompilation)?.LanguageVersion >= LanguageVersion.CSharp8;
return isLanguageVersionGreaterOrEqualToCSharp8 ? !SyntaxFactsService.IsAnonymousOrLocalFunction(node) : !SyntaxFactsService.IsLocalFunctionStatement(node);
}
}
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,25 @@ 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 = GetCollidableSymbols(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)
/// <summary>
/// Retrieves all symbols that could collide with a symbol at the specified location.
/// A symbol can possibly collide with the location if it is available to that location and/or
/// could cause a compiler error if its name is re-used at that location.
/// </summary>
protected virtual IEnumerable<ISymbol> GetCollidableSymbols(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> descendInto = 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, descendInto)
.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> descendInto = 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 (ShouldDescendInto(childNode, descendInto))
{
GetAllDeclaredSymbols(semanticModel, child.AsNode(), symbols, cancellationToken, descendInto);
}
}
}
static bool ShouldDescendInto(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.
先完成此消息的编辑!
想要评论请 注册