Skip to content

Extend ToArray optimization when targeting read-only collection types#80104

Merged
RikkiGibson merged 32 commits intodotnet:mainfrom
DoctorKrolic:covariant-to-array
Oct 13, 2025
Merged

Extend ToArray optimization when targeting read-only collection types#80104
RikkiGibson merged 32 commits intodotnet:mainfrom
DoctorKrolic:covariant-to-array

Conversation

@DoctorKrolic
Copy link
Contributor

@DoctorKrolic DoctorKrolic commented Sep 2, 2025

  • Enable ToArry optimization for covariant conversions when targeting ReadOnlySpan, ImmutableArray, IEnumerable, IReadOnlyCollection or IReadOnlyList
  • Fix ToArray optimization when types are different in C# but identical in runtime (e.g. object vs dynamic)
  • Fix CopyTo optimization in the same scenario
  • Some code refactoring with @RikkiGibson

@DoctorKrolic DoctorKrolic requested a review from a team as a code owner September 2, 2025 17:49
@dotnet-policy-service dotnet-policy-service bot added the Community The pull request was submitted by a contributor who is not a Microsoft employee. label Sep 2, 2025
@DoctorKrolic
Copy link
Contributor Author

Can I have a review here? @RikkiGibson ?

@RikkiGibson RikkiGibson self-assigned this Sep 10, 2025
@RikkiGibson
Copy link
Member

I am concerned that the result of ToArray(), which we are doing a covariant conversion on, will be ultimately converted to Span and throw at runtime. See the following test which throws during execution in this branch

[Fact]
public void SingleSpread_SpanToSpan_Covariant()
{
    var source = """
        using System;

        class C
        {
            static void Main()
            {
                Span<string> span1 = ["a", "b", "c"];
                span1.Report();
                Span<object> span2 = [..span1];
                span2.Report();
            }
        }
        """;

    var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[a, b, c], [a, b, c],"), targetFramework: TargetFramework.Net80, verify: Verification.Skipped);
    verifier.VerifyDiagnostics();
            verifier.VerifyIL("C.Main", """
        TODO2: update baseline
        """);
}

I think this optimization needs to be limited to cases where the collection-expr type is a "readonly" type, such as ReadOnlySpan<T>, or an IEnumerable<T> converted from a private <>z__ReadOnlyArray<T>.

We can't use this optimization when the collection-expr is of an array type, for similar reasons as the Span case above. Today the array's runtime type is the same as the element type. If we start returning a TBase[] which throws when assigning a TBase into it, it's a behavioral breaking change. e.g. I think Array_06 is not a change we can make, since it is converting Derived[] to Base[], and not further converting the value to a "readonly collection type".

Could you please also add a test which does something like Base[] arr = [.. derivedArray]; arr[0] = new Base();, or something similar, to demonstrate that particular behavior that I think we should preserve.

@RikkiGibson
Copy link
Member

Taking another look this afternoon.

Copy link
Member

@RikkiGibson RikkiGibson left a comment

Choose a reason for hiding this comment

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

Looking much better and close to being able to sign off. Thanks

@RikkiGibson
Copy link
Member

RikkiGibson commented Sep 25, 2025

I pushed 9d61d77 to refactor VisitArrayOrSpanCollectionExpression.

This is a pure refactor as much as possible. I went into the original code, wrapped some sections in local functions, and moved some things around from one function to another, in order to make each path more focused on a specific task. I also tried to eliminate locals when they did not provide significant value, in order to make the set of data flowing from one function to another easier to follow.

Copy link
Member

@RikkiGibson RikkiGibson left a comment

Choose a reason for hiding this comment

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

PR LGTM, but, I think that 2 other @dotnet/roslyn-compiler members should look at the implementation side, since I pushed code to this.

@RikkiGibson
Copy link
Member

@jcouv for a new review pass

@@ -5046,6 +5045,1016 @@ static void Main()
Assert.Equal(CollectionExpressionTypeKind.ImplementsIEnumerable, ConversionsBase.GetCollectionExpressionTypeKind(comp, collectionType, out _));
}

[Theory, CombinatorialData]
Copy link
Contributor

Choose a reason for hiding this comment

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

to prevent massive collisions. Can you put the new teests in a new file. That will make my life much niccer.

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

LGTM Thanks (commit 31)

Copy link
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

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

Quickly glanced over compiler changes (commit 31). See other comments for details.

@RikkiGibson RikkiGibson requested a review from AlekseyTs October 10, 2025 21:57
@RikkiGibson
Copy link
Member

RikkiGibson commented Oct 10, 2025

@CyrusNajmabadi I tried merging the PR branch for #80568 into this PR branch locally. I found the conflicts fairly straightforward to handle, including that there were no conflicts in the tests. See the conflict resolutions in RikkiGibson@1d3ce15.

@AlekseyTs AlekseyTs dismissed their stale review October 13, 2025 18:24

The comments were addressed in commit 32

@DoctorKrolic
Copy link
Contributor Author

@RikkiGibson Can we merge now (assuming CI passes after rerun)?

@RikkiGibson
Copy link
Member

It looks like the failing test jobs were "dead letter" work items in Helix. I suspect this is due to some infrastructure issue. Will plan to merge if the rerun passes. Thanks

@RikkiGibson RikkiGibson merged commit c821d47 into dotnet:main Oct 13, 2025
24 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Oct 13, 2025
@RikkiGibson
Copy link
Member

Thanks for the contribution @DoctorKrolic!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-Compilers Code Gen Quality Room for improvement in the quality of the compiler's generated code Community The pull request was submitted by a contributor who is not a Microsoft employee.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants