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
- 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;
}
-
Build the containing project.
-
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?
Description
[GenerateAssertion]emits invalid C# when the source method has aparamsarray preceded by any other parameter. The generated extension method appends[CallerArgumentExpression(...)]diagnostic parameters AFTER theparamsarray, 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 theparamsarray.Actual Behavior
The generated extension method puts
[CallerArgumentExpression]parameters after theparamsarray. The C# compiler rejects the file witherror CS0231: A params parameter must be the last parameter in a parameter list.Steps to Reproduce
[GenerateAssertion]source method that has at least one non-params parameter followed byparams:Build the containing project.
Observe the build fails with CS0231 in the generated file
Demo.GeneratedAssertions.g.cs.The generator emits:
The
params string[] candidatesparameter 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 listAdditional Context
Why production
[GenerateAssertion]methods do not currently trip this:GenericAssertions.IsIn/IsNotInare the only[GenerateAssertion]methods inTUnit.Assertionsthat useparams, and both declare ONLY theparamsparameter (no preceding non-params source parameter). The generator's existingif (!param.IsParams)skip prevents emitting aCallerArgumentExpressionfor theparamsparameter itself, so the single-parameter shape stays legal C#. CS0231 fires only when at least one non-params source parameter sits before aparamssource parameter.Workaround: drop the
paramsmodifier 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 theparamsparameter: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?
dotnet testordotnet run, not just in my IDE