diff --git a/src/EditorFeatures/CSharpTest/InlineDeclaration/CSharpInlineDeclarationTests.cs b/src/EditorFeatures/CSharpTest/InlineDeclaration/CSharpInlineDeclarationTests.cs index 4a956f5d84f3561c819028e7a18bd9563b5fa05a..b7fe6d0d701d61343b2edd60897de19e289ec774 100644 --- a/src/EditorFeatures/CSharpTest/InlineDeclaration/CSharpInlineDeclarationTests.cs +++ b/src/EditorFeatures/CSharpTest/InlineDeclaration/CSharpInlineDeclarationTests.cs @@ -2005,6 +2005,135 @@ static void M(bool condition) var result = condition && int.TryParse(""2"", out x); Console.WriteLine(x); } +}"); + } + + [WorkItem(21907, "https://github.com/dotnet/roslyn/issues/21907")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineDeclaration)] + public async Task TestMissingOnCrossFunction1() + { + await TestMissingInRegularAndScriptAsync( +@" +using System; + +class Program +{ + static void Main(string[] args) + { + Method(); + } + + public static void Method() + { + [|T t|]; + void Local() + { + Out(out t); + Console.WriteLine(t); + } + Local(); + } + + public static void Out(out T t) => t = default; +}"); + } + + [WorkItem(21907, "https://github.com/dotnet/roslyn/issues/21907")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineDeclaration)] + public async Task TestMissingOnCrossFunction2() + { + await TestMissingInRegularAndScriptAsync( +@" +using System; + +class Program +{ + static void Main(string[] args) + { + Method(); + } + + public static void Method() + { + void Local() + { + [|T t|]; + void InnerLocal() + { + Out(out t); + Console.WriteLine(t); + } + } + Local(); + } + + public static void Out(out T t) => t = default; +}"); + } + + [WorkItem(21907, "https://github.com/dotnet/roslyn/issues/21907")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineDeclaration)] + public async Task TestMissingOnCrossFunction3() + { + await TestMissingInRegularAndScriptAsync( +@" +using System; + +class Program +{ + static void Main(string[] args) + { + Method(); + } + + public static void Method() + { + [|T t|]; + void Local() + { + { // <-- note this set of added braces + Out(out t); + Console.WriteLine(t); + } + } + Local(); + } + + public static void Out(out T t) => t = default; +}"); + } + + [WorkItem(21907, "https://github.com/dotnet/roslyn/issues/21907")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineDeclaration)] + public async Task TestMissingOnCrossFunction4() + { + await TestMissingInRegularAndScriptAsync( +@" +using System; + +class Program +{ + static void Main(string[] args) + { + Method(); + } + + public static void Method() + { + { // <-- note this set of added braces + [|T t|]; + void Local() + { + { // <-- and my axe + Out(out t); + Console.WriteLine(t); + } + } + Local(); + } + } + + public static void Out(out T t) => t = default; }"); } } diff --git a/src/EditorFeatures/CSharpTest/MoveDeclarationNearReference/MoveDeclarationNearReferenceTests.cs b/src/EditorFeatures/CSharpTest/MoveDeclarationNearReference/MoveDeclarationNearReferenceTests.cs index b5b6922ea08fa56c01b0db3cf49c470f6cae8f2d..ee76349de322bb72b748283684209bbc9cdcf9ba 100644 --- a/src/EditorFeatures/CSharpTest/MoveDeclarationNearReference/MoveDeclarationNearReferenceTests.cs +++ b/src/EditorFeatures/CSharpTest/MoveDeclarationNearReference/MoveDeclarationNearReferenceTests.cs @@ -1196,6 +1196,135 @@ static void Main(string[] args) Console.Write(i); } } +}"); + } + + [WorkItem(21907, "https://github.com/dotnet/roslyn/issues/21907")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveDeclarationNearReference)] + public async Task TestMissingOnCrossFunction1() + { + await TestMissingInRegularAndScriptAsync( +@" +using System; + +class Program +{ + static void Main(string[] args) + { + Method(); + } + + public static void Method() + { + [|T t|]; + void Local() + { + Out(out t); + Console.WriteLine(t); + } + Local(); + } + + public static void Out(out T t) => t = default; +}"); + } + + [WorkItem(21907, "https://github.com/dotnet/roslyn/issues/21907")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveDeclarationNearReference)] + public async Task TestMissingOnCrossFunction2() + { + await TestMissingInRegularAndScriptAsync( +@" +using System; + +class Program +{ + static void Main(string[] args) + { + Method(); + } + + public static void Method() + { + void Local() + { + [|T t|]; + void InnerLocal() + { + Out(out t); + Console.WriteLine(t); + } + } + Local(); + } + + public static void Out(out T t) => t = default; +}"); + } + + [WorkItem(21907, "https://github.com/dotnet/roslyn/issues/21907")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveDeclarationNearReference)] + public async Task TestMissingOnCrossFunction3() + { + await TestMissingInRegularAndScriptAsync( +@" +using System; + +class Program +{ + static void Main(string[] args) + { + Method(); + } + + public static void Method() + { + [|T t|]; + void Local() + { + { // <-- note this set of added braces + Out(out t); + Console.WriteLine(t); + } + } + Local(); + } + + public static void Out(out T t) => t = default; +}"); + } + + [WorkItem(21907, "https://github.com/dotnet/roslyn/issues/21907")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMoveDeclarationNearReference)] + public async Task TestMissingOnCrossFunction4() + { + await TestMissingInRegularAndScriptAsync( +@" +using System; + +class Program +{ + static void Main(string[] args) + { + Method(); + } + + public static void Method() + { + { // <-- note this set of added braces + [|T t|]; + void Local() + { + { // <-- and my axe + Out(out t); + Console.WriteLine(t); + } + } + Local(); + } + } + + public static void Out(out T t) => t = default; }"); } } diff --git a/src/Features/CSharp/Portable/InlineDeclaration/CSharpInlineDeclarationDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/InlineDeclaration/CSharpInlineDeclarationDiagnosticAnalyzer.cs index 70cf35abc02f4b20b7dccf2924ba41a90bc5fc9e..658050320c1e3240b64f2ccd3b87d7968597b864 100644 --- a/src/Features/CSharp/Portable/InlineDeclaration/CSharpInlineDeclarationDiagnosticAnalyzer.cs +++ b/src/Features/CSharp/Portable/InlineDeclaration/CSharpInlineDeclarationDiagnosticAnalyzer.cs @@ -194,6 +194,11 @@ private void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context, INamedTypeSymb // rewrite things. var outArgumentScope = GetOutArgumentScope(argumentExpression); + if (!outLocalSymbol.CanSafelyMoveLocalToBlock(enclosingBlockOfLocalStatement, outArgumentScope)) + { + return; + } + // Make sure that variable is not accessed outside of that scope. var dataFlow = semanticModel.AnalyzeDataFlow(outArgumentScope); if (dataFlow.ReadOutside.Contains(outLocalSymbol) || dataFlow.WrittenOutside.Contains(outLocalSymbol)) diff --git a/src/Features/CSharp/Portable/MoveDeclarationNearReference/CSharpMoveDeclarationNearReferenceCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/MoveDeclarationNearReference/CSharpMoveDeclarationNearReferenceCodeRefactoringProvider.cs index 4c0bd6a0d614e44bbb690d0f50f6bbd7c0fdfc65..9138d6e2afd424107477944927d56f28e23ab2dc 100644 --- a/src/Features/CSharp/Portable/MoveDeclarationNearReference/CSharpMoveDeclarationNearReferenceCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/MoveDeclarationNearReference/CSharpMoveDeclarationNearReferenceCodeRefactoringProvider.cs @@ -1,9 +1,11 @@ // 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.Composition; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.MoveDeclarationNearReference; @@ -54,5 +56,8 @@ protected override SyntaxToken GetIdentifierOfVariableDeclarator(VariableDeclara return true; } + + protected override bool CanMoveToBlock(ILocalSymbol localSymbol, SyntaxNode currentBlock, SyntaxNode destinationBlock) + => localSymbol.CanSafelyMoveLocalToBlock(currentBlock, destinationBlock); } } diff --git a/src/Features/Core/Portable/MoveDeclarationNearReference/AbstractMoveDeclarationNearReferenceCodeRefactoringProvider.cs b/src/Features/Core/Portable/MoveDeclarationNearReference/AbstractMoveDeclarationNearReferenceCodeRefactoringProvider.cs index c2779cceaa2c70ffd098c905049f109b63834077..032ef85f33f065faf729f1e3baca41f41d90a305 100644 --- a/src/Features/Core/Portable/MoveDeclarationNearReference/AbstractMoveDeclarationNearReferenceCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/MoveDeclarationNearReference/AbstractMoveDeclarationNearReferenceCodeRefactoringProvider.cs @@ -28,6 +28,7 @@ internal abstract partial class AbstractMoveDeclarationNearReferenceCodeRefactor where TVariableDeclaratorSyntax : SyntaxNode { protected abstract bool IsMeaningfulBlock(SyntaxNode node); + protected abstract bool CanMoveToBlock(ILocalSymbol localSymbol, SyntaxNode currentBlock, SyntaxNode destinationBlock); protected abstract SyntaxNode GetVariableDeclaratorSymbolNode(TVariableDeclaratorSyntax variableDeclarator); protected abstract bool IsValidVariableDeclarator(TVariableDeclaratorSyntax variableDeclarator); protected abstract SyntaxToken GetIdentifierOfVariableDeclarator(TVariableDeclaratorSyntax variableDeclarator); @@ -58,6 +59,11 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return; } + if (!CanMoveToBlock(state.LocalSymbol, state.OutermostBlock, state.InnermostBlock)) + { + return; + } + // Don't offer the refactoring inside the initializer for the variable. var syntaxFacts = document.GetLanguageService(); var initializer = syntaxFacts.GetInitializerOfVariableDeclarator(state.VariableDeclarator); diff --git a/src/Features/VisualBasic/Portable/MoveDeclarationNearReference/VisualBasicMoveDeclarationNearReferenceCodeRefactoringProvider.vb b/src/Features/VisualBasic/Portable/MoveDeclarationNearReference/VisualBasicMoveDeclarationNearReferenceCodeRefactoringProvider.vb index 124c366f96d89b0d3e351c1966b0e7aa85cf7366..1e299829e7f91bb77893dc426e74583c49329b63 100644 --- a/src/Features/VisualBasic/Portable/MoveDeclarationNearReference/VisualBasicMoveDeclarationNearReferenceCodeRefactoringProvider.vb +++ b/src/Features/VisualBasic/Portable/MoveDeclarationNearReference/VisualBasicMoveDeclarationNearReferenceCodeRefactoringProvider.vb @@ -39,5 +39,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MoveDeclarationNearReference Protected Overrides Function TypesAreCompatibleAsync(document As Document, localSymbol As ILocalSymbol, declarationStatement As LocalDeclarationStatementSyntax, right As SyntaxNode, cancellationToken As CancellationToken) As Task(Of Boolean) Return SpecializedTasks.True End Function + + Protected Overrides Function CanMoveToBlock(localSymbol As ILocalSymbol, currentBlock As SyntaxNode, destinationBlock As SyntaxNode) As Boolean + Return True + End Function End Class End Namespace diff --git a/src/Workspaces/CSharp/Portable/Extensions/ILocalSymbolExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/ILocalSymbolExtensions.cs new file mode 100644 index 0000000000000000000000000000000000000000..e9dbc9c27e029eaf08f41d8a69394b2cc84dd58d --- /dev/null +++ b/src/Workspaces/CSharp/Portable/Extensions/ILocalSymbolExtensions.cs @@ -0,0 +1,47 @@ +// 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.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.CodeAnalysis.CSharp.Extensions +{ + internal static class ILocalSymbolExtensions + { + public static bool CanSafelyMoveLocalToBlock(this ILocalSymbol localSymbol, SyntaxNode currentBlock, SyntaxNode destinationBlock) + { + if (currentBlock != destinationBlock) + { + var localFunctionOrMethodDeclaration = currentBlock.AncestorsAndSelf() + .FirstOrDefault(node => node.IsKind(SyntaxKind.LocalFunctionStatement) || node.IsKind(SyntaxKind.MethodDeclaration)); + var localFunctionStatement = destinationBlock.FirstAncestorOrSelf(); + + if (localFunctionOrMethodDeclaration != localFunctionStatement && + HasTypeParameterWithName(localFunctionOrMethodDeclaration, localSymbol.Type.Name) && + HasTypeParameterWithName(localFunctionStatement, localSymbol.Type.Name)) + { + return false; + } + } + + return true; + + bool HasTypeParameterWithName(SyntaxNode node, string name) + { + SeparatedSyntaxList? typeParameters; + switch (node) + { + case MethodDeclarationSyntax methodDeclaration: + typeParameters = methodDeclaration.TypeParameterList?.Parameters; + break; + case LocalFunctionStatementSyntax localFunctionStatement: + typeParameters = localFunctionStatement.TypeParameterList?.Parameters; + break; + default: + return false; + } + + return typeParameters.HasValue && typeParameters.Value.Any(typeParameter => typeParameter.Identifier.ValueText == name); + } + } + } +}