Description
When an [AssertionExtension]-decorated class introduces its own generic type parameter and derives from Assertion<TConcrete> for a non-sealed concrete receiver, AssertionExtensionGenerator emits the receiver's covariant type parameter and the class's own generic parameter as two adjacent angle-bracket lists in the generated extension method signature.
Expected Behavior
The generator should emit a single merged generic-parameter list (for example <TActual, T>) so the generated extension compiles.
Actual Behavior
The generator emits something shaped like <TActual><T>, with two separate generic-parameter blocks back-to-back. This is invalid C# syntax; the file fails to parse.
Steps to Reproduce
- Declare an
[AssertionExtension] class that introduces a generic parameter and derives from Assertion<NonSealedConcreteType>:
[AssertionExtension("Matches")]
public class MatchesAssertion<T> : Assertion<System.Exception>
{
private readonly System.Func<T, bool> _predicate;
public MatchesAssertion(AssertionContext<System.Exception> context, System.Func<T, bool> predicate)
: base(context) { _predicate = predicate; }
protected override System.Threading.Tasks.Task<AssertionResult> CheckAsync(
EvaluationMetadata<System.Exception> metadata)
=> System.Threading.Tasks.Task.FromResult(AssertionResult.Passed);
protected override string GetExpectation() => "to match";
}
- Build the project. The generated extension method has two adjacent
<...> blocks in its signature and the build fails.
TUnit Version
1.44.0
.NET Version
.NET 10.0
Operating System
Windows
IDE / Test Runner
dotnet CLI (dotnet test / dotnet run)
Error Output / Stack Trace
error CS1003: Syntax error, ',' expected
Additional Context
The bug surfaces when both conditions hold:
- The assertion class declares its own generic type parameter (
class MyAssertion<T> : ...).
- The base assertion type is
Assertion<TConcrete> where TConcrete is a non-sealed class (i.e. a covariance candidate per the generator's existing covariance logic).
A class that derives from Assertion<TConcrete> for a sealed type, or a class without its own generic parameter, is not affected.
IDE-Specific Issue?
Description
When an
[AssertionExtension]-decorated class introduces its own generic type parameter and derives fromAssertion<TConcrete>for a non-sealed concrete receiver,AssertionExtensionGeneratoremits the receiver's covariant type parameter and the class's own generic parameter as two adjacent angle-bracket lists in the generated extension method signature.Expected Behavior
The generator should emit a single merged generic-parameter list (for example
<TActual, T>) so the generated extension compiles.Actual Behavior
The generator emits something shaped like
<TActual><T>, with two separate generic-parameter blocks back-to-back. This is invalid C# syntax; the file fails to parse.Steps to Reproduce
[AssertionExtension]class that introduces a generic parameter and derives fromAssertion<NonSealedConcreteType>:<...>blocks in its signature and the build fails.TUnit Version
1.44.0
.NET Version
.NET 10.0
Operating System
Windows
IDE / Test Runner
dotnet CLI (dotnet test / dotnet run)
Error Output / Stack Trace
error CS1003: Syntax error, ',' expectedAdditional Context
The bug surfaces when both conditions hold:
class MyAssertion<T> : ...).Assertion<TConcrete>whereTConcreteis a non-sealed class (i.e. a covariance candidate per the generator's existing covariance logic).A class that derives from
Assertion<TConcrete>for a sealed type, or a class without its own generic parameter, is not affected.IDE-Specific Issue?
dotnet testordotnet run, not just in my IDE