Skip to content

Source-gen: generic method runs only the first [Arguments] row per inferred type #7096

Closed

Description

@thomhurst

Summary

In source-generated mode, a generic test method with several [Arguments] rows only runs the first row for each inferred type argument. Any later row that infers the same T is silently dropped. Reflection mode runs every row.

Found while working on #7094.

Repro

public class GenericMethodSameTypeArgumentsTests
{
    [Test]
    [Arguments(1)]
    [Arguments(2)]
    [Arguments(3)]
    public async Task SameType<T>(T value)
    {
        await Assert.That(value).IsTypeOf<int>();
    }

    [Test]
    [Arguments(1)]
    [Arguments("a")]
    [Arguments(2)]
    [Arguments("b")]
    public async Task MixedTypes<T>(T value)
    {
        await Assert.That(value is int or string).IsTrue();
    }
}

Run with --treenode-filter "/*/<namespace>/GenericMethodSameTypeArgumentsTests/*".

Expected

7 tests in both modes: SameType runs for 1, 2 and 3, and MixedTypes runs for 1, "a", 2 and "b".

Actual

Mode Total Tests that ran
Source-gen 3 SameType<int>(1), MixedTypes<int>(1), MixedTypes<string>(a)
Reflection (--reflection) 7 SameType(1), SameType(2), SameType(3), MixedTypes(1), MixedTypes(2), MixedTypes(a), MixedTypes(b)

Nothing fails or warns. The extra rows just never get discovered.

Root cause

TestMetadataGenerator.CollectConcreteInstantiations builds one concrete instantiation per type-argument combination and skips any combination it has already seen (processedTypeCombinations, keyed by BuildTypeKey). For [Arguments] rows, the instantiation also records the row that produced it (SpecificArgumentsAttribute), and EmitConcreteDataSources / EmitConcreteAttributeFactory then emit only that row as the entry's data source. So the second [Arguments(2)] row infers int, hits the dedup, and is discarded, and the int entry only carries [Arguments(1)].

Related observations (not part of this fix)

Generic classes have similar gaps, but there reflection mode doesn't run every row either, so the two modes already agree:

  • [Arguments(1)] [Arguments(2)] class Foo<T>(T value) runs 1 test in both modes.
  • class Foo<T> { [Test, Arguments(1), Arguments(2)] void M(T value) } runs 1 test in source-gen and 0 in reflection.

Activity

  1. added a commit that references this issue on Oct 18, 2026
    e31ee4f
  2. added a commit that references this issue on Oct 18, 2026
    c076d1a
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