Skip to content

[Bug]: [GenerateAssertion] emits CallerArgumentExpression after params, producing CS0231 #5939

@JohnVerheij

Description

@JohnVerheij

Description

[GenerateAssertion] emits invalid C# when the source method has a params array preceded by any other parameter. The generated extension method appends [CallerArgumentExpression(...)] diagnostic parameters AFTER the params array, which violates C#'s "params must be the last parameter" rule and produces compile error CS0231.

Expected Behavior

The generator produces an extension method that compiles cleanly, with each [CallerArgumentExpression] parameter placed before the params array.

Actual Behavior

The generated extension method puts [CallerArgumentExpression] parameters after the params array. The C# compiler rejects the file with error CS0231: A params parameter must be the last parameter in a parameter list.

Steps to Reproduce

  1. Create a static partial class with a [GenerateAssertion] source method that has at least one non-params parameter followed by params:
using TUnit.Assertions.Attributes;

public static partial class Demo
{
    [GenerateAssertion]
    public static bool ContainsAny(this string value, string label, params string[] candidates)
        => candidates.Length > 0;
}
  1. Build the containing project.

  2. Observe the build fails with CS0231 in the generated file Demo.GeneratedAssertions.g.cs.

The generator emits:

public static String_ContainsAny_String_StringArray_Assertion ContainsAny(
    this IAssertionSource<string> source,
    string label,
    params string[] candidates,
    [CallerArgumentExpression(nameof(label))] string? labelExpression = null)

The params string[] candidates parameter is followed by another parameter, which is illegal.

TUnit Version

1.44.39 (also confirmed against current main at 420248e)

.NET Version

Reproduces on all targets supported by TUnit.Assertions (net472 / net8.0 / net9.0 / net10.0)

Operating System

Windows

IDE / Test Runner

dotnet CLI (dotnet test / dotnet run)

Error Output / Stack Trace

error CS0231: A params parameter must be the last parameter in a parameter list

Additional Context

Why production [GenerateAssertion] methods do not currently trip this: GenericAssertions.IsIn / IsNotIn are the only [GenerateAssertion] methods in TUnit.Assertions that use params, and both declare ONLY the params parameter (no preceding non-params source parameter). The generator's existing if (!param.IsParams) skip prevents emitting a CallerArgumentExpression for the params parameter itself, so the single-parameter shape stays legal C#. CS0231 fires only when at least one non-params source parameter sits before a params source parameter.

Workaround: drop the params modifier and accept a regular array; consumers then need C# 12 collection-expression syntax (["a", "b"]) at the call site instead of positional "a", "b".

Fix direction: reorder the generator emit so the [CallerArgumentExpression] parameters for non-params source parameters precede the params parameter:

public static String_ContainsAny_String_StringArray_Assertion ContainsAny(
    this IAssertionSource<string> source,
    string label,
    [CallerArgumentExpression(nameof(label))] string? labelExpression = null,
    params string[] candidates)

C# allows optional-with-default parameters to precede params, so the reorder is semantically transparent at every call site.

A PR with this fix and a regression-test fixture is ready to go alongside this issue.

IDE-Specific Issue?

  • I've confirmed this issue occurs when running via dotnet test or dotnet run, not just in my IDE

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