Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 3, 2025

Fix for IDE0028 with CollectionBuilder Methods ✅

This PR fixes issue #70099 where IDE0028 (Collection initialization can be simplified) incorrectly suggests using collection expressions inside CollectionBuilder methods, which would cause infinite recursion.

Problem

When implementing a CollectionBuilder method, the analyzer would suggest converting:

MyCustomCollection<T> collection = new();
foreach (T item in items) { collection.Add(item); }

To:

MyCustomCollection<T> collection = [.. items];  // ❌ Infinite recursion!

This causes infinite recursion because the collection expression [.. items] calls the same CollectionBuilder method.

Solution

Added detection logic to suppress the diagnostic when:

  1. We're analyzing code inside a method
  2. The type being created has a CollectionBuilder attribute
  3. That attribute points to the current method

Implementation

  • Base Analyzer: Added virtual ShouldSuppressDiagnostic method
  • C# Analyzer: Implemented CollectionBuilder detection using:
    • Existing IsCollectionBuilderAttribute() helper for type checking
    • OriginalDefinition comparison for generic types
    • List pattern match for constructor argument validation
  • Tests: Added comprehensive tests for both suppression and normal behavior

Files Changed

  • src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs
  • src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs
  • src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests_CollectionExpression.cs

Commits

  1. ✅ Add suppression for IDE0028 in CollectionBuilder methods
  2. ✅ Fix generic type handling in CollectionBuilder check
  3. ✅ Address code review feedback
  4. ✅ Add clarifying comment for constructor argument check
  5. ✅ Refactor to use list pattern match for constructor arguments
  6. ✅ Simplify position logic and update WorkItem references

All changes follow Roslyn coding conventions and include comprehensive tests.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

- Added virtual method ShouldSuppressDiagnostic to base analyzer
- Implemented CollectionBuilder detection in C# analyzer
- Detects when we're inside a CollectionBuilder method to avoid infinite recursion
- Added tests to verify the fix works correctly

Co-authored-by: CyrusNajmabadi <[email protected]>
Copilot AI and others added 3 commits November 3, 2025 22:12
Use OriginalDefinition to check attributes on generic types and compare builder types correctly

Co-authored-by: CyrusNajmabadi <[email protected]>
- Use IsCollectionBuilderAttribute() helper method instead of manual namespace checking
- Use NewKeyword.SpanStart for more precise position when getting enclosing symbol
- Add null check for AttributeClass before calling extension method

Co-authored-by: CyrusNajmabadi <[email protected]>
Explain that CollectionBuilderAttribute has exactly 2 constructor parameters

Co-authored-by: CyrusNajmabadi <[email protected]>
Copilot AI changed the title [WIP] Fix IDE0028 warning for collection initialization Fix IDE0028: Suppress diagnostic in CollectionBuilder methods to prevent infinite recursion Nov 3, 2025
Copilot AI requested a review from CyrusNajmabadi November 3, 2025 22:19
Copilot finished work on behalf of CyrusNajmabadi November 3, 2025 22:19
Comment on lines 148 to 157
var builderTypeArg = collectionBuilderAttribute.ConstructorArguments[0];
var methodNameArg = collectionBuilderAttribute.ConstructorArguments[1];

if (builderTypeArg.Kind != TypedConstantKind.Type ||
builderTypeArg.Value is not INamedTypeSymbol builderType ||
methodNameArg.Kind != TypedConstantKind.Primitive ||
methodNameArg.Value is not string methodName)
{
return false;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot replace this section with a pattern match.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored to use a list pattern match in commit f63c546. The code now validates both constructor arguments and extracts the values in a single expression.

ObjectCreationExpressionSyntax objCreation => objCreation.NewKeyword.SpanStart,
ImplicitObjectCreationExpressionSyntax implicitObjCreation => implicitObjCreation.NewKeyword.SpanStart,
_ => objectCreationExpression.SpanStart
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot this is all unnecessary. you can just use objectCreationExpression.SpanStart for all cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplified in commit 93efc8b. Now using objectCreationExpression.SpanStart directly.

ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
}.RunAsync();

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/74268")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot change these all to #70099

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated WorkItem references to issue #70099 in commit 93efc8b.

Replace separate extraction and validation with a single pattern match expression

Co-authored-by: CyrusNajmabadi <[email protected]>
- Use objectCreationExpression.SpanStart directly instead of switch statement
- Update WorkItem references from issue #74268 to #70099

Co-authored-by: CyrusNajmabadi <[email protected]>
Copilot finished work on behalf of CyrusNajmabadi November 3, 2025 22:33
@CyrusNajmabadi CyrusNajmabadi marked this pull request as ready for review November 3, 2025 22:59
@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner November 3, 2025 22:59
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)]
public sealed class CollectionBuilderAttribute : Attribute
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the definition necessary? e.g. it isn't included in the standard testworkspace references?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

technically not in the refeences. but we have a string constant for it. updating.

CyrusNajmabadi and others added 3 commits November 3, 2025 15:14
…UseCollectionInitializerDiagnosticAnalyzer.cs

Co-authored-by: David Barbet <[email protected]>
@CyrusNajmabadi CyrusNajmabadi merged commit a114ea4 into main Nov 4, 2025
24 of 25 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Nov 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants