From 5027b778e20103486cda6060f9682a55915ad1a4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 5 Jun 2023 13:15:29 -0700 Subject: [PATCH] Fix 'use local function' with implicitly typed lambdas --- ...SharpUseLocalFunctionDiagnosticAnalyzer.cs | 48 ++++++++++--------- .../UseLocalFunction/UseLocalFunctionTests.cs | 41 ++++++++++++++++ 2 files changed, 66 insertions(+), 23 deletions(-) diff --git a/src/Analyzers/CSharp/Analyzers/UseLocalFunction/CSharpUseLocalFunctionDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseLocalFunction/CSharpUseLocalFunctionDiagnosticAnalyzer.cs index 501127330c7f5..f9d205e23feae 100644 --- a/src/Analyzers/CSharp/Analyzers/UseLocalFunction/CSharpUseLocalFunctionDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseLocalFunction/CSharpUseLocalFunctionDiagnosticAnalyzer.cs @@ -77,45 +77,33 @@ protected override void InitializeWorker(AnalysisContext context) private void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext, INamedTypeSymbol? expressionType) { var styleOption = syntaxContext.GetCSharpAnalyzerOptions().PreferLocalOverAnonymousFunction; + // Bail immediately if the user has disabled this feature. if (!styleOption.Value) - { - // Bail immediately if the user has disabled this feature. return; - } var severity = styleOption.Notification.Severity; var anonymousFunction = (AnonymousFunctionExpressionSyntax)syntaxContext.Node; var semanticModel = syntaxContext.SemanticModel; if (!CheckForPattern(anonymousFunction, out var localDeclaration)) - { return; - } if (localDeclaration.Declaration.Variables.Count != 1) - { return; - } if (localDeclaration.Parent is not BlockSyntax block) - { return; - } // If there are compiler error on the declaration we can't reliably // tell that the refactoring will be accurate, so don't provide any // code diagnostics if (localDeclaration.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error)) - { return; - } var cancellationToken = syntaxContext.CancellationToken; var local = semanticModel.GetDeclaredSymbol(localDeclaration.Declaration.Variables[0], cancellationToken); if (local == null) - { return; - } var delegateType = semanticModel.GetTypeInfo(anonymousFunction, cancellationToken).ConvertedType as INamedTypeSymbol; if (!delegateType.IsDelegateType() || @@ -128,6 +116,13 @@ private void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext, INamedTyp if (!CanReplaceAnonymousWithLocalFunction(semanticModel, expressionType, local, block, anonymousFunction, out var referenceLocations, cancellationToken)) return; + if (localDeclaration.Declaration.Type.IsVar) + { + var options = semanticModel.SyntaxTree.Options; + if (options.LanguageVersion() < LanguageVersion.CSharp10) + return; + } + // Looks good! var additionalLocations = ImmutableArray.Create( localDeclaration.GetLocation(), @@ -190,15 +185,22 @@ private static bool CheckForSimpleLocalDeclarationPattern( [NotNullWhen(true)] out LocalDeclarationStatementSyntax? localDeclaration) { // Type t = - if (anonymousFunction.IsParentKind(SyntaxKind.EqualsValueClause) && - anonymousFunction.Parent.IsParentKind(SyntaxKind.VariableDeclarator) && - anonymousFunction.Parent.Parent.IsParentKind(SyntaxKind.VariableDeclaration) && - anonymousFunction.Parent.Parent.Parent.IsParentKind(SyntaxKind.LocalDeclarationStatement, out localDeclaration)) - { - if (!localDeclaration.Declaration.Type.IsVar) + if (anonymousFunction is { - return true; - } + Parent: EqualsValueClauseSyntax + { + Parent: VariableDeclaratorSyntax + { + Parent: VariableDeclarationSyntax + { + Parent: LocalDeclarationStatementSyntax declaration, + } + } + } + }) + { + localDeclaration = declaration; + return true; } localDeclaration = null; @@ -211,7 +213,7 @@ private static bool CanReplaceAnonymousWithLocalFunction( { // Check all the references to the anonymous function and disallow the conversion if // they're used in certain ways. - var references = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var references); referenceLocations = ImmutableArray.Empty; var anonymousFunctionStart = anonymousFunction.SpanStart; foreach (var descendentNode in block.DescendantNodes()) @@ -285,7 +287,7 @@ private static bool CanReplaceAnonymousWithLocalFunction( } } - referenceLocations = references.ToImmutableAndFree(); + referenceLocations = references.ToImmutableAndClear(); return true; } diff --git a/src/Analyzers/CSharp/Tests/UseLocalFunction/UseLocalFunctionTests.cs b/src/Analyzers/CSharp/Tests/UseLocalFunction/UseLocalFunctionTests.cs index 436ea7ad5cf5e..173f28d7f06f6 100644 --- a/src/Analyzers/CSharp/Tests/UseLocalFunction/UseLocalFunctionTests.cs +++ b/src/Analyzers/CSharp/Tests/UseLocalFunction/UseLocalFunctionTests.cs @@ -4139,5 +4139,46 @@ static int last(params int[] xs) } """); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68439")] + public async Task TestImplicitlyTypedLambdaCSharp10() + { + await TestInRegularAndScriptAsync( + """ + class Program + { + static void Main() + { + var [||]test = (int n) => n * 2; + } + } + """, + """ + class Program + { + static void Main() + { + static int test(int n) => n * 2; + } + } + """, + parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp10)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/68439")] + public async Task TestImplicitlyTypedLambdaCSharp9() + { + await TestMissingInRegularAndScriptAsync( + """ + class Program + { + static void Main() + { + var [||]test = (int n) => n * 2; + } + } + """, + new TestParameters(parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp9))); + } } }