diff --git a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.MethodOnConcreteNonSealedReceiver.DotNet10_0.verified.txt b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.MethodOnConcreteNonSealedReceiver.DotNet10_0.verified.txt
new file mode 100644
index 0000000000..cb8f1c660e
--- /dev/null
+++ b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.MethodOnConcreteNonSealedReceiver.DotNet10_0.verified.txt
@@ -0,0 +1,71 @@
+[
+//
+#pragma warning disable
+#nullable enable
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using TUnit.Assertions.Core;
+using TUnit.Assertions.Tests.TestData;
+
+namespace TUnit.Assertions.Extensions;
+
+///
+/// Generated assertion for HasItem
+///
+[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+public sealed class MethodOnConcreteNonSealedReceiver_HasItem_T_Assertion : Assertion
+{
+ private static readonly Task _passedTask = Task.FromResult(AssertionResult.Passed);
+
+ private readonly T _item;
+
+ public MethodOnConcreteNonSealedReceiver_HasItem_T_Assertion(AssertionContext context, T item)
+ : base(context)
+ {
+ _item = item;
+ }
+
+ protected override Task CheckAsync(EvaluationMetadata metadata)
+ {
+ var value = metadata.Value;
+ var exception = metadata.Exception;
+
+ if (exception != null)
+ {
+ return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().FullName}"));
+ }
+
+ if (value is null)
+ {
+ return Task.FromResult(AssertionResult.Failed("Actual value is null"));
+ }
+
+ var result = value!.HasItem(_item);
+ return result
+ ? _passedTask
+ : Task.FromResult(AssertionResult.Failed($"found {value}"));
+ }
+
+ protected override string GetExpectation()
+ {
+ return $"to satisfy HasItem({_item})";
+ }
+}
+
+public static partial class MethodOnConcreteNonSealedReceiverExtensions
+{
+ ///
+ /// Generated extension method for HasItem
+ ///
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static MethodOnConcreteNonSealedReceiver_HasItem_T_Assertion HasItem(this IAssertionSource source, T item, [CallerArgumentExpression(nameof(item))] string? itemExpression = null)
+ {
+ source.Context.ExpressionBuilder.Append($".HasItem({itemExpression})");
+ return new MethodOnConcreteNonSealedReceiver_HasItem_T_Assertion(source.Context, item);
+ }
+
+}
+
+]
\ No newline at end of file
diff --git a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.MethodOnConcreteNonSealedReceiver.DotNet8_0.verified.txt b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.MethodOnConcreteNonSealedReceiver.DotNet8_0.verified.txt
new file mode 100644
index 0000000000..cb8f1c660e
--- /dev/null
+++ b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.MethodOnConcreteNonSealedReceiver.DotNet8_0.verified.txt
@@ -0,0 +1,71 @@
+[
+//
+#pragma warning disable
+#nullable enable
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using TUnit.Assertions.Core;
+using TUnit.Assertions.Tests.TestData;
+
+namespace TUnit.Assertions.Extensions;
+
+///
+/// Generated assertion for HasItem
+///
+[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+public sealed class MethodOnConcreteNonSealedReceiver_HasItem_T_Assertion : Assertion
+{
+ private static readonly Task _passedTask = Task.FromResult(AssertionResult.Passed);
+
+ private readonly T _item;
+
+ public MethodOnConcreteNonSealedReceiver_HasItem_T_Assertion(AssertionContext context, T item)
+ : base(context)
+ {
+ _item = item;
+ }
+
+ protected override Task CheckAsync(EvaluationMetadata metadata)
+ {
+ var value = metadata.Value;
+ var exception = metadata.Exception;
+
+ if (exception != null)
+ {
+ return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().FullName}"));
+ }
+
+ if (value is null)
+ {
+ return Task.FromResult(AssertionResult.Failed("Actual value is null"));
+ }
+
+ var result = value!.HasItem(_item);
+ return result
+ ? _passedTask
+ : Task.FromResult(AssertionResult.Failed($"found {value}"));
+ }
+
+ protected override string GetExpectation()
+ {
+ return $"to satisfy HasItem({_item})";
+ }
+}
+
+public static partial class MethodOnConcreteNonSealedReceiverExtensions
+{
+ ///
+ /// Generated extension method for HasItem
+ ///
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static MethodOnConcreteNonSealedReceiver_HasItem_T_Assertion HasItem(this IAssertionSource source, T item, [CallerArgumentExpression(nameof(item))] string? itemExpression = null)
+ {
+ source.Context.ExpressionBuilder.Append($".HasItem({itemExpression})");
+ return new MethodOnConcreteNonSealedReceiver_HasItem_T_Assertion(source.Context, item);
+ }
+
+}
+
+]
\ No newline at end of file
diff --git a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.MethodOnConcreteNonSealedReceiver.DotNet9_0.verified.txt b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.MethodOnConcreteNonSealedReceiver.DotNet9_0.verified.txt
new file mode 100644
index 0000000000..cb8f1c660e
--- /dev/null
+++ b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.MethodOnConcreteNonSealedReceiver.DotNet9_0.verified.txt
@@ -0,0 +1,71 @@
+[
+//
+#pragma warning disable
+#nullable enable
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using TUnit.Assertions.Core;
+using TUnit.Assertions.Tests.TestData;
+
+namespace TUnit.Assertions.Extensions;
+
+///
+/// Generated assertion for HasItem
+///
+[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+public sealed class MethodOnConcreteNonSealedReceiver_HasItem_T_Assertion : Assertion
+{
+ private static readonly Task _passedTask = Task.FromResult(AssertionResult.Passed);
+
+ private readonly T _item;
+
+ public MethodOnConcreteNonSealedReceiver_HasItem_T_Assertion(AssertionContext context, T item)
+ : base(context)
+ {
+ _item = item;
+ }
+
+ protected override Task CheckAsync(EvaluationMetadata metadata)
+ {
+ var value = metadata.Value;
+ var exception = metadata.Exception;
+
+ if (exception != null)
+ {
+ return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().FullName}"));
+ }
+
+ if (value is null)
+ {
+ return Task.FromResult(AssertionResult.Failed("Actual value is null"));
+ }
+
+ var result = value!.HasItem(_item);
+ return result
+ ? _passedTask
+ : Task.FromResult(AssertionResult.Failed($"found {value}"));
+ }
+
+ protected override string GetExpectation()
+ {
+ return $"to satisfy HasItem({_item})";
+ }
+}
+
+public static partial class MethodOnConcreteNonSealedReceiverExtensions
+{
+ ///
+ /// Generated extension method for HasItem
+ ///
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static MethodOnConcreteNonSealedReceiver_HasItem_T_Assertion HasItem(this IAssertionSource source, T item, [CallerArgumentExpression(nameof(item))] string? itemExpression = null)
+ {
+ source.Context.ExpressionBuilder.Append($".HasItem({itemExpression})");
+ return new MethodOnConcreteNonSealedReceiver_HasItem_T_Assertion(source.Context, item);
+ }
+
+}
+
+]
\ No newline at end of file
diff --git a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.MethodOnConcreteNonSealedReceiver.Net4_7.verified.txt b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.MethodOnConcreteNonSealedReceiver.Net4_7.verified.txt
new file mode 100644
index 0000000000..cb8f1c660e
--- /dev/null
+++ b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.MethodOnConcreteNonSealedReceiver.Net4_7.verified.txt
@@ -0,0 +1,71 @@
+[
+//
+#pragma warning disable
+#nullable enable
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using TUnit.Assertions.Core;
+using TUnit.Assertions.Tests.TestData;
+
+namespace TUnit.Assertions.Extensions;
+
+///
+/// Generated assertion for HasItem
+///
+[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+public sealed class MethodOnConcreteNonSealedReceiver_HasItem_T_Assertion : Assertion
+{
+ private static readonly Task _passedTask = Task.FromResult(AssertionResult.Passed);
+
+ private readonly T _item;
+
+ public MethodOnConcreteNonSealedReceiver_HasItem_T_Assertion(AssertionContext context, T item)
+ : base(context)
+ {
+ _item = item;
+ }
+
+ protected override Task CheckAsync(EvaluationMetadata metadata)
+ {
+ var value = metadata.Value;
+ var exception = metadata.Exception;
+
+ if (exception != null)
+ {
+ return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().FullName}"));
+ }
+
+ if (value is null)
+ {
+ return Task.FromResult(AssertionResult.Failed("Actual value is null"));
+ }
+
+ var result = value!.HasItem(_item);
+ return result
+ ? _passedTask
+ : Task.FromResult(AssertionResult.Failed($"found {value}"));
+ }
+
+ protected override string GetExpectation()
+ {
+ return $"to satisfy HasItem({_item})";
+ }
+}
+
+public static partial class MethodOnConcreteNonSealedReceiverExtensions
+{
+ ///
+ /// Generated extension method for HasItem
+ ///
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static MethodOnConcreteNonSealedReceiver_HasItem_T_Assertion HasItem(this IAssertionSource source, T item, [CallerArgumentExpression(nameof(item))] string? itemExpression = null)
+ {
+ source.Context.ExpressionBuilder.Append($".HasItem({itemExpression})");
+ return new MethodOnConcreteNonSealedReceiver_HasItem_T_Assertion(source.Context, item);
+ }
+
+}
+
+]
\ No newline at end of file
diff --git a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.cs b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.cs
index 47bd63421d..b8a3a2df43 100644
--- a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.cs
+++ b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.cs
@@ -395,4 +395,27 @@ await Assert.That(mainFile).Contains(
// that a content-only assertion cannot see.
await CompileChecker.AssertNoErrors(generatedFiles);
});
+
+ [Test]
+ public Task MethodOnConcreteNonSealedReceiver() => RunTest(
+ Path.Combine(Sourcy.Git.RootDirectory.FullName,
+ "TUnit.Assertions.SourceGenerator.Tests",
+ "TestData",
+ "MethodOnConcreteNonSealedReceiver.cs"),
+ async generatedFiles =>
+ {
+ await Assert.That(generatedFiles).Count().IsEqualTo(1);
+
+ var mainFile = generatedFiles.First();
+ await Assert.That(mainFile).IsNotNull();
+
+ // The generated extension method must declare a single type parameter (T from the
+ // source method) and target the exact receiver type. Prepending the covariant
+ // receiver-type parameter (TActual) for this shape produces a two-type-parameter
+ // signature that callers cannot satisfy via partial type-argument specification,
+ // breaking call sites like .HasItem(42) with CS1929.
+ await Assert.That(mainFile).Contains("HasItem(this IAssertionSource source");
+ await Assert.That(mainFile).DoesNotContain("HasItem");
+ await Assert.That(mainFile).DoesNotContain("where TActual :");
+ });
}
diff --git a/TUnit.Assertions.SourceGenerator.Tests/TestData/MethodOnConcreteNonSealedReceiver.cs b/TUnit.Assertions.SourceGenerator.Tests/TestData/MethodOnConcreteNonSealedReceiver.cs
new file mode 100644
index 0000000000..dd5d8d548e
--- /dev/null
+++ b/TUnit.Assertions.SourceGenerator.Tests/TestData/MethodOnConcreteNonSealedReceiver.cs
@@ -0,0 +1,18 @@
+using TUnit.Assertions.Attributes;
+
+namespace TUnit.Assertions.Tests.TestData;
+
+///
+/// Test case: generic [GenerateAssertion] method on a concrete non-sealed receiver.
+/// Generated extension must declare a single type parameter (T) targeting the exact
+/// receiver type, not a two-parameter <TActual, T> covariant shape.
+///
+public class MethodOnConcreteNonSealedReceiver
+{
+}
+
+public static partial class MethodOnConcreteNonSealedReceiverExtensions
+{
+ [GenerateAssertion]
+ public static bool HasItem(this MethodOnConcreteNonSealedReceiver receiver, T item) => true;
+}
diff --git a/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs b/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs
index 114d1010a8..4fac7f299b 100644
--- a/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs
+++ b/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs
@@ -996,7 +996,14 @@ private static void GenerateExtensionMethod(StringBuilder sb, AssertionMethodDat
var targetTypeName = data.TargetType.TypeName;
var methodName = data.Method.Name;
var genericParams = data.Method.GenericTypeParameters;
- var isCovariant = data.TargetType.IsCovariantCandidate;
+ // Suppress receiver-type covariance when the source method has its own type
+ // parameters. With covariance, the extension prepends a TActual parameter so a
+ // more-derived static receiver can bind; but the resulting two-parameter signature
+ // cannot accept a call site that names the method's own type arguments explicitly
+ // (e.g. `.MyMethod(...)`) because C# does not allow partial type-argument
+ // specification, so the call fails with CS1929. The dominant call shape supplies
+ // the method's own arguments; a more-derived static receiver can upcast.
+ var isCovariant = data.TargetType.IsCovariantCandidate && genericParams.Count == 0;
// Pick a covariant type param name that doesn't collide with existing generic params
var covariantParam = isCovariant ? CovarianceHelper.GetCovariantTypeParamName(genericParams) : null;
diff --git a/TUnit.Assertions.Tests/GenerateAssertionGenericMethodOnNonSealedReceiverTests.cs b/TUnit.Assertions.Tests/GenerateAssertionGenericMethodOnNonSealedReceiverTests.cs
new file mode 100644
index 0000000000..84bad32931
--- /dev/null
+++ b/TUnit.Assertions.Tests/GenerateAssertionGenericMethodOnNonSealedReceiverTests.cs
@@ -0,0 +1,124 @@
+using System.Threading.Tasks;
+using TUnit.Assertions.Attributes;
+using TUnit.Assertions.Core;
+
+namespace TUnit.Assertions.Tests;
+
+///
+/// Regression coverage for [GenerateAssertion] on a generic source method whose receiver is a
+/// concrete non-sealed reference type. The generator previously prepended a covariant
+/// receiver-type parameter, producing a two-type-parameter extension that no call site
+/// supplying one explicit type argument (e.g. .HasItem<int>(42)) could bind to,
+/// because C# does not permit partial type-argument specification. Control cases isolate the
+/// other variables (sealed receiver, non-generic source, full inference); additional cases
+/// cover multi-parameter generics, interface receivers, async-result returns, constrained
+/// type parameters, and the documented upcast workaround for a more-derived static receiver.
+///
+public class GenerateAssertionGenericMethodOnNonSealedReceiverTests
+{
+ [Test]
+ public async Task GenericMethod_OnNonSealedReceiver_WithExplicitTypeArg()
+ {
+ var container = new NonSealedContainer();
+ await Assert.That(container).HasItem(42);
+ }
+
+ [Test]
+ public async Task GenericMethod_OnSealedReceiver_WithExplicitTypeArg()
+ {
+ var container = new SealedContainer();
+ await Assert.That(container).HasItemSealed(42);
+ }
+
+ [Test]
+ public async Task NonGenericMethod_OnNonSealedReceiver()
+ {
+ var container = new NonSealedContainer();
+ await Assert.That(container).HasInt(42);
+ }
+
+ [Test]
+ public async Task GenericMethod_OnNonSealedReceiver_WithFullInference()
+ {
+ var container = new NonSealedContainer();
+ await Assert.That(container).HasItem(42);
+ }
+
+ [Test]
+ public async Task GenericMethod_WithMultipleTypeParameters_WithExplicitTypeArgs()
+ {
+ var container = new NonSealedContainer();
+ await Assert.That(container).HasPair(42, "x");
+ }
+
+ [Test]
+ public async Task GenericMethod_OnInterfaceReceiver_WithExplicitTypeArg()
+ {
+ IContainerInterface container = new NonSealedContainer();
+ await Assert.That(container).HasInterfaceItem(42);
+ }
+
+ [Test]
+ public async Task GenericMethod_WithConstraint_OnNonSealedReceiver_WithExplicitTypeArg()
+ {
+ var container = new NonSealedContainer();
+ await Assert.That(container).HasParsable("42");
+ }
+
+ [Test]
+ public async Task GenericMethod_AsyncResult_OnNonSealedReceiver_WithExplicitTypeArg()
+ {
+ var container = new NonSealedContainer();
+ await Assert.That(container).HasItemAsync(42);
+ }
+
+ [Test]
+ public async Task GenericMethod_DerivedStaticReceiver_UpcastWorkaround()
+ {
+ var derived = new DerivedNonSealedContainer();
+ await Assert.That((NonSealedContainer)derived).HasItem(42);
+ }
+}
+
+public class NonSealedContainer : IContainerInterface
+{
+}
+
+public class DerivedNonSealedContainer : NonSealedContainer
+{
+}
+
+public sealed class SealedContainer
+{
+}
+
+public interface IContainerInterface
+{
+}
+
+public static partial class GenerateAssertionGenericMethodOnNonSealedReceiverTestExtensions
+{
+ [GenerateAssertion]
+ public static bool HasItem(this NonSealedContainer container, T item) => true;
+
+ [GenerateAssertion]
+ public static bool HasItemSealed(this SealedContainer container, T item) => true;
+
+ [GenerateAssertion]
+ public static bool HasInt(this NonSealedContainer container, int item) => true;
+
+ [GenerateAssertion]
+ public static bool HasPair(this NonSealedContainer container, TFirst first, TSecond second) => true;
+
+ [GenerateAssertion]
+ public static bool HasInterfaceItem(this IContainerInterface container, T item) => true;
+
+ [GenerateAssertion]
+ public static bool HasParsable(this NonSealedContainer container, string text)
+ where T : IParsable
+ => T.TryParse(text, System.Globalization.CultureInfo.InvariantCulture, out _);
+
+ [GenerateAssertion]
+ public static Task HasItemAsync(this NonSealedContainer container, T item)
+ => Task.FromResult(AssertionResult.Passed);
+}