Skip to content

Commit 78075d8

Browse files
Do not offer to use expression body on a lambda if it would cause overload resolution to change. (#76646)
Fixes #76645
2 parents cd134f7 + 34af8cb commit 78075d8

File tree

6 files changed

+89
-75
lines changed

6 files changed

+89
-75
lines changed

src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,30 +71,30 @@ private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context, CodeStyleOp
7171
SemanticModel semanticModel, CodeStyleOption2<ExpressionBodyPreference> option,
7272
LambdaExpressionSyntax declaration, AnalyzerOptions analyzerOptions, CancellationToken cancellationToken)
7373
{
74-
if (UseExpressionBodyForLambdaHelpers.CanOfferUseExpressionBody(option.Value, declaration, declaration.GetLanguageVersion(), cancellationToken))
74+
if (UseExpressionBodyForLambdaHelpers.CanOfferUseExpressionBody(
75+
semanticModel, option.Value, declaration, declaration.GetLanguageVersion(), cancellationToken))
7576
{
76-
var location = GetDiagnosticLocation(declaration);
77-
78-
var additionalLocations = ImmutableArray.Create(declaration.GetLocation());
79-
var properties = ImmutableDictionary<string, string?>.Empty;
8077
return DiagnosticHelper.Create(
8178
s_useExpressionBodyForLambda,
82-
location, option.Notification,
83-
analyzerOptions, additionalLocations, properties);
79+
GetDiagnosticLocation(declaration),
80+
option.Notification,
81+
analyzerOptions,
82+
[declaration.GetLocation()],
83+
ImmutableDictionary<string, string?>.Empty);
8484
}
8585

86-
if (UseExpressionBodyForLambdaHelpers.CanOfferUseBlockBody(semanticModel, option.Value, declaration, cancellationToken))
86+
if (UseExpressionBodyForLambdaHelpers.CanOfferUseBlockBody(
87+
semanticModel, option.Value, declaration, cancellationToken))
8788
{
8889
// They have an expression body. Create a diagnostic to convert it to a block
8990
// if they don't want expression bodies for this member.
90-
var location = GetDiagnosticLocation(declaration);
91-
92-
var properties = ImmutableDictionary<string, string?>.Empty;
93-
var additionalLocations = ImmutableArray.Create(declaration.GetLocation());
9491
return DiagnosticHelper.Create(
9592
s_useBlockBodyForLambda,
96-
location, option.Notification,
97-
analyzerOptions, additionalLocations, properties);
93+
GetDiagnosticLocation(declaration),
94+
option.Notification,
95+
analyzerOptions,
96+
[declaration.GetLocation()],
97+
ImmutableDictionary<string, string?>.Empty);
9898
}
9999

100100
return null;

src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaHelpers.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.CodeAnalysis.CodeStyle;
99
using Microsoft.CodeAnalysis.CSharp.Extensions;
1010
using Microsoft.CodeAnalysis.CSharp.Syntax;
11+
using Microsoft.CodeAnalysis.CSharp.Utilities;
1112
using Microsoft.CodeAnalysis.Diagnostics;
1213

1314
namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda;
@@ -64,7 +65,7 @@ internal static bool CanOfferUseBlockBody(
6465
}
6566

6667
internal static bool CanOfferUseExpressionBody(
67-
ExpressionBodyPreference preference, LambdaExpressionSyntax declaration, LanguageVersion languageVersion, CancellationToken cancellationToken)
68+
SemanticModel semanticModel, ExpressionBodyPreference preference, LambdaExpressionSyntax declaration, LanguageVersion languageVersion, CancellationToken cancellationToken)
6869
{
6970
var userPrefersExpressionBodies = preference != ExpressionBodyPreference.Never;
7071
if (!userPrefersExpressionBodies)
@@ -82,7 +83,7 @@ internal static bool CanOfferUseExpressionBody(
8283

8384
// They don't have an expression body. See if we could convert the block they
8485
// have into one.
85-
return TryConvertToExpressionBody(declaration, languageVersion, preference, cancellationToken, out _);
86+
return TryConvertToExpressionBody(semanticModel, declaration, languageVersion, preference, cancellationToken, out _);
8687
}
8788

8889
internal static ExpressionSyntax? GetBodyAsExpression(LambdaExpressionSyntax declaration)
@@ -105,6 +106,7 @@ internal static ReportDiagnostic GetOptionSeverity(CodeStyleOption2<ExpressionBo
105106
}
106107

107108
internal static bool TryConvertToExpressionBody(
109+
SemanticModel semanticModel,
108110
LambdaExpressionSyntax declaration,
109111
LanguageVersion languageVersion,
110112
ExpressionBodyPreference conversionPreference,
@@ -140,6 +142,22 @@ internal static bool TryConvertToExpressionBody(
140142
if (semicolonToken.TrailingTrivia.Any(t => t.IsDirective))
141143
return false;
142144

145+
// Changing from a block to an expression body can change semantics. Consider:
146+
//
147+
// X(() => { A = 1; });
148+
//
149+
// void X(Action action);
150+
// void X(Func<int> func);
151+
//
152+
// Changing this to `X(() => A = 1);` would change from calling the 'Action' overload to the 'Func<int>'
153+
// overload. Do a final semantic check to make sure the code meaning stays the same.
154+
var speculationAnalyzer = new SpeculationAnalyzer(
155+
declaration,
156+
declaration.WithBody(expression),
157+
semanticModel, cancellationToken);
158+
if (speculationAnalyzer.ReplacementChangesSemantics())
159+
return false;
160+
143161
return true;
144162
}
145163
}

src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeActionHelpers.cs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,23 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.UseExpressionBodyForLambda;
1919

2020
internal static class UseExpressionBodyForLambdaCodeActionHelpers
2121
{
22-
internal static LambdaExpressionSyntax Update(SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration, CancellationToken cancellationToken)
23-
=> UpdateWorker(semanticModel, originalDeclaration, currentDeclaration, cancellationToken).WithAdditionalAnnotations(Formatter.Annotation);
22+
internal static LambdaExpressionSyntax Update(SemanticModel semanticModel, LambdaExpressionSyntax lambdaExpression, CancellationToken cancellationToken)
23+
=> UpdateWorker(semanticModel, lambdaExpression, cancellationToken).WithAdditionalAnnotations(Formatter.Annotation);
2424

2525
private static LambdaExpressionSyntax UpdateWorker(
26-
SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration, CancellationToken cancellationToken)
26+
SemanticModel semanticModel, LambdaExpressionSyntax lambdaExpression, CancellationToken cancellationToken)
2727
{
28-
var expressionBody = UseExpressionBodyForLambdaHelpers.GetBodyAsExpression(currentDeclaration);
28+
var expressionBody = UseExpressionBodyForLambdaHelpers.GetBodyAsExpression(lambdaExpression);
2929
return expressionBody == null
30-
? WithExpressionBody(currentDeclaration, originalDeclaration.GetLanguageVersion(), cancellationToken)
31-
: WithBlockBody(semanticModel, originalDeclaration, currentDeclaration, expressionBody);
30+
? WithExpressionBody(semanticModel, lambdaExpression, cancellationToken)
31+
: WithBlockBody(semanticModel, lambdaExpression, expressionBody);
3232
}
3333

34-
private static LambdaExpressionSyntax WithExpressionBody(LambdaExpressionSyntax declaration, LanguageVersion languageVersion, CancellationToken cancellationToken)
34+
private static LambdaExpressionSyntax WithExpressionBody(
35+
SemanticModel semanticModel, LambdaExpressionSyntax declaration, CancellationToken cancellationToken)
3536
{
3637
if (!UseExpressionBodyForLambdaHelpers.TryConvertToExpressionBody(
37-
declaration, languageVersion, ExpressionBodyPreference.WhenPossible, cancellationToken, out var expressionBody))
38+
semanticModel, declaration, declaration.GetLanguageVersion(), ExpressionBodyPreference.WhenPossible, cancellationToken, out var expressionBody))
3839
{
3940
return declaration;
4041
}
@@ -53,23 +54,23 @@ private static LambdaExpressionSyntax WithExpressionBody(LambdaExpressionSyntax
5354
}
5455

5556
private static LambdaExpressionSyntax WithBlockBody(
56-
SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration, ExpressionSyntax expressionBody)
57+
SemanticModel semanticModel, LambdaExpressionSyntax lambdaExpression, ExpressionSyntax expressionBody)
5758
{
5859
var createReturnStatementForExpression = CreateReturnStatementForExpression(
59-
semanticModel, originalDeclaration);
60+
semanticModel, lambdaExpression);
6061

6162
if (!expressionBody.TryConvertToStatement(
6263
semicolonTokenOpt: null,
6364
createReturnStatementForExpression,
6465
out var statement))
6566
{
66-
return currentDeclaration;
67+
return lambdaExpression;
6768
}
6869

6970
// If the user is converting to a block, it's likely they intend to add multiple
7071
// statements to it. So make a multi-line block so that things are formatted properly
7172
// for them to do so.
72-
return currentDeclaration.WithBody(Block(
73+
return lambdaExpression.WithBody(Block(
7374
OpenBraceToken.WithAppendedTrailingTrivia(ElasticCarriageReturnLineFeed),
7475
[statement],
7576
CloseBraceToken));

src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using System.Diagnostics.CodeAnalysis;
88
using System.Threading;
99
using System.Threading.Tasks;
10-
using Microsoft.CodeAnalysis.CodeActions;
1110
using Microsoft.CodeAnalysis.CodeFixes;
1211
using Microsoft.CodeAnalysis.CSharp.CodeFixes.UseExpressionBodyForLambda;
1312
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -20,51 +19,28 @@ namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda;
2019
[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseExpressionBodyForLambda), Shared]
2120
[method: ImportingConstructor]
2221
[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
23-
internal sealed class UseExpressionBodyForLambdaCodeFixProvider() : SyntaxEditorBasedCodeFixProvider
22+
internal sealed class UseExpressionBodyForLambdaCodeFixProvider() : ForkingSyntaxEditorBasedCodeFixProvider<LambdaExpressionSyntax>
2423
{
2524
public override ImmutableArray<string> FixableDiagnosticIds { get; } = [IDEDiagnosticIds.UseExpressionBodyForLambdaExpressionsDiagnosticId];
2625

27-
public override Task RegisterCodeFixesAsync(CodeFixContext context)
26+
protected override (string title, string equivalenceKey) GetTitleAndEquivalenceKey(CodeFixContext context)
2827
{
29-
var document = context.Document;
3028
var diagnostic = context.Diagnostics[0];
31-
3229
var title = diagnostic.GetMessage();
33-
var codeAction = CodeAction.Create(
34-
title,
35-
c => FixWithSyntaxEditorAsync(document, diagnostic, c),
36-
title);
37-
38-
context.RegisterCodeFix(codeAction, context.Diagnostics);
39-
return Task.CompletedTask;
30+
return (title, title);
4031
}
4132

42-
protected override Task FixAllAsync(Document document, ImmutableArray<Diagnostic> diagnostics, SyntaxEditor editor, CancellationToken cancellationToken)
43-
=> FixAllImplAsync(document, diagnostics, editor, cancellationToken);
44-
45-
private static async Task FixAllImplAsync(Document document, ImmutableArray<Diagnostic> diagnostics, SyntaxEditor editor, CancellationToken cancellationToken)
33+
protected override async Task FixAsync(
34+
Document document,
35+
SyntaxEditor editor,
36+
LambdaExpressionSyntax lambdaExpression,
37+
ImmutableDictionary<string, string?> properties,
38+
CancellationToken cancellationToken)
4639
{
4740
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
48-
foreach (var diagnostic in diagnostics)
49-
{
50-
cancellationToken.ThrowIfCancellationRequested();
51-
AddEdits(editor, semanticModel, diagnostic, cancellationToken);
52-
}
53-
}
54-
55-
private static Task<Document> FixWithSyntaxEditorAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
56-
=> FixAllWithEditorAsync(
57-
document, editor => FixAllImplAsync(document, [diagnostic], editor, cancellationToken), cancellationToken);
58-
59-
private static void AddEdits(
60-
SyntaxEditor editor, SemanticModel semanticModel,
61-
Diagnostic diagnostic, CancellationToken cancellationToken)
62-
{
63-
var declarationLocation = diagnostic.AdditionalLocations[0];
64-
var originalDeclaration = (LambdaExpressionSyntax)declarationLocation.FindNode(getInnermostNodeForTie: true, cancellationToken);
65-
6641
editor.ReplaceNode(
67-
originalDeclaration,
68-
(current, _) => UseExpressionBodyForLambdaCodeActionHelpers.Update(semanticModel, originalDeclaration, (LambdaExpressionSyntax)current, cancellationToken));
42+
lambdaExpression,
43+
UseExpressionBodyForLambdaCodeActionHelpers.Update(
44+
semanticModel, lambdaExpression, cancellationToken));
6945
}
7046
}

src/Analyzers/CSharp/Tests/UseExpressionBodyForLambda/UseExpressionBodyForLambdasAnalyzerTests.cs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,16 @@
1010
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics;
1111
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
1212
using Microsoft.CodeAnalysis.Test.Utilities;
13+
using Roslyn.Test.Utilities;
1314
using Xunit;
1415
using Xunit.Abstractions;
1516

1617
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseExpressionBody;
1718

1819
[Trait(Traits.Feature, Traits.Features.CodeActionsUseExpressionBody)]
19-
public class UseExpressionBodyForLambdasAnalyzerTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor
20+
public sealed class UseExpressionBodyForLambdasAnalyzerTests(ITestOutputHelper logger)
21+
: AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor(logger)
2022
{
21-
public UseExpressionBodyForLambdasAnalyzerTests(ITestOutputHelper logger)
22-
: base(logger)
23-
{
24-
}
25-
2623
internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace)
2724
=> (new UseExpressionBodyForLambdaDiagnosticAnalyzer(), new UseExpressionBodyForLambdaCodeFixProvider());
2825

@@ -1469,4 +1466,27 @@ void Goo()
14691466
}
14701467
""", options: UseBlockBody);
14711468
}
1469+
1470+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/76645")]
1471+
public async Task TestMethodOverloadResolutionChange()
1472+
{
1473+
await TestMissingInRegularAndScriptAsync(
1474+
"""
1475+
using System;
1476+
1477+
class C
1478+
{
1479+
void Goo(int result, Func<int> function)
1480+
{
1481+
Execute(() [|=>|]
1482+
{
1483+
result = function();
1484+
});
1485+
}
1486+
1487+
void Execute(Action action) { }
1488+
void Execute(Func<int> function) { }
1489+
}
1490+
""", new(options: UseExpressionBody));
1491+
}
14721492
}

src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeRefactoringProvider.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,8 @@ private static async Task<ImmutableArray<CodeAction>> ComputeRefactoringsAsync(
169169
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
170170

171171
using var result = TemporaryArray<CodeAction>.Empty;
172-
if (UseExpressionBodyForLambdaHelpers.CanOfferUseExpressionBody(option, lambdaNode, root.GetLanguageVersion(), cancellationToken))
172+
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
173+
if (UseExpressionBodyForLambdaHelpers.CanOfferUseExpressionBody(semanticModel, option, lambdaNode, root.GetLanguageVersion(), cancellationToken))
173174
{
174175
var title = UseExpressionBodyForLambdaHelpers.UseExpressionBodyTitle.ToString();
175176
result.Add(CodeAction.Create(
@@ -178,7 +179,6 @@ private static async Task<ImmutableArray<CodeAction>> ComputeRefactoringsAsync(
178179
title));
179180
}
180181

181-
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
182182
if (UseExpressionBodyForLambdaHelpers.CanOfferUseBlockBody(semanticModel, option, lambdaNode, cancellationToken))
183183
{
184184
var title = UseExpressionBodyForLambdaHelpers.UseBlockBodyTitle.ToString();
@@ -196,9 +196,8 @@ private static async Task<Document> UpdateDocumentAsync(
196196
{
197197
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
198198

199-
// We're only replacing a single declaration in the refactoring. So pass 'declaration'
200-
// as both the 'original' and 'current' declaration.
201-
var updatedDeclaration = UseExpressionBodyForLambdaCodeActionHelpers.Update(semanticModel, declaration, declaration, cancellationToken);
199+
var updatedDeclaration = UseExpressionBodyForLambdaCodeActionHelpers.Update(
200+
semanticModel, declaration, cancellationToken);
202201

203202
var newRoot = root.ReplaceNode(declaration, updatedDeclaration);
204203
return document.WithSyntaxRoot(newRoot);

0 commit comments

Comments
 (0)