提交 08370ebe 编写于 作者: C Cyrus Najmabadi

When converting a lambda to a local function, make static if possible (and desired)

上级 2111c79e
......@@ -18,6 +18,7 @@ internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProvider
=> (new CSharpUseLocalFunctionDiagnosticAnalyzer(), new CSharpUseLocalFunctionCodeFixProvider());
private static ParseOptions CSharp72ParseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_2);
private static ParseOptions CSharp8ParseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp8);
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseLocalFunction)]
public async Task TestMissingBeforeCSharp7()
......@@ -3551,5 +3552,113 @@ void Method<T>(Func<T, string> o)
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseLocalFunction)]
public async Task TestMakeStaticIfNoCaptures()
{
await TestInRegularAndScriptAsync(
@"using System;
class C
{
void M()
{
Func<int, int> [||]fibonacci = v =>
{
if (v <= 1)
{
return 1;
}
return fibonacci(v - 1, v - 2);
};
}
}",
@"using System;
class C
{
void M()
{
static int fibonacci(int v)
{
if (v <= 1)
{
return 1;
}
return fibonacci(v - 1, v - 2);
}
}
}", parseOptions: CSharp8ParseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseLocalFunction)]
public async Task TestDoNotMakeStaticIfCaptures()
{
await TestInRegularAndScriptAsync(
@"using System;
class C
{
void M()
{
Func<int, int> [||]fibonacci = v =>
{
M();
if (v <= 1)
{
return 1;
}
return fibonacci(v - 1, v - 2);
};
}
}",
@"using System;
class C
{
void M()
{
int fibonacci(int v)
{
M();
if (v <= 1)
{
return 1;
}
return fibonacci(v - 1, v - 2);
}
}
}", parseOptions: CSharp8ParseOptions);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseLocalFunction)]
public async Task TestWithAsyncLambdaExpression_MakeStatic()
{
await TestInRegularAndScriptAsync(
@"using System;
using System.Threading.Tasks;
class C
{
void M()
{
Func<Task> [||]f = async () => await Task.Yield();
}
}",
@"using System;
using System.Threading.Tasks;
class C
{
void M()
{
static async Task f() => await Task.Yield();
}
}", parseOptions: CSharp8ParseOptions);
}
}
}
......@@ -10,6 +10,7 @@
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
......@@ -23,8 +24,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseLocalFunction
[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
internal class CSharpUseLocalFunctionCodeFixProvider : SyntaxEditorBasedCodeFixProvider
{
private static TypeSyntax s_voidType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword));
private static TypeSyntax s_objectType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword));
private static readonly TypeSyntax s_objectType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword));
public override ImmutableArray<string> FixableDiagnosticIds
=> ImmutableArray.Create(IDEDiagnosticIds.UseLocalFunctionDiagnosticId);
......@@ -75,12 +75,18 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
var root = editor.OriginalRoot;
var currentRoot = root.TrackNodes(nodesToTrack);
var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var languageVersion = ((CSharpParseOptions)semanticModel.SyntaxTree.Options).LanguageVersion;
var makeStaticIfPossible = languageVersion >= LanguageVersion.CSharp8 &&
options.GetOption(CSharpCodeStyleOptions.PreferStaticLocalFunction).Value;
// Process declarations in reverse order so that we see the effects of nested
// declarations befor processing the outer decls.
foreach (var (localDeclaration, anonymousFunction, references) in nodesFromDiagnostics.OrderByDescending(nodes => nodes.function.SpanStart))
{
var delegateType = (INamedTypeSymbol)semanticModel.GetTypeInfo(anonymousFunction, cancellationToken).ConvertedType;
var parameterList = GenerateParameterList(anonymousFunction, delegateType.DelegateInvokeMethod);
var makeStatic = MakeStatic(semanticModel, makeStaticIfPossible, localDeclaration, cancellationToken);
var currentLocalDeclaration = currentRoot.GetCurrentNode(localDeclaration);
var currentAnonymousFunction = currentRoot.GetCurrentNode(anonymousFunction);
......@@ -88,7 +94,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
currentRoot = ReplaceAnonymousWithLocalFunction(
document.Project.Solution.Workspace, currentRoot,
currentLocalDeclaration, currentAnonymousFunction,
delegateType.DelegateInvokeMethod, parameterList);
delegateType.DelegateInvokeMethod, parameterList, makeStatic);
// these invocations might actually be inside the local function! so we have to do this separately
currentRoot = ReplaceReferences(
......@@ -100,12 +106,41 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
editor.ReplaceNode(root, currentRoot);
}
private static bool MakeStatic(
SemanticModel semanticModel,
bool makeStaticIfPossible,
LocalDeclarationStatementSyntax localDeclaration,
CancellationToken cancellationToken)
{
// Determines if we can make the local function 'static'. We can make it static
// if the original lambda did not cpature any variables (other than the local
// variable itself). it's ok for the lambda to capture itself as a static-local
// function can reference itself without any problems.
if (makeStaticIfPossible)
{
var localSymbol = semanticModel.GetDeclaredSymbol(
localDeclaration.Declaration.Variables[0], cancellationToken);
var dataFlow = semanticModel.AnalyzeDataFlow(localDeclaration);
if (dataFlow.Succeeded)
{
var capturedVariables = dataFlow.Captured.Remove(localSymbol);
if (capturedVariables.IsEmpty)
{
return true;
}
}
}
return false;
}
private static SyntaxNode ReplaceAnonymousWithLocalFunction(
Workspace workspace, SyntaxNode currentRoot,
LocalDeclarationStatementSyntax localDeclaration, AnonymousFunctionExpressionSyntax anonymousFunction,
IMethodSymbol delegateMethod, ParameterListSyntax parameterList)
IMethodSymbol delegateMethod, ParameterListSyntax parameterList, bool makeStatic)
{
var newLocalFunctionStatement = CreateLocalFunctionStatement(localDeclaration, anonymousFunction, delegateMethod, parameterList)
var newLocalFunctionStatement = CreateLocalFunctionStatement(localDeclaration, anonymousFunction, delegateMethod, parameterList, makeStatic)
.WithTriviaFrom(localDeclaration)
.WithAdditionalAnnotations(Formatter.Annotation);
......@@ -149,11 +184,19 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
LocalDeclarationStatementSyntax localDeclaration,
AnonymousFunctionExpressionSyntax anonymousFunction,
IMethodSymbol delegateMethod,
ParameterListSyntax parameterList)
ParameterListSyntax parameterList,
bool makeStatic)
{
var modifiers = anonymousFunction.AsyncKeyword.IsKind(SyntaxKind.AsyncKeyword)
? new SyntaxTokenList(anonymousFunction.AsyncKeyword)
: default;
var modifiers = new SyntaxTokenList();
if (makeStatic)
{
modifiers = modifiers.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword));
}
if (anonymousFunction.AsyncKeyword.IsKind(SyntaxKind.AsyncKeyword))
{
modifiers = modifiers.Add(anonymousFunction.AsyncKeyword);
}
var returnType = delegateMethod.GenerateReturnTypeSyntax();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册