Skip to content

Fix false positive "infinite chain of calls" error in collection expressions when parameterless constructor exists alongside params constructor#82591

Draft
Copilot wants to merge 4 commits intomainfrom
copilot/fix-false-positive-cs9223
Draft

Fix false positive "infinite chain of calls" error in collection expressions when parameterless constructor exists alongside params constructor#82591
Copilot wants to merge 4 commits intomainfrom
copilot/fix-false-positive-cs9223

Conversation

Copy link
Contributor

Copilot AI commented Mar 2, 2026

  • Understand the issue: False positive CS9223 when MyCollection has both a parameterless constructor and a params constructor
  • Fix HasCollectionExpressionApplicableConstructor to find the parameterless constructor when in cycle with no with-element
  • Fix TryConvertCollectionExpressionImplementsIEnumerableType to only report CS9223 when constructor is null (true cycle)
  • Update test ParamsCycle_MultipleConstructors to remove false positive expectation + add [WorkItem] attribute
  • Remove duplicate ParamsCycle_MultipleConstructors_ClassContext test (same coverage as existing test)
  • Add Debug.Assert(constructor.Parameters.IsEmpty) and improve comment to clarify constructor propagation
  • Re-add ParamsCycle_MultipleConstructors_ClassContext as a CompileAndVerify test with expectedOutput: "1" to verify code execution
  • Run tests - all ParamsCycle tests pass
Original prompt

This section details on the original issue you should resolve

<issue_title>False positive CS9223 in collection expression arguments</issue_title>
<issue_description>Steps to Reproduce:
Consider this code .NET lab:

class MyCollection<T> : IEnumerable<T>
{
    private readonly List<T> _list;
    IEnumerator<T> IEnumerable<T>.GetEnumerator() => _list.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator();
    public MyCollection() { _list = new(); }
    public MyCollection(params MyCollection<T> other) { _list = new(other); }
    public void Add(T t) { _list.Add(t); }
}

class C
{
    public void M()
    {
        MyCollection<int> c = [with(1)]; // Creation of params collection 'MyCollection<int>' results in an infinite chain of invocation of constructor 'MyCollection<T>.MyCollection()'.
    }
}

Diagnostic Id: CS9223

Expected Behavior:
No CS9223 is reported, because the call stack will be MyCollection(params MyCollection<T> other) --> MyCollection() --> .Add() and no infinite recursion is happened. This exact behavior is fixed under a test CollectionExpressionTests_WithElement_Extra.ParamsCycle_MultipleConstructors

Actual Behavior:
CS9223 is reported</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

…ameterless constructor exists

Co-authored-by: jjonescz <3669664+jjonescz@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix false positive CS9223 in collection expression arguments Fix false positive CS9223 in collection expressions when parameterless constructor exists alongside params constructor Mar 2, 2026
Copy link
Member

@jjonescz jjonescz left a comment

Choose a reason for hiding this comment

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

…st, add Debug.Assert

Co-authored-by: jjonescz <3669664+jjonescz@users.noreply.github.com>
…fy test with expectedOutput

Co-authored-by: jjonescz <3669664+jjonescz@users.noreply.github.com>
@jjonescz jjonescz changed the title Fix false positive CS9223 in collection expressions when parameterless constructor exists alongside params constructor Fix false positive "infinite chain of calls" error in collection expressions when parameterless constructor exists alongside params constructor Mar 3, 2026
@jjonescz jjonescz marked this pull request as ready for review March 3, 2026 15:01
@jjonescz jjonescz requested a review from a team as a code owner March 3, 2026 15:01
Comment on lines 8303 to 8305
Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "with()").WithArguments("x", "MyCollection<int>.MyCollection(int, params MyCollection<int>)").WithLocation(3, 6),
// (4,6): error CS9223: Creation of params collection 'MyCollection<int>' results in an infinite chain of invocation of constructor 'MyCollection<T>.MyCollection(T, params MyCollection<T>)'.
// c = [with(1)];
Copy link

@Spolutrean Spolutrean Mar 4, 2026

Choose a reason for hiding this comment

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

As I can understand here is also won't be any infinite recursion, because we cannot instantiate MyCollection<T> without at least one argument.

But I expect here an error of unresolved constructor call of MyCollection<T> due to arguments to parameters mismatch, because the params MyCollection<T> y parameter will be treated as a regular non-params parameter (because there is no default .ctor on MyCollection<T>), therefore we didn't provided the suitable argument for it.

@jjonescz #Closed

Copy link
Member

Choose a reason for hiding this comment

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

It is required that a params collection has a parameterless constructor, that's why we have the error below for this (8,30): error CS9228: Non-array params collection type must have an applicable constructor that can be called with no arguments.

Then the compiler can assume it can create the params collection without arguments and the cycle error makes sense. Sure we could omit this error but it wouldn't solve much, just slightly better error recovery.

Debug.Assert(inProgressConstructor is not null);
_diagnostics.Add(ErrorCode.ERR_ParamsCollectionInfiniteChainOfConstructorCalls, syntax, inProgress, inProgressConstructor.OriginalDefinition);
return null;
if (constructor is null)
Copy link
Contributor

Choose a reason for hiding this comment

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

if (constructor is null)

The logic here looks suspicious If we haven't found a constructor, shouldn't that indicate an error on its own?

if (!hasWithElement)
{
// We are in a cycle, but without a with-element, a parameterless constructor can break the
// cycle since it won't recursively invoke the params constructor. Check if one is accessible.
Copy link
Contributor

Choose a reason for hiding this comment

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

This logic feels fragile and suspicious.

@@ -979,9 +979,17 @@ CollectionExpressionTypeKind.Array or CollectionExpressionTypeKind.Span or Colle
if (_targetType is NamedTypeSymbol namedType &&
_binder.HasParamsCollectionTypeInProgress(namedType, out NamedTypeSymbol? inProgress, out MethodSymbol? inProgressConstructor))
Copy link
Contributor

Choose a reason for hiding this comment

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

_binder.HasParamsCollectionTypeInProgress(namedType, out NamedTypeSymbol? inProgress, out MethodSymbol? inProgressConstructor)

If tracking cycle per type is not precise enough, we probably should track it per specific constructor.

@AlekseyTs
Copy link
Contributor

                    var binder = new ParamsCollectionTypeInProgressBinder(namedType, @this._binder, withElement != null, constructor);

Is this line doing the right thing when withElement is used? For example, the following code doesn't use with and doesn't complain about a cycle:

using System.Collections;
using System.Collections.Generic;

partial class MyCollection : IEnumerable
{
    public List<object> Array;
    public MyCollection(params MyCollection p)
    {
        Array = new List<object>();
    }

    IEnumerator IEnumerable.GetEnumerator() => throw null;
    public void Add(object l, params MyCollection p) => Array.Add(l);
}

class Program
{
    static void Main()
    {
        Test();
        Test(1);
        Test(2, 3);
    }

    static void Test(params MyCollection a)
    {
    }
}

partial class MyCollection : IEnumerable
{
    public MyCollection()
    {

    }
}

And, if I suppress creation of this binder in presence of with, the error is not reported for scenario in ParamsCycle_MultipleConstructors_ClassContext unit-test.


Refers to: src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs:1058 in fe0b802. [](commit_id = fe0b802, deletion_comment = False)

@AlekseyTs
Copy link
Contributor

Done with review pass (commit 4)

@jjonescz jjonescz marked this pull request as draft March 9, 2026 16:01
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.

False positive CS9223 in collection expression arguments

4 participants