Skip to content

Reflection mode: MethodDataSource Arguments that omit an optional parameter throw AmbiguousMatchException when the method is overloaded #7331

Closed

Description

@thomhurst

What happened

In reflection mode, a [MethodDataSource] row whose Arguments leave out an optional parameter throws AmbiguousMatchException when the data method name has overloads and no argument is null. Source-gen mode picks the overload, so the two modes diverge.

AmbiguousMatchException: Ambiguous match found for '... DataSourceOverloadParityDerivedProvider ... OverriddenDefault(System.String, Int32)'.
   at System.RuntimeType.GetMethodImplCommon(...)
   at System.Type.GetMethod(String name, BindingFlags bindingAttr)

Where

src/TUnit.Core/Attributes/TestData/MethodDataSourceAttribute.cs:

  • ResolveDataSourceMethod passes the argument types to the runtime binder (targetType.GetMethod(name, 0, BindingFlags, null, argumentTypes, null)). The default binder only matches exact-arity overloads unless BindingFlags.OptionalParamBinding is set, so it returns null for Rows(string expected, int count = 1) with Arguments = { "x" }.
  • The caller then falls back to the name-only targetType.GetMethod(MethodNameProvidingDataSource, BindingFlags), which throws AmbiguousMatchException as soon as the name is overloaded.
  • With a null argument, the row goes through ResolveDataSourceMethodWithNullArgument, which scores optional parameters the way the source generator's ScoreDataSourceOverload does, so that path works.

Repro

public static IEnumerable<string> Rows(string expected, int count = 1) => [expected];
public static IEnumerable<string> Rows(int value) => ["int"];

[Test]
#pragma warning disable TUnit0001 // the analyzer requires one argument per parameter
[MethodDataSource(nameof(Rows), Arguments = new object[] { "a" })]
#pragma warning restore TUnit0001
public void Test(string value) { }

Run with --reflection: the test fails with AmbiguousMatchException. Without --reflection it passes and calls Rows(string, int).

Found while validating #7323: its OverriddenDefault parity row uses a null argument to stay on the working path.

Expected

Reflection mode scores optional and CancellationToken parameters for non-null arguments too, for example by falling back to the ResolveDataSourceMethodWithNullArgument scoring when the binder finds no exact-arity overload. Both modes then pick the same overload.

Related: TUnit0001 also requires one argument per parameter, so it rejects these rows at compile time.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium: edge case, mode divergence, flaky test, or workaround existsbugSomething isn't workingin-progressWork is in progress

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions