Skip to content

Source-gen: untyped class-level data source with mixed row types runs every row on a method-row-inferred instantiation; reflection partitions by row type #7210

Open

Description

@thomhurst

What happens

A generic test class has a class-level [Arguments] row and an untyped class-level [MethodDataSource] whose rows have different types. Source-generated mode then pairs the data source's rows with the wrong instantiations. It runs every row on an instantiation that only a method row infers, and none of them on the instantiation that the class [Arguments] row infers.

public static class ProbeSources
{
    public static IEnumerable<object> Mixed() => ["s", 7];
}

[Arguments(1)]
[MethodDataSource(typeof(ProbeSources), nameof(ProbeSources.Mixed))]
public class ProbeMixedClassSource<T>(T value)
{
    [Test]
    [Arguments(1)]
    [Arguments("a")]
    public async Task Test(T arg) =>
        await Assert.That($"T={typeof(T).Name} value={value} arg={arg}").IsEqualTo("show");
}

Observed on fix/issue-7118-generic-row-pairing (PR #7149, head 234dd09), net10.0. Every test fails on purpose, so that it prints its values:

Mode Tests
Source-generated C<int>: value=1 arg=1. C<string>: value=s arg=a, plus an extra test that fails with MissingMethodException (row 7 passed to C<string>(string)).
Reflection C<int>: value=1 arg=1 and value=7 arg=1. C<string>: value=s arg=a.

Expected

Source-generated mode should match reflection. Reflection groups the data source's rows by the types of their values. Row 7 should run on C<int> beside the [Arguments(1)] row, and C<string> should get only "s", with no extra failing test.

Why it is not a compile-time fix

The generator cannot see the row types of an untyped source (IEnumerable<object>). Since 234dd09 it attaches such a source to every instantiation that only method rows infer. That is right when its rows match the instantiation, but not when they mix types. Before that commit, the C<string> test failed with the "no class row" message. Matching reflection needs a run-time partition of the source's rows by the class type arguments that they infer, which is what reflection does with StaticDataSourceAttribute.PartitionOf. That partition must also respect the fallback that #7192 (#7161) adds: when no row infers the instantiation, every row is used and converted to the constructor's types.

Found in review of PR #7149: #7149 (comment)

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions