diff --git a/src/EditorFeatures/CSharpTest/CodeActions/ConvertLinq/ConvertLinqQueryToForEachTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/ConvertLinq/ConvertLinqQueryToForEachTests.cs index 0bf1599b2ffaf96e1572718be1004395b62ea8e5..2c15e9e53e3b2d544db580af8b7243d3b08df26b 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/ConvertLinq/ConvertLinqQueryToForEachTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/ConvertLinq/ConvertLinqQueryToForEachTests.cs @@ -1,7 +1,9 @@ // 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 M(IEnumerable 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 c = new List{ 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 c = new List{ 1, 2, 3, 4, 5, 6, 7 }; + IEnumerable 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 c = new List{ 1, 2, 3, 4, 5, 6, 7 }; + var r = [|from i in c select i+1|]; + + void M(IEnumerable enumerable) { } + } +}"; + string output = @" +using System; +using System.Collections.Generic; +using System.Linq; +class Query +{ + public static void Main(string[] args) + { + List c = new List{ 1, 2, 3, 4, 5, 6, 7 }; + IEnumerable enumerable() + { + foreach (var i in c) + { + yield return i + 1; + } + } + + var r = enumerable(); + + void M(IEnumerable 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 c = new List{ 1, 2, 3, 4, 5, 6, 7 }; + var r = [|from i in c select i+1|]; + + Action myLambda = enumerable => { }; + } +}"; + string output = @" +using System; +using System.Collections.Generic; +using System.Linq; +class Query +{ + public static void Main(string[] args) + { + List c = new List{ 1, 2, 3, 4, 5, 6, 7 }; + IEnumerable enumerable1() + { + foreach (var i in c) + { + yield return i + 1; + } + } + + var r = enumerable1(); + + Action 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 c = new List{ 1, 2, 3, 4, 5, 6, 7 }; + var r = [|from i in c select i+1|]; + + Action myLambda = enumerable => { }; + } +}"; + string output = @" +using System; +using System.Collections.Generic; +using System.Linq; +class Query +{ + public static void Main(string[] args) + { + List c = new List{ 1, 2, 3, 4, 5, 6, 7 }; + IEnumerable enumerable() + { + foreach (var i in c) + { + yield return i + 1; + } + } + + var r = enumerable(); + + Action myLambda = enumerable => { }; + } +}"; + await TestInRegularAndScriptAsync(source, output, parseOptions: new CSharpParseOptions(CodeAnalysis.CSharp.LanguageVersion.CSharp8)); + } + + #endregion } } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs index 644ef5d8c8891f63c49e72ad2d9f2c00b2d4dd67..ad3fafb54ee3e1ecf4c4b6f4819b351d95f08958 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs @@ -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[] diff --git a/src/EditorFeatures/CSharpTest/ConvertForToForEach/ConvertForToForEachTests.cs b/src/EditorFeatures/CSharpTest/ConvertForToForEach/ConvertForToForEachTests.cs index 4439a5334d5e282e0df3b6d513e88f56fd803c82..5860328d7127f35997bc6b39ecfde5431235ae95 100644 --- a/src/EditorFeatures/CSharpTest/ConvertForToForEach/ConvertForToForEachTests.cs +++ b/src/EditorFeatures/CSharpTest/ConvertForToForEach/ConvertForToForEachTests.cs @@ -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 myLambda = v => { }; + } +}", + @"using System; + +class C +{ + void Test(string[] array) + { + foreach (string {|Rename:v1|} in array) + { + Console.WriteLine(v1); + } + + Action 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 myLambda = v => { }; + } +}", + @"using System; + +class C +{ + void Test(string[] array) + { + foreach (string {|Rename:v|} in array) + { + Console.WriteLine(v); + } + + Action myLambda = v => { }; + } +}", parameters: new TestParameters(new CSharpParseOptions(LanguageVersion.CSharp8))); + } } } diff --git a/src/EditorFeatures/CSharpTest/RemoveUnusedParametersAndValues/RemoveUnusedValueAssignmentTests.cs b/src/EditorFeatures/CSharpTest/RemoveUnusedParametersAndValues/RemoveUnusedValueAssignmentTests.cs index 4716db5c39900a71bd54a176c76b65b925a743bd..e75ed380bb9a777bde6c61e5e7f5da4d97fbcfb4 100644 --- a/src/EditorFeatures/CSharpTest/RemoveUnusedParametersAndValues/RemoveUnusedValueAssignmentTests.cs +++ b/src/EditorFeatures/CSharpTest/RemoveUnusedParametersAndValues/RemoveUnusedValueAssignmentTests.cs @@ -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 myLambda = unused => { }; + + return x; + } + + int M2() => 0; +}", +@" +using System; +class C +{ + int M() + { + int unused1 = M2(); + int x = 2; + Action 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 myLambda = unused => { }; + + return x; + } + + int M2() => 0; +}", +@" +using System; +class C +{ + int M() + { + int unused = M2(); + int x = 2; + Action myLambda = unused => { }; + + return x; + } + + int M2() => 0; +}", options: PreferUnusedLocal, parseOptions: new CSharpParseOptions(LanguageVersion.CSharp8)); + } } } diff --git a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs index a72b576cc2210be47118449e255c3c1f5123443c..b9c766ff6d418fee4b47403fa3d7034ea14259be 100644 --- a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs +++ b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs @@ -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; diff --git a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSemanticFactsService.cs b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSemanticFactsService.cs index 2aea68cbf87321383863717734984ae86277822f..3af4a70bd810aadbce748cdb5a059a8655b2f21c 100644 --- a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSemanticFactsService.cs +++ b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSemanticFactsService.cs @@ -26,6 +26,30 @@ private CSharpSemanticFactsService() { } + protected override IEnumerable 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; diff --git a/src/Workspaces/Core/Portable/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs b/src/Workspaces/Core/Portable/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs index db7e69c219902782cac528a63c83c72708d17395..f5c40d7b753512f80cb16c8d781d664af7c850cd 100644 --- a/src/Workspaces/Core/Portable/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs +++ b/src/Workspaces/Core/Portable/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs @@ -1,6 +1,7 @@ // 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 candidates, IEnumerable usedNames, CancellationToken cancellationToken) + /// + /// 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. + /// + protected virtual IEnumerable GetCollidableSymbols(SemanticModel semanticModel, SyntaxNode location, SyntaxNode container, CancellationToken cancellationToken) + => semanticModel.LookupSymbols(location.SpanStart).Concat(semanticModel.GetExistingSymbols(container, cancellationToken)); + + private SyntaxToken GenerateUniqueName(string baseName, IEnumerable usedNames) { return this.SyntaxFactsService.ToIdentifierToken( NameGenerator.EnsureUniqueness( - baseName, candidates.Select(s => s.Name).Concat(usedNames), this.SyntaxFactsService.IsCaseSensitive)); + baseName, usedNames, this.SyntaxFactsService.IsCaseSensitive)); } } } diff --git a/src/Workspaces/Core/Portable/LanguageServices/SemanticsFactsService/ISemanticFactsService.cs b/src/Workspaces/Core/Portable/LanguageServices/SemanticsFactsService/ISemanticFactsService.cs index 32616533e4c6a5c17976ae8b9e54b55b57af0883..69fca6ee5514a7497e9d64fb0a6e58c1080a0ffd 100644 --- a/src/Workspaces/Core/Portable/LanguageServices/SemanticsFactsService/ISemanticFactsService.cs +++ b/src/Workspaces/Core/Portable/LanguageServices/SemanticsFactsService/ISemanticFactsService.cs @@ -112,9 +112,8 @@ internal interface ISemanticFactsService : ILanguageService SemanticModel semanticModel, SyntaxNode location, SyntaxNode containerOpt, string baseName, IEnumerable usedNames, CancellationToken cancellationToken); - SyntaxToken GenerateUniqueName( - SemanticModel semanticModel, SyntaxNode location, - SyntaxNode containerOpt, string baseName, Func filter, IEnumerable usedNames, CancellationToken cancellationToken); + SyntaxToken GenerateUniqueName(SemanticModel semanticModel, SyntaxNode location, SyntaxNode containerOpt, string baseName, + Func filter, IEnumerable usedNames, CancellationToken cancellationToken); SyntaxToken GenerateUniqueLocalName( SemanticModel semanticModel, SyntaxNode location, diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/SemanticModelExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/SemanticModelExtensions.cs index ab783bf1059bff3427d82340a3093eaf89f8434c..5b71c9d17c35bccb3eb8aa43098380bb6c86f210 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/SemanticModelExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/SemanticModelExtensions.cs @@ -1,5 +1,6 @@ // 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 GetAllDeclaredSymbols( - this SemanticModel semanticModel, SyntaxNode container, CancellationToken cancellationToken) + this SemanticModel semanticModel, SyntaxNode container, CancellationToken cancellationToken, Func filter = null) { var symbols = new HashSet(); if (container != null) { - GetAllDeclaredSymbols(semanticModel, container, symbols, cancellationToken); + GetAllDeclaredSymbols(semanticModel, container, symbols, cancellationToken, filter); } return symbols; } public static IEnumerable GetExistingSymbols( - this SemanticModel semanticModel, SyntaxNode container, CancellationToken cancellationToken) + this SemanticModel semanticModel, SyntaxNode container, CancellationToken cancellationToken, Func 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 symbols, CancellationToken cancellationToken) + HashSet symbols, CancellationToken cancellationToken, Func 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 filter) + => filter != null ? filter(node) : true; } } } diff --git a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSemanticFactsService.vb b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSemanticFactsService.vb index 5677677fcb3eb7081471be14b23a7d5792a82403..88466103bb4a6e0d9486e0c07c6dd3b714c77375 100644 --- a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSemanticFactsService.vb +++ b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSemanticFactsService.vb @@ -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