Skip to content

Fix ArgumentsSource on external types not actually working #2820

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

JimmyCushnie
Copy link
Contributor

@JimmyCushnie JimmyCushnie commented Aug 14, 2025

Followup to #2748. As of BDN 0.15.2, you can't use [ArgumentsSource] with an external type, because the generated code has compiler errors.

When I made #2748, I guess I must have forgotten to actually test the functionality, which is very embarrassing of me and which I apologize for! I just tried to use the functionality in my project and it didn't work, so here I am fixing it 😭

The issue

If you have code like this:

[Benchmark]
[ArgumentsSource(typeof(DiagnosticsCases), nameof(DiagnosticsCases.MethodACases))]
public MethodAResults BenchmarkMethodA(MethodACase @case)
    => CodeBeingBenchmarked.MethodA(@case.Param1, @case.Param2, @case.Param3);

Then BDN currently generates code like this:

public Runnable_0()
{
    globalSetupAction = () => { };
    globalCleanupAction = () => { };
    iterationSetupAction = () => { };
    iterationCleanupAction = () => { };
    overheadDelegate = __Overhead;
    workloadDelegate = WithChildrenAdded;
    __argField0 = (ExampleNamespace.MethodACase)BenchmarkDotNet.Parameters.ParameterExtractor.GetParameter(MethodACases(), 0);;
}

The problem is that MethodACases is not in the type that the generated class (Runnable_0) inherits from, it's in an external type (DiagnosticsCases). So there's a compiler error here ("error CS0103: The name 'MethodACases' does not exist in the current context)

The fix

In this example, we need to put the fully qualified type name before the method call. The last line of the generated code should instead read:

__argField0 = (ExampleNamespace.MethodACase)BenchmarkDotNet.Parameters.ParameterExtractor.GetParameter(ExampleNamespace.DiagnosticsCases.MethodACases(), 0);;

However, we can't always add the fully qualified type name, because ArgumentsSource can also be used with non-static methods/properties.

So, this PR checks if ArgumentsSource is being used with a static type. If so, the fully qualified type name is added at this location; otherwise, the generated code is unchanged compared to before this PR.

I am worried about external instance methods

Using ArgumentsSource with different kinds of members:

Does it work before this PR Does it work after this PR
Static member from the same class
Static member from an external class
Instance member from the same class
Instance member from an external class

As you can see from this helpful table, this PR fixes one broken case but there is still one broken case remaining. How best to fix this? Should the generated code create an instance of the member's type, and call the member from the new instance?

To be honest, I think there's no benefit to supporting instance members at all. @timcassell also dislikes that we support instance members and we discussed it a bit here: #2744 (comment).

Maybe we should take this opportunity to just drop support for instance members in ArgumentsSource and ParamsSource?

Other things I am worried about

  • The way this PR checks if the member is static is kind of brittle and makes me nervous. Is there a better way of doing it? (If we just drop support for instance members, this problem goes away!!)
  • Tests were added for this in Allow ArgumentsSource to reference methods in other types #2748, using BenchmarkTestExecutor.CanExecute. Why didn't those tests catch that the test case actually couldn't execute?
    • Is this a limitation of BenchmarkTestExecutor.CanExecute?
    • Should new tests be added that actually catch it?
    • Should BenchmarkTestExecutor.CanExecute be updated to account for this category of problem? (I'm assuming it can't catch problems with the codegen, but I haven't investigated)
  • Is the code I wrote the correct way to fix the described issue? I would appreciate some feedback from someone more familiar with the codegen parts of BDN. It's very complex code and I couldn't fully wrap my head around it. But I did actually test it this time and I can confirm that this PR fixes the described issue.

@filzrev
Copy link
Contributor

filzrev commented Aug 15, 2025

I thought sample benchmark IntroArgumentsSource.cs also needs to be fixed.

Currently IntroArgumentsSource benchmark failed with following errors.
(And it seems to be fixed by adding static modifier)

BenchmarkDotNet.Samples-DefaultJob-1.notcs(2565,103): error CS0103: The name 'TimeSpans' does not exist in the current context

@JimmyCushnie
Copy link
Contributor Author

Thanks, nice catch. Should be fixed now!

@timcassell
Copy link
Collaborator

I'm trying to understand what was broken before. When I run the tests, they succeed. Can you add tests that failed previously that pass with this change?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants