diff --git a/src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs index 69b943c5b43f5..c0bd02c1d2d3c 100644 --- a/src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs @@ -2,6 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.LanguageService; @@ -10,10 +13,13 @@ using Microsoft.CodeAnalysis.CSharp.UseCollectionExpression; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageService; +using Microsoft.CodeAnalysis.UseCollectionExpression; using Microsoft.CodeAnalysis.UseCollectionInitializer; namespace Microsoft.CodeAnalysis.CSharp.UseCollectionInitializer; +using static SyntaxFactory; + [DiagnosticAnalyzer(LanguageNames.CSharp)] internal sealed class CSharpUseCollectionInitializerDiagnosticAnalyzer : AbstractUseCollectionInitializerDiagnosticAnalyzer< @@ -40,13 +46,39 @@ protected override bool AreCollectionInitializersSupported(Compilation compilati protected override bool AreCollectionExpressionsSupported(Compilation compilation) => compilation.LanguageVersion().SupportsCollectionExpressions(); - protected override bool CanUseCollectionExpression(SemanticModel semanticModel, BaseObjectCreationExpressionSyntax objectCreationExpression, INamedTypeSymbol? expressionType, bool allowSemanticsChange, CancellationToken cancellationToken, out bool changesSemantics) + protected override bool CanUseCollectionExpression( + SemanticModel semanticModel, + BaseObjectCreationExpressionSyntax objectCreationExpression, + INamedTypeSymbol? expressionType, + ImmutableArray> preMatches, + bool allowSemanticsChange, + CancellationToken cancellationToken, + out bool changesSemantics) { // Synthesize the final collection expression we would replace this object-creation with. That will allow us to // determine if we end up calling the right overload in cases of overloaded methods. - var replacement = UseCollectionExpressionHelpers.CreateReplacementCollectionExpressionForAnalysis(objectCreationExpression.Initializer); + var replacement = CollectionExpression(SeparatedList( + GetMatchElements(preMatches).Concat(GetInitializerElements(objectCreationExpression.Initializer)))); return UseCollectionExpressionHelpers.CanReplaceWithCollectionExpression( semanticModel, objectCreationExpression, replacement, expressionType, isSingletonInstance: false, allowSemanticsChange, skipVerificationForReplacedNode: true, cancellationToken, out changesSemantics); + + static IEnumerable GetMatchElements(ImmutableArray> preMatches) + { + foreach (var match in preMatches) + { + if (match.Node is ExpressionSyntax expression) + yield return match.UseSpread ? SpreadElement(expression) : ExpressionElement(expression); + } + } + + static IEnumerable GetInitializerElements(InitializerExpressionSyntax? initializer) + { + if (initializer != null) + { + foreach (var expression in initializer.Expressions) + yield return ExpressionElement(expression); + } + } } } diff --git a/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests_CollectionExpression.cs b/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests_CollectionExpression.cs index 7a19fa362e961..3d5ad523fa0a8 100644 --- a/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests_CollectionExpression.cs +++ b/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests_CollectionExpression.cs @@ -5866,4 +5866,26 @@ public class Class2 { } ReferenceAssemblies = ReferenceAssemblies.Net.Net80, }.RunAsync(); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75894")] + public async Task TestNotOnDictionaryConstructor() + { + await new VerifyCS.Test + { + TestCode = """ + using System.Collections.Generic; + + class C + { + void Main() + { + Dictionary a = null; + Dictionary d = new(a); + } + } + """, + LanguageVersion = LanguageVersion.CSharp13, + ReferenceAssemblies = ReferenceAssemblies.Net.Net90, + }.RunAsync(); + } } diff --git a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs index 6b84494d3acfc..671b11f76ec94 100644 --- a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs @@ -80,7 +80,13 @@ protected AbstractUseCollectionInitializerDiagnosticAnalyzer() protected abstract bool AreCollectionInitializersSupported(Compilation compilation); protected abstract bool AreCollectionExpressionsSupported(Compilation compilation); protected abstract bool CanUseCollectionExpression( - SemanticModel semanticModel, TObjectCreationExpressionSyntax objectCreationExpression, INamedTypeSymbol? expressionType, bool allowSemanticsChange, CancellationToken cancellationToken, out bool changesSemantics); + SemanticModel semanticModel, + TObjectCreationExpressionSyntax objectCreationExpression, + INamedTypeSymbol? expressionType, + ImmutableArray> preMatches, + bool allowSemanticsChange, + CancellationToken cancellationToken, + out bool changesSemantics); protected abstract TAnalyzer GetAnalyzer(); @@ -218,18 +224,18 @@ private void AnalyzeNode( if (!this.AreCollectionExpressionsSupported(context.Compilation)) return null; - var (_, matches) = analyzer.Analyze(semanticModel, syntaxFacts, objectCreationExpression, analyzeForCollectionExpression: true, cancellationToken); + var (preMatches, postMatches) = analyzer.Analyze(semanticModel, syntaxFacts, objectCreationExpression, analyzeForCollectionExpression: true, cancellationToken); // If analysis failed, we can't change this, no matter what. - if (matches.IsDefault) + if (preMatches.IsDefault || postMatches.IsDefault) return null; // Check if it would actually be legal to use a collection expression here though. var allowSemanticsChange = preferExpressionOption.Value == CollectionExpressionPreference.WhenTypesLooselyMatch; - if (!CanUseCollectionExpression(semanticModel, objectCreationExpression, expressionType, allowSemanticsChange, cancellationToken, out var changesSemantics)) + if (!CanUseCollectionExpression(semanticModel, objectCreationExpression, expressionType, preMatches, allowSemanticsChange, cancellationToken, out var changesSemantics)) return null; - return (matches, shouldUseCollectionExpression: true, changesSemantics); + return (preMatches.Concat(postMatches), shouldUseCollectionExpression: true, changesSemantics); } } diff --git a/src/Analyzers/VisualBasic/Analyzers/UseCollectionInitializer/VisualBasicUseCollectionInitializerDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/UseCollectionInitializer/VisualBasicUseCollectionInitializerDiagnosticAnalyzer.vb index b15c4bf2bd948..f8227d27903ee 100644 --- a/src/Analyzers/VisualBasic/Analyzers/UseCollectionInitializer/VisualBasicUseCollectionInitializerDiagnosticAnalyzer.vb +++ b/src/Analyzers/VisualBasic/Analyzers/UseCollectionInitializer/VisualBasicUseCollectionInitializerDiagnosticAnalyzer.vb @@ -2,9 +2,11 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. +Imports System.Collections.Immutable Imports System.Threading Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.LanguageService +Imports Microsoft.CodeAnalysis.UseCollectionExpression Imports Microsoft.CodeAnalysis.UseCollectionInitializer Imports Microsoft.CodeAnalysis.VisualBasic.LanguageService Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -38,7 +40,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseCollectionInitializer Return False End Function - 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 + Protected Overrides Function CanUseCollectionExpression( + semanticModel As SemanticModel, + objectCreationExpression As ObjectCreationExpressionSyntax, + expressionType As INamedTypeSymbol, + matches As ImmutableArray(Of CollectionMatch(Of SyntaxNode)), + allowSemanticsChange As Boolean, + cancellationToken As CancellationToken, + ByRef changesSemantics As Boolean) As Boolean Throw ExceptionUtilities.Unreachable() End Function End Class