Skip to content

Commit 2a67954

Browse files
Fix case where we were erroneously offering to convert a dictionary to use a collection expression. (#75897)
2 parents 30cd2b3 + 0307d39 commit 2a67954

File tree

4 files changed

+77
-8
lines changed

4 files changed

+77
-8
lines changed

src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.Collections.Generic;
6+
using System.Collections.Immutable;
7+
using System.Linq;
58
using System.Threading;
69
using Microsoft.CodeAnalysis.CSharp.Extensions;
710
using Microsoft.CodeAnalysis.CSharp.LanguageService;
@@ -10,10 +13,13 @@
1013
using Microsoft.CodeAnalysis.CSharp.UseCollectionExpression;
1114
using Microsoft.CodeAnalysis.Diagnostics;
1215
using Microsoft.CodeAnalysis.LanguageService;
16+
using Microsoft.CodeAnalysis.UseCollectionExpression;
1317
using Microsoft.CodeAnalysis.UseCollectionInitializer;
1418

1519
namespace Microsoft.CodeAnalysis.CSharp.UseCollectionInitializer;
1620

21+
using static SyntaxFactory;
22+
1723
[DiagnosticAnalyzer(LanguageNames.CSharp)]
1824
internal sealed class CSharpUseCollectionInitializerDiagnosticAnalyzer :
1925
AbstractUseCollectionInitializerDiagnosticAnalyzer<
@@ -40,13 +46,39 @@ protected override bool AreCollectionInitializersSupported(Compilation compilati
4046
protected override bool AreCollectionExpressionsSupported(Compilation compilation)
4147
=> compilation.LanguageVersion().SupportsCollectionExpressions();
4248

43-
protected override bool CanUseCollectionExpression(SemanticModel semanticModel, BaseObjectCreationExpressionSyntax objectCreationExpression, INamedTypeSymbol? expressionType, bool allowSemanticsChange, CancellationToken cancellationToken, out bool changesSemantics)
49+
protected override bool CanUseCollectionExpression(
50+
SemanticModel semanticModel,
51+
BaseObjectCreationExpressionSyntax objectCreationExpression,
52+
INamedTypeSymbol? expressionType,
53+
ImmutableArray<CollectionMatch<SyntaxNode>> preMatches,
54+
bool allowSemanticsChange,
55+
CancellationToken cancellationToken,
56+
out bool changesSemantics)
4457
{
4558
// Synthesize the final collection expression we would replace this object-creation with. That will allow us to
4659
// determine if we end up calling the right overload in cases of overloaded methods.
47-
var replacement = UseCollectionExpressionHelpers.CreateReplacementCollectionExpressionForAnalysis(objectCreationExpression.Initializer);
60+
var replacement = CollectionExpression(SeparatedList(
61+
GetMatchElements(preMatches).Concat(GetInitializerElements(objectCreationExpression.Initializer))));
4862

4963
return UseCollectionExpressionHelpers.CanReplaceWithCollectionExpression(
5064
semanticModel, objectCreationExpression, replacement, expressionType, isSingletonInstance: false, allowSemanticsChange, skipVerificationForReplacedNode: true, cancellationToken, out changesSemantics);
65+
66+
static IEnumerable<CollectionElementSyntax> GetMatchElements(ImmutableArray<CollectionMatch<SyntaxNode>> preMatches)
67+
{
68+
foreach (var match in preMatches)
69+
{
70+
if (match.Node is ExpressionSyntax expression)
71+
yield return match.UseSpread ? SpreadElement(expression) : ExpressionElement(expression);
72+
}
73+
}
74+
75+
static IEnumerable<CollectionElementSyntax> GetInitializerElements(InitializerExpressionSyntax? initializer)
76+
{
77+
if (initializer != null)
78+
{
79+
foreach (var expression in initializer.Expressions)
80+
yield return ExpressionElement(expression);
81+
}
82+
}
5183
}
5284
}

src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests_CollectionExpression.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5866,4 +5866,26 @@ public class Class2 { }
58665866
ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
58675867
}.RunAsync();
58685868
}
5869+
5870+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75894")]
5871+
public async Task TestNotOnDictionaryConstructor()
5872+
{
5873+
await new VerifyCS.Test
5874+
{
5875+
TestCode = """
5876+
using System.Collections.Generic;
5877+
5878+
class C
5879+
{
5880+
void Main()
5881+
{
5882+
Dictionary<string, string> a = null;
5883+
Dictionary<string, string> d = new(a);
5884+
}
5885+
}
5886+
""",
5887+
LanguageVersion = LanguageVersion.CSharp13,
5888+
ReferenceAssemblies = ReferenceAssemblies.Net.Net90,
5889+
}.RunAsync();
5890+
}
58695891
}

src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,13 @@ protected AbstractUseCollectionInitializerDiagnosticAnalyzer()
8080
protected abstract bool AreCollectionInitializersSupported(Compilation compilation);
8181
protected abstract bool AreCollectionExpressionsSupported(Compilation compilation);
8282
protected abstract bool CanUseCollectionExpression(
83-
SemanticModel semanticModel, TObjectCreationExpressionSyntax objectCreationExpression, INamedTypeSymbol? expressionType, bool allowSemanticsChange, CancellationToken cancellationToken, out bool changesSemantics);
83+
SemanticModel semanticModel,
84+
TObjectCreationExpressionSyntax objectCreationExpression,
85+
INamedTypeSymbol? expressionType,
86+
ImmutableArray<CollectionMatch<SyntaxNode>> preMatches,
87+
bool allowSemanticsChange,
88+
CancellationToken cancellationToken,
89+
out bool changesSemantics);
8490

8591
protected abstract TAnalyzer GetAnalyzer();
8692

@@ -218,18 +224,18 @@ private void AnalyzeNode(
218224
if (!this.AreCollectionExpressionsSupported(context.Compilation))
219225
return null;
220226

221-
var (_, matches) = analyzer.Analyze(semanticModel, syntaxFacts, objectCreationExpression, analyzeForCollectionExpression: true, cancellationToken);
227+
var (preMatches, postMatches) = analyzer.Analyze(semanticModel, syntaxFacts, objectCreationExpression, analyzeForCollectionExpression: true, cancellationToken);
222228

223229
// If analysis failed, we can't change this, no matter what.
224-
if (matches.IsDefault)
230+
if (preMatches.IsDefault || postMatches.IsDefault)
225231
return null;
226232

227233
// Check if it would actually be legal to use a collection expression here though.
228234
var allowSemanticsChange = preferExpressionOption.Value == CollectionExpressionPreference.WhenTypesLooselyMatch;
229-
if (!CanUseCollectionExpression(semanticModel, objectCreationExpression, expressionType, allowSemanticsChange, cancellationToken, out var changesSemantics))
235+
if (!CanUseCollectionExpression(semanticModel, objectCreationExpression, expressionType, preMatches, allowSemanticsChange, cancellationToken, out var changesSemantics))
230236
return null;
231237

232-
return (matches, shouldUseCollectionExpression: true, changesSemantics);
238+
return (preMatches.Concat(postMatches), shouldUseCollectionExpression: true, changesSemantics);
233239
}
234240
}
235241

src/Analyzers/VisualBasic/Analyzers/UseCollectionInitializer/VisualBasicUseCollectionInitializerDiagnosticAnalyzer.vb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
' The .NET Foundation licenses this file to you under the MIT license.
33
' See the LICENSE file in the project root for more information.
44

5+
Imports System.Collections.Immutable
56
Imports System.Threading
67
Imports Microsoft.CodeAnalysis.Diagnostics
78
Imports Microsoft.CodeAnalysis.LanguageService
9+
Imports Microsoft.CodeAnalysis.UseCollectionExpression
810
Imports Microsoft.CodeAnalysis.UseCollectionInitializer
911
Imports Microsoft.CodeAnalysis.VisualBasic.LanguageService
1012
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
@@ -38,7 +40,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseCollectionInitializer
3840
Return False
3941
End Function
4042

41-
Protected Overrides Function CanUseCollectionExpression(semanticModel As SemanticModel, objectCreationExpression As ObjectCreationExpressionSyntax, expressionType As INamedTypeSymbol, allowSemanticsChange As Boolean, cancellationToken As CancellationToken, ByRef changesSemantics As Boolean) As Boolean
43+
Protected Overrides Function CanUseCollectionExpression(
44+
semanticModel As SemanticModel,
45+
objectCreationExpression As ObjectCreationExpressionSyntax,
46+
expressionType As INamedTypeSymbol,
47+
matches As ImmutableArray(Of CollectionMatch(Of SyntaxNode)),
48+
allowSemanticsChange As Boolean,
49+
cancellationToken As CancellationToken,
50+
ByRef changesSemantics As Boolean) As Boolean
4251
Throw ExceptionUtilities.Unreachable()
4352
End Function
4453
End Class

0 commit comments

Comments
 (0)