Skip to content

Source generator ignores class constructor arguments for a [GenerateGenericTest] generic method (CS7036) #7368

Closed

Description

@thomhurst

What happens

A generic test method compiles to broken code when its class takes constructor arguments from a class-level data source, if the method's type argument is fixed with [GenerateGenericTest]. The source generator emits __CreateInstance_0 as new TestClass() and ignores args, so the build fails with CS7036.

public sealed class ServiceDependencyInjectionAttribute : DependencyInjectionDataSourceAttribute<MyScope> { /* ... */ }

[ServiceDependencyInjection]
public class GenericMethodWithClassArgsTests(MyService service)
{
    public static IEnumerable<object> Rows() => [1];

    [Test]
    [GenerateGenericTest(typeof(int))]
    [MethodDataSource(nameof(Rows))]
    public async Task Run<T>(T value) => await Assert.That(service).IsNotNull();
}
TUnit_..._GenericMethodWithClassArgsTests_Run__T.g.cs(31,20): error CS7036: There is no argument given that corresponds to the required parameter 'service' of 'GenericMethodWithClassArgsTests.GenericMethodWithClassArgsTests(MyService)'

Generated code (net10.0, TUnit.Core.SourceGenerator, TestMetadataGenerator):

private static global::...GenericMethodWithClassArgsTests __CreateInstance_0(global::System.Type[] typeArgs, object?[] args)
{
    return new global::...GenericMethodWithClassArgsTests();
}

Where

Found while adding a regression test to #7328, at tests/TUnit.TestProject/Bugs/_7205 (the test was dropped). The instance factory for a generic method's concrete instantiation is written in src/TUnit.Core.SourceGenerator/Generators/TestMetadataGenerator.cs.

Expected

__CreateInstance_0 passes args to the constructor, as the non-generic __CreateInstance does (for example TUnit_TestProject_Bugs__7205_FailedRowScopeTests__TestSource.g.cs). Reflection mode should be checked too: it builds this class without a compile error, but whether it passes the arguments is unverified.

Acceptance criteria

  • A generic method fixed by [GenerateGenericTest] on a non-generic class with constructor data compiles and receives every constructor argument.
  • A targeted fixture passes in source-generated and reflection modes, asserting constructor and method values.
  • Add and review a generator snapshot that checks the concrete instance factory uses args.

Current-main evidence: TryAddInstantiation sets HasParameterizedConstructor only inside if (testMethod.IsGenericType); EmitConcreteInstanceCreation falls back to parameterless new for the non-generic class/generic-method combination.

Activity

  1. added
    bugSomething isn't working
    P2Medium: edge case, mode divergence, flaky test, or workaround exists
    on Nov 2, 2026
  2. thomhurst commented on Nov 3, 2026

    @thomhurst
    OwnerAuthor

    Audited against origin/main d87516c: TryAddInstantiation sets HasParameterizedConstructor only for a generic class (lines 1043–1054). A generic method on a non-generic class therefore reaches the parameterless new fallback in EmitConcreteInstanceCreation (line 1937). Added concrete acceptance criteria; reflection behavior still needs verification.

  3. thomhurst commented on Nov 5, 2026

    @thomhurst
    OwnerAuthor

    Already implemented by #7344, merged as a412ddd, and still present on current main (1a40c80).

    • EmitConcreteInstanceCreation now routes a non-generic class of a generic method through InstanceFactoryGenerator.GenerateInstanceFactoryBody(testMethod.TypeSymbol), passing constructor arguments instead of emitting a parameterless new.
    • The committed generator snapshot shows __CreateInstance_0 constructing ConstructorArgumentGenerateGenericTestArityTests with CastHelper.Cast<int>(args[0]).
    • The regression fixture asserts that the constructor receives its class row. Issue7222Tests checks its passing concrete instantiation in both reflection and AOT modes through InvokableTestBase.

    Verified the implementation and committed coverage against current main; no new code changes were needed. Did not rerun the engine suite locally, as required by the repository instructions. Closing as already fixed.

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

    P2Medium: edge case, mode divergence, flaky test, or workaround existsbugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions