From 776f26599fa2bcbf22fff788be5d3890d3e40558 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 23 Mar 2026 01:45:26 +0000 Subject: [PATCH 1/6] feat: covariant assertions for interfaces and non-sealed classes When the target type of a [GenerateAssertion] or [AssertionExtension] is an interface or non-sealed class, the generated extension method now uses a generic type parameter with a constraint instead of the concrete type: // Before: public static Assertion HasFoo(this IAssertionSource source) // After: public static Assertion HasFoo(this IAssertionSource source) where TActual : IInterface This allows assertions defined for a base type to work seamlessly on derived types without requiring explicit casts. Covariance is applied only when safe: - Interfaces and non-sealed classes: yes - Value types, sealed classes, arrays, type parameters: no - Types containing unresolved type parameters (e.g., Lazy): no Closes #4830 --- ...xceptionAssertions.DotNet10_0.verified.txt | 60 ++++++++---- ...ionResultOfTMethod.DotNet10_0.verified.txt | 6 +- ...ionResultOfTMethod.DotNet10_0.verified.txt | 6 +- .../Generators/AssertionExtensionGenerator.cs | 32 ++++++- .../Generators/MethodAssertionGenerator.cs | 93 +++++++++++++++++-- 5 files changed, 164 insertions(+), 33 deletions(-) diff --git a/TUnit.Assertions.SourceGenerator.Tests/ExceptionAssertionGeneratorTests.GeneratesExceptionAssertions.DotNet10_0.verified.txt b/TUnit.Assertions.SourceGenerator.Tests/ExceptionAssertionGeneratorTests.GeneratesExceptionAssertions.DotNet10_0.verified.txt index 776124a663..4c2caae9f8 100644 --- a/TUnit.Assertions.SourceGenerator.Tests/ExceptionAssertionGeneratorTests.GeneratesExceptionAssertions.DotNet10_0.verified.txt +++ b/TUnit.Assertions.SourceGenerator.Tests/ExceptionAssertionGeneratorTests.GeneratesExceptionAssertions.DotNet10_0.verified.txt @@ -406,93 +406,113 @@ public static partial class ExceptionAssertionExtensions /// /// Generated extension method for HasInnerException /// - public static Exception_HasInnerException_Assertion HasInnerException(this IAssertionSource source) + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")] + public static Exception_HasInnerException_Assertion HasInnerException(this IAssertionSource source) + where TActual : System.Exception { source.Context.ExpressionBuilder.Append(".HasInnerException()"); - return new Exception_HasInnerException_Assertion(source.Context); + return new Exception_HasInnerException_Assertion(source.Context.Map(static x => (System.Exception?)x)); } /// /// Generated extension method for HasNoInnerException /// - public static Exception_HasNoInnerException_Assertion HasNoInnerException(this IAssertionSource source) + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")] + public static Exception_HasNoInnerException_Assertion HasNoInnerException(this IAssertionSource source) + where TActual : System.Exception { source.Context.ExpressionBuilder.Append(".HasNoInnerException()"); - return new Exception_HasNoInnerException_Assertion(source.Context); + return new Exception_HasNoInnerException_Assertion(source.Context.Map(static x => (System.Exception?)x)); } /// /// Generated extension method for HasStackTrace /// - public static Exception_HasStackTrace_Assertion HasStackTrace(this IAssertionSource source) + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")] + public static Exception_HasStackTrace_Assertion HasStackTrace(this IAssertionSource source) + where TActual : System.Exception { source.Context.ExpressionBuilder.Append(".HasStackTrace()"); - return new Exception_HasStackTrace_Assertion(source.Context); + return new Exception_HasStackTrace_Assertion(source.Context.Map(static x => (System.Exception?)x)); } /// /// Generated extension method for HasNoData /// - public static Exception_HasNoData_Assertion HasNoData(this IAssertionSource source) + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")] + public static Exception_HasNoData_Assertion HasNoData(this IAssertionSource source) + where TActual : System.Exception { source.Context.ExpressionBuilder.Append(".HasNoData()"); - return new Exception_HasNoData_Assertion(source.Context); + return new Exception_HasNoData_Assertion(source.Context.Map(static x => (System.Exception?)x)); } /// /// Generated extension method for HasHelpLink /// - public static Exception_HasHelpLink_Assertion HasHelpLink(this IAssertionSource source) + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")] + public static Exception_HasHelpLink_Assertion HasHelpLink(this IAssertionSource source) + where TActual : System.Exception { source.Context.ExpressionBuilder.Append(".HasHelpLink()"); - return new Exception_HasHelpLink_Assertion(source.Context); + return new Exception_HasHelpLink_Assertion(source.Context.Map(static x => (System.Exception?)x)); } /// /// Generated extension method for HasNoHelpLink /// - public static Exception_HasNoHelpLink_Assertion HasNoHelpLink(this IAssertionSource source) + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")] + public static Exception_HasNoHelpLink_Assertion HasNoHelpLink(this IAssertionSource source) + where TActual : System.Exception { source.Context.ExpressionBuilder.Append(".HasNoHelpLink()"); - return new Exception_HasNoHelpLink_Assertion(source.Context); + return new Exception_HasNoHelpLink_Assertion(source.Context.Map(static x => (System.Exception?)x)); } /// /// Generated extension method for HasSource /// - public static Exception_HasSource_Assertion HasSource(this IAssertionSource source) + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")] + public static Exception_HasSource_Assertion HasSource(this IAssertionSource source) + where TActual : System.Exception { source.Context.ExpressionBuilder.Append(".HasSource()"); - return new Exception_HasSource_Assertion(source.Context); + return new Exception_HasSource_Assertion(source.Context.Map(static x => (System.Exception?)x)); } /// /// Generated extension method for HasNoSource /// - public static Exception_HasNoSource_Assertion HasNoSource(this IAssertionSource source) + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")] + public static Exception_HasNoSource_Assertion HasNoSource(this IAssertionSource source) + where TActual : System.Exception { source.Context.ExpressionBuilder.Append(".HasNoSource()"); - return new Exception_HasNoSource_Assertion(source.Context); + return new Exception_HasNoSource_Assertion(source.Context.Map(static x => (System.Exception?)x)); } /// /// Generated extension method for HasTargetSite /// + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")] [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Exception.TargetSite uses reflection which may be trimmed in AOT scenarios")] - public static Exception_HasTargetSite_Assertion HasTargetSite(this IAssertionSource source) + public static Exception_HasTargetSite_Assertion HasTargetSite(this IAssertionSource source) + where TActual : System.Exception { source.Context.ExpressionBuilder.Append(".HasTargetSite()"); - return new Exception_HasTargetSite_Assertion(source.Context); + return new Exception_HasTargetSite_Assertion(source.Context.Map(static x => (System.Exception?)x)); } /// /// Generated extension method for HasNoTargetSite /// + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")] [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Exception.TargetSite uses reflection which may be trimmed in AOT scenarios")] - public static Exception_HasNoTargetSite_Assertion HasNoTargetSite(this IAssertionSource source) + public static Exception_HasNoTargetSite_Assertion HasNoTargetSite(this IAssertionSource source) + where TActual : System.Exception { source.Context.ExpressionBuilder.Append(".HasNoTargetSite()"); - return new Exception_HasNoTargetSite_Assertion(source.Context); + return new Exception_HasNoTargetSite_Assertion(source.Context.Map(static x => (System.Exception?)x)); } } diff --git a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AssertionResultOfTMethod.DotNet10_0.verified.txt b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AssertionResultOfTMethod.DotNet10_0.verified.txt index 56eb54174c..62c15bf931 100644 --- a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AssertionResultOfTMethod.DotNet10_0.verified.txt +++ b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AssertionResultOfTMethod.DotNet10_0.verified.txt @@ -70,10 +70,12 @@ public static partial class AssertionResultOfTMethodExtensions /// /// Generated extension method for ContainsMatch /// - public static IEnumerableString_ContainsMatch_String_Assertion ContainsMatch(this IAssertionSource> source, string needle, [CallerArgumentExpression(nameof(needle))] string? needleExpression = null) + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")] + public static IEnumerableString_ContainsMatch_String_Assertion ContainsMatch(this IAssertionSource source, string needle, [CallerArgumentExpression(nameof(needle))] string? needleExpression = null) + where TActual : System.Collections.Generic.IEnumerable { source.Context.ExpressionBuilder.Append($".ContainsMatch({needleExpression})"); - return new IEnumerableString_ContainsMatch_String_Assertion(source.Context, needle); + return new IEnumerableString_ContainsMatch_String_Assertion(source.Context.Map>(static x => (System.Collections.Generic.IEnumerable?)x), needle); } } diff --git a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AsyncAssertionResultOfTMethod.DotNet10_0.verified.txt b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AsyncAssertionResultOfTMethod.DotNet10_0.verified.txt index 25500147f3..a6a2aa30eb 100644 --- a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AsyncAssertionResultOfTMethod.DotNet10_0.verified.txt +++ b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AsyncAssertionResultOfTMethod.DotNet10_0.verified.txt @@ -70,10 +70,12 @@ public static partial class AsyncAssertionResultOfTMethodExtensions /// /// Generated extension method for ContainsMatchAsync /// - public static IEnumerableString_ContainsMatchAsync_String_Assertion ContainsMatchAsync(this IAssertionSource> source, string needle, [CallerArgumentExpression(nameof(needle))] string? needleExpression = null) + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")] + public static IEnumerableString_ContainsMatchAsync_String_Assertion ContainsMatchAsync(this IAssertionSource source, string needle, [CallerArgumentExpression(nameof(needle))] string? needleExpression = null) + where TActual : System.Collections.Generic.IEnumerable { source.Context.ExpressionBuilder.Append($".ContainsMatchAsync({needleExpression})"); - return new IEnumerableString_ContainsMatchAsync_String_Assertion(source.Context, needle); + return new IEnumerableString_ContainsMatchAsync_String_Assertion(source.Context.Map>(static x => (System.Collections.Generic.IEnumerable?)x), needle); } } diff --git a/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs b/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs index 5778c0cfc8..257d2dcaab 100644 --- a/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs +++ b/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs @@ -360,8 +360,14 @@ private static void GenerateExtensionMethod( // The extension method extends IAssertionSource where T is the type argument // from the Assertion base class. string sourceType; - string genericTypeParam = null; - string genericConstraint = null; + string? genericTypeParam = null; + string? genericConstraint = null; + + // Determine if the target type supports covariant assertions + // Covariance applies to interfaces and non-sealed classes only + var isCovariantCandidate = !isNullableOverload + && (typeParam.TypeKind == TypeKind.Interface || typeParam.TypeKind == TypeKind.Class) + && !typeParam.IsSealed; if (isNullableOverload) { @@ -376,6 +382,19 @@ private static void GenerateExtensionMethod( { sourceType = $"IAssertionSource<{baseTypeParam.Name}>"; } + else if (isCovariantCandidate) + { + // For covariant assertions, use a generic TActual with a type constraint + var typeParamDisplay = typeParam.ToDisplayString(); + var constraintType = typeParamDisplay; + if (typeParam.NullableAnnotation == NullableAnnotation.Annotated && typeParamDisplay.EndsWith("?")) + { + constraintType = typeParamDisplay.Substring(0, typeParamDisplay.Length - 1); + } + sourceType = "IAssertionSource"; + genericTypeParam = "TActual"; + genericConstraint = $"where TActual : {constraintType}"; + } else { sourceType = $"IAssertionSource<{typeParam.ToDisplayString()}>"; @@ -465,6 +484,15 @@ private static void GenerateExtensionMethod( { sourceBuilder.Append("source.Context.AsNullable()"); } + else if (isCovariantCandidate) + { + // Map the context from TActual to the target type + // Use nullable cast since Map's Func takes TValue? and returns TNew? + // Explicitly specify TNew to preserve nullability (e.g., JsonNode? vs JsonNode) + var typeParamDisplay = typeParam.ToDisplayString(); + var nullableCastType = typeParamDisplay.EndsWith("?") ? typeParamDisplay : $"{typeParamDisplay}?"; + sourceBuilder.Append($"source.Context.Map<{typeParamDisplay}>(static x => ({nullableCastType})x)"); + } else { sourceBuilder.Append("source.Context"); diff --git a/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs b/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs index b88b7bda8e..5a39ee4173 100644 --- a/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs +++ b/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs @@ -312,12 +312,31 @@ private static (AssertionMethodData? Data, Diagnostic? Diagnostic) GetAssertionM ContainingType = containingTypeData, }; + // Determine if the target type supports covariant assertions + // Covariance applies to interfaces and non-sealed classes only + // Excludes: value types, sealed classes, type parameters, arrays, error types, + // and types containing unresolved type parameters (e.g., Lazy) since adding TActual + // would break type inference - the compiler can't infer T from IAssertionSource + var isCovariantCandidate = (targetType.TypeKind == TypeKind.Interface || targetType.TypeKind == TypeKind.Class) + && !targetType.IsSealed + && !ContainsTypeParameter(targetType); + + // For constraints, we need the non-nullable form of the type name + var typeName = targetType.ToDisplayString(); + var constraintTypeName = typeName; + if (isCovariantCandidate && targetType.NullableAnnotation == NullableAnnotation.Annotated && typeName.EndsWith("?")) + { + constraintTypeName = typeName.Substring(0, typeName.Length - 1); + } + var targetTypeData = new TargetTypeData() { - TypeName = targetType.ToDisplayString(), + TypeName = typeName, SimpleTypeName = GetSimpleTypeName(targetType), IsNullable = targetType.IsReferenceType || targetType.NullableAnnotation == NullableAnnotation.Annotated, + IsCovariantCandidate = isCovariantCandidate, + ConstraintTypeName = constraintTypeName, }; var data = new AssertionMethodData( @@ -962,7 +981,19 @@ private static void GenerateExtensionMethod(StringBuilder sb, AssertionMethodDat var targetTypeName = data.TargetType.TypeName; var methodName = data.Method.Name; var genericParams = data.Method.GenericTypeParameters; - var genericDeclaration = genericParams.Count > 0 ? $"<{string.Join(", ", genericParams)}>" : ""; + var isCovariant = data.TargetType.IsCovariantCandidate; + + // Build generic declaration - prepend TActual for covariant assertions + var allGenericParams = new List(); + if (isCovariant) + { + allGenericParams.Add("TActual"); + } + allGenericParams.AddRange(genericParams); + var genericDeclaration = allGenericParams.Count > 0 ? $"<{string.Join(", ", allGenericParams)}>" : ""; + + // For the return type, we only need the method's own generic params (not TActual) + var returnGenericDeclaration = genericParams.Count > 0 ? $"<{string.Join(", ", genericParams)}>" : ""; // Collect generic constraints from the method var genericConstraints = data.Method.GenericConstraints; @@ -973,7 +1004,7 @@ private static void GenerateExtensionMethod(StringBuilder sb, AssertionMethodDat sb.AppendLine(" /// "); // Add suppression for generic types to avoid trimming warnings - if (genericParams.Count > 0) + if (allGenericParams.Count > 0) { sb.AppendLine($" [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage(\"Trimming\", \"IL2091\", Justification = \"Generic type parameter is only used for property access, not instantiation\")]"); } @@ -988,8 +1019,11 @@ private static void GenerateExtensionMethod(StringBuilder sb, AssertionMethodDat } // Method signature - sb.Append($" public static {className}{genericDeclaration} {methodName}{genericDeclaration}("); - sb.Append($"this IAssertionSource<{targetTypeName}> source"); + sb.Append($" public static {className}{returnGenericDeclaration} {methodName}{genericDeclaration}("); + + // For covariant assertions, use IAssertionSource with a type constraint + var sourceTypeName = isCovariant ? "TActual" : targetTypeName; + sb.Append($"this IAssertionSource<{sourceTypeName}> source"); // Additional parameters foreach (var param in data.AdditionalParameters) @@ -1014,6 +1048,12 @@ private static void GenerateExtensionMethod(StringBuilder sb, AssertionMethodDat sb.AppendLine(")"); + // Apply covariant type constraint + if (isCovariant) + { + sb.AppendLine($" where TActual : {data.TargetType.ConstraintTypeName}"); + } + // Apply generic constraints if present if (genericConstraints.Count > 0) { @@ -1039,8 +1079,21 @@ private static void GenerateExtensionMethod(StringBuilder sb, AssertionMethodDat } // Construct and return assertion + // For covariant assertions, map the context from TActual to the target type // Note: Ref struct parameters (like interpolated string handlers) are converted to string - sb.Append($" return new {className}{genericDeclaration}(source.Context"); + string contextExpr; + if (isCovariant) + { + // Use nullable cast since Map's Func takes TValue? and returns TNew? + // Explicitly specify TNew to preserve nullability (e.g., JsonNode? vs JsonNode) + var nullableCastType = targetTypeName.EndsWith("?") ? targetTypeName : $"{targetTypeName}?"; + contextExpr = $"source.Context.Map<{targetTypeName}>(static x => ({nullableCastType})x)"; + } + else + { + contextExpr = "source.Context"; + } + sb.Append($" return new {className}{returnGenericDeclaration}({contextExpr}"); foreach (var param in data.AdditionalParameters) { if (param.IsRefStruct) @@ -1062,6 +1115,32 @@ private static void GenerateExtensionMethod(StringBuilder sb, AssertionMethodDat sb.AppendLine(" }"); } + private static bool ContainsTypeParameter(ITypeSymbol type) + { + if (type is ITypeParameterSymbol) + { + return true; + } + + if (type is INamedTypeSymbol namedType) + { + foreach (var typeArg in namedType.TypeArguments) + { + if (ContainsTypeParameter(typeArg)) + { + return true; + } + } + } + + if (type is IArrayTypeSymbol arrayType) + { + return ContainsTypeParameter(arrayType.ElementType); + } + + return false; + } + private static string GenerateClassName(AssertionMethodData data) { var methodName = data.Method.Name; @@ -1311,7 +1390,7 @@ private record ContainingTypeData( string ContainingNamespace ); - private record struct TargetTypeData(string TypeName, string SimpleTypeName, bool IsNullable); + private record struct TargetTypeData(string TypeName, string SimpleTypeName, bool IsNullable, bool IsCovariantCandidate, string ConstraintTypeName); private record struct MethodData( string Name, From 4da27a18283443f1f969668b136710af1e0782af Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 23 Mar 2026 01:52:15 +0000 Subject: [PATCH 2/6] refactor: extract shared CovarianceHelper, fix missing ContainsTypeParameter check - Extract duplicated covariance logic into CovarianceHelper static class - Fix bug: AssertionExtensionGenerator was missing ContainsTypeParameter check, which would incorrectly treat types like Lazy as covariant - Remove dead code: unused isMultiParameterGeneric and isNullableReferenceType - Eliminate unnecessary List allocation in non-covariant path - Remove redundant typeParamDisplay computation in AssertionExtensionGenerator --- .../Generators/AssertionExtensionGenerator.cs | 31 +------ .../Generators/CovarianceHelper.cs | 80 +++++++++++++++++++ .../Generators/MethodAssertionGenerator.cs | 76 +++--------------- 3 files changed, 96 insertions(+), 91 deletions(-) create mode 100644 TUnit.Assertions.SourceGenerator/Generators/CovarianceHelper.cs diff --git a/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs b/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs index 257d2dcaab..6580dcbdca 100644 --- a/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs +++ b/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs @@ -170,11 +170,6 @@ private static void GenerateExtensionMethods(SourceProductionContext context, As continue; } - // Check if the type parameter is a nullable reference type (e.g., string?) - var typeParam = data.AssertionBaseType.TypeArguments[0]; - var isNullableReferenceType = typeParam.NullableAnnotation == NullableAnnotation.Annotated && - typeParam.IsReferenceType; - // Generate positive assertion method GenerateExtensionMethod(sourceBuilder, data, constructor, negated: false, isNullableOverload: false); @@ -242,15 +237,9 @@ private static void GenerateExtensionMethod( requiresUnreferencedCodeMessage = data.RequiresUnreferencedCodeMessage; } - // Build generic type parameters string - // Use the assertion class's own type parameters if it has them var genericParams = new List(); var typeConstraints = new List(); - // NEW: Detect if this is a multi-parameter generic assertion (e.g., collection assertions) - // Check if the assertion class has multiple type parameters beyond just Assertion - var isMultiParameterGeneric = assertionType.TypeParameters.Length > 1; - if (assertionType.IsGenericType && assertionType.TypeParameters.Length > 0) { // The assertion class defines its own generic type parameters @@ -363,11 +352,8 @@ private static void GenerateExtensionMethod( string? genericTypeParam = null; string? genericConstraint = null; - // Determine if the target type supports covariant assertions - // Covariance applies to interfaces and non-sealed classes only var isCovariantCandidate = !isNullableOverload - && (typeParam.TypeKind == TypeKind.Interface || typeParam.TypeKind == TypeKind.Class) - && !typeParam.IsSealed; + && CovarianceHelper.IsCovariantCandidate(typeParam); if (isNullableOverload) { @@ -384,16 +370,10 @@ private static void GenerateExtensionMethod( } else if (isCovariantCandidate) { - // For covariant assertions, use a generic TActual with a type constraint var typeParamDisplay = typeParam.ToDisplayString(); - var constraintType = typeParamDisplay; - if (typeParam.NullableAnnotation == NullableAnnotation.Annotated && typeParamDisplay.EndsWith("?")) - { - constraintType = typeParamDisplay.Substring(0, typeParamDisplay.Length - 1); - } sourceType = "IAssertionSource"; genericTypeParam = "TActual"; - genericConstraint = $"where TActual : {constraintType}"; + genericConstraint = $"where TActual : {CovarianceHelper.GetConstraintTypeName(typeParamDisplay, typeParam)}"; } else { @@ -486,12 +466,7 @@ private static void GenerateExtensionMethod( } else if (isCovariantCandidate) { - // Map the context from TActual to the target type - // Use nullable cast since Map's Func takes TValue? and returns TNew? - // Explicitly specify TNew to preserve nullability (e.g., JsonNode? vs JsonNode) - var typeParamDisplay = typeParam.ToDisplayString(); - var nullableCastType = typeParamDisplay.EndsWith("?") ? typeParamDisplay : $"{typeParamDisplay}?"; - sourceBuilder.Append($"source.Context.Map<{typeParamDisplay}>(static x => ({nullableCastType})x)"); + sourceBuilder.Append(CovarianceHelper.GetCovariantContextExpr(typeParam.ToDisplayString())); } else { diff --git a/TUnit.Assertions.SourceGenerator/Generators/CovarianceHelper.cs b/TUnit.Assertions.SourceGenerator/Generators/CovarianceHelper.cs new file mode 100644 index 0000000000..0567b4ea2b --- /dev/null +++ b/TUnit.Assertions.SourceGenerator/Generators/CovarianceHelper.cs @@ -0,0 +1,80 @@ +using Microsoft.CodeAnalysis; + +namespace TUnit.Assertions.SourceGenerator.Generators; + +/// +/// Shared helpers for covariant assertion generation across MethodAssertionGenerator and AssertionExtensionGenerator. +/// +internal static class CovarianceHelper +{ + /// + /// Determines if a target type supports covariant assertions. + /// Returns true for interfaces and non-sealed classes that don't contain unresolved type parameters. + /// + public static bool IsCovariantCandidate(ITypeSymbol type) + { + return (type.TypeKind == TypeKind.Interface || type.TypeKind == TypeKind.Class) + && !type.IsSealed + && !ContainsTypeParameter(type); + } + + /// + /// Returns the type name suitable for use in a generic constraint (strips trailing nullable annotation). + /// + public static string GetConstraintTypeName(string typeName, ITypeSymbol type) + { + if (type.NullableAnnotation == NullableAnnotation.Annotated && typeName.EndsWith("?")) + { + return typeName.Substring(0, typeName.Length - 1); + } + return typeName; + } + + /// + /// Returns the nullable cast form of a type name for use in Map lambdas. + /// Map's Func takes TValue? and returns TNew?, so the cast must be to the nullable form. + /// + public static string GetNullableCastType(string typeName) + { + return typeName.EndsWith("?") ? typeName : $"{typeName}?"; + } + + /// + /// Generates the context mapping expression for covariant assertions. + /// + public static string GetCovariantContextExpr(string targetTypeName) + { + var nullableCastType = GetNullableCastType(targetTypeName); + return $"source.Context.Map<{targetTypeName}>(static x => ({nullableCastType})x)"; + } + + /// + /// Recursively checks if a type symbol contains any unresolved type parameters. + /// Types like Lazy<T> would break type inference if made covariant. + /// + public static bool ContainsTypeParameter(ITypeSymbol type) + { + if (type is ITypeParameterSymbol) + { + return true; + } + + if (type is INamedTypeSymbol namedType) + { + foreach (var typeArg in namedType.TypeArguments) + { + if (ContainsTypeParameter(typeArg)) + { + return true; + } + } + } + + if (type is IArrayTypeSymbol arrayType) + { + return ContainsTypeParameter(arrayType.ElementType); + } + + return false; + } +} diff --git a/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs b/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs index 5a39ee4173..23494b4a95 100644 --- a/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs +++ b/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs @@ -312,22 +312,8 @@ private static (AssertionMethodData? Data, Diagnostic? Diagnostic) GetAssertionM ContainingType = containingTypeData, }; - // Determine if the target type supports covariant assertions - // Covariance applies to interfaces and non-sealed classes only - // Excludes: value types, sealed classes, type parameters, arrays, error types, - // and types containing unresolved type parameters (e.g., Lazy) since adding TActual - // would break type inference - the compiler can't infer T from IAssertionSource - var isCovariantCandidate = (targetType.TypeKind == TypeKind.Interface || targetType.TypeKind == TypeKind.Class) - && !targetType.IsSealed - && !ContainsTypeParameter(targetType); - - // For constraints, we need the non-nullable form of the type name + var isCovariantCandidate = CovarianceHelper.IsCovariantCandidate(targetType); var typeName = targetType.ToDisplayString(); - var constraintTypeName = typeName; - if (isCovariantCandidate && targetType.NullableAnnotation == NullableAnnotation.Annotated && typeName.EndsWith("?")) - { - constraintTypeName = typeName.Substring(0, typeName.Length - 1); - } var targetTypeData = new TargetTypeData() { @@ -336,7 +322,7 @@ private static (AssertionMethodData? Data, Diagnostic? Diagnostic) GetAssertionM IsNullable = targetType.IsReferenceType || targetType.NullableAnnotation == NullableAnnotation.Annotated, IsCovariantCandidate = isCovariantCandidate, - ConstraintTypeName = constraintTypeName, + ConstraintTypeName = isCovariantCandidate ? CovarianceHelper.GetConstraintTypeName(typeName, targetType) : typeName, }; var data = new AssertionMethodData( @@ -984,13 +970,13 @@ private static void GenerateExtensionMethod(StringBuilder sb, AssertionMethodDat var isCovariant = data.TargetType.IsCovariantCandidate; // Build generic declaration - prepend TActual for covariant assertions - var allGenericParams = new List(); - if (isCovariant) - { - allGenericParams.Add("TActual"); - } - allGenericParams.AddRange(genericParams); - var genericDeclaration = allGenericParams.Count > 0 ? $"<{string.Join(", ", allGenericParams)}>" : ""; + var genericDeclaration = isCovariant + ? genericParams.Count > 0 + ? $"" + : "" + : genericParams.Count > 0 + ? $"<{string.Join(", ", genericParams)}>" + : ""; // For the return type, we only need the method's own generic params (not TActual) var returnGenericDeclaration = genericParams.Count > 0 ? $"<{string.Join(", ", genericParams)}>" : ""; @@ -1004,7 +990,7 @@ private static void GenerateExtensionMethod(StringBuilder sb, AssertionMethodDat sb.AppendLine(" /// "); // Add suppression for generic types to avoid trimming warnings - if (allGenericParams.Count > 0) + if (isCovariant || genericParams.Count > 0) { sb.AppendLine($" [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage(\"Trimming\", \"IL2091\", Justification = \"Generic type parameter is only used for property access, not instantiation\")]"); } @@ -1079,20 +1065,10 @@ private static void GenerateExtensionMethod(StringBuilder sb, AssertionMethodDat } // Construct and return assertion - // For covariant assertions, map the context from TActual to the target type // Note: Ref struct parameters (like interpolated string handlers) are converted to string - string contextExpr; - if (isCovariant) - { - // Use nullable cast since Map's Func takes TValue? and returns TNew? - // Explicitly specify TNew to preserve nullability (e.g., JsonNode? vs JsonNode) - var nullableCastType = targetTypeName.EndsWith("?") ? targetTypeName : $"{targetTypeName}?"; - contextExpr = $"source.Context.Map<{targetTypeName}>(static x => ({nullableCastType})x)"; - } - else - { - contextExpr = "source.Context"; - } + var contextExpr = isCovariant + ? CovarianceHelper.GetCovariantContextExpr(targetTypeName) + : "source.Context"; sb.Append($" return new {className}{returnGenericDeclaration}({contextExpr}"); foreach (var param in data.AdditionalParameters) { @@ -1115,32 +1091,6 @@ private static void GenerateExtensionMethod(StringBuilder sb, AssertionMethodDat sb.AppendLine(" }"); } - private static bool ContainsTypeParameter(ITypeSymbol type) - { - if (type is ITypeParameterSymbol) - { - return true; - } - - if (type is INamedTypeSymbol namedType) - { - foreach (var typeArg in namedType.TypeArguments) - { - if (ContainsTypeParameter(typeArg)) - { - return true; - } - } - } - - if (type is IArrayTypeSymbol arrayType) - { - return ContainsTypeParameter(arrayType.ElementType); - } - - return false; - } - private static string GenerateClassName(AssertionMethodData data) { var methodName = data.Method.Name; From ff70b549d6e9ff644af4eda02722491a58784269 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 23 Mar 2026 01:54:56 +0000 Subject: [PATCH 3/6] fix: avoid TActual name collision with existing generic parameters Use CovarianceHelper.GetCovariantTypeParamName() to pick a type parameter name that doesn't conflict with the method's existing generic parameters. Falls back to TActual_ (with appended underscores) if TActual is taken. --- .../Generators/AssertionExtensionGenerator.cs | 7 +++--- .../Generators/CovarianceHelper.cs | 25 +++++++++++++++++++ .../Generators/MethodAssertionGenerator.cs | 14 ++++++----- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs b/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs index 6580dcbdca..7e98fb8277 100644 --- a/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs +++ b/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs @@ -371,9 +371,10 @@ private static void GenerateExtensionMethod( else if (isCovariantCandidate) { var typeParamDisplay = typeParam.ToDisplayString(); - sourceType = "IAssertionSource"; - genericTypeParam = "TActual"; - genericConstraint = $"where TActual : {CovarianceHelper.GetConstraintTypeName(typeParamDisplay, typeParam)}"; + var covariantParam = CovarianceHelper.GetCovariantTypeParamName(genericParams); + sourceType = $"IAssertionSource<{covariantParam}>"; + genericTypeParam = covariantParam; + genericConstraint = $"where {covariantParam} : {CovarianceHelper.GetConstraintTypeName(typeParamDisplay, typeParam)}"; } else { diff --git a/TUnit.Assertions.SourceGenerator/Generators/CovarianceHelper.cs b/TUnit.Assertions.SourceGenerator/Generators/CovarianceHelper.cs index 0567b4ea2b..7d0c0eb6bb 100644 --- a/TUnit.Assertions.SourceGenerator/Generators/CovarianceHelper.cs +++ b/TUnit.Assertions.SourceGenerator/Generators/CovarianceHelper.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Linq; using Microsoft.CodeAnalysis; namespace TUnit.Assertions.SourceGenerator.Generators; @@ -7,6 +9,29 @@ namespace TUnit.Assertions.SourceGenerator.Generators; /// internal static class CovarianceHelper { + private const string PreferredName = "TActual"; + + /// + /// Returns a type parameter name for the covariant source type that doesn't conflict + /// with any existing generic parameters on the method. + /// + public static string GetCovariantTypeParamName(IEnumerable existingGenericParams) + { + var existing = new HashSet(existingGenericParams); + if (!existing.Contains(PreferredName)) + { + return PreferredName; + } + + // Append underscores until unique + var candidate = PreferredName + "_"; + while (existing.Contains(candidate)) + { + candidate += "_"; + } + return candidate; + } + /// /// Determines if a target type supports covariant assertions. /// Returns true for interfaces and non-sealed classes that don't contain unresolved type parameters. diff --git a/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs b/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs index 23494b4a95..f66b278773 100644 --- a/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs +++ b/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs @@ -969,11 +969,14 @@ private static void GenerateExtensionMethod(StringBuilder sb, AssertionMethodDat var genericParams = data.Method.GenericTypeParameters; var isCovariant = data.TargetType.IsCovariantCandidate; - // Build generic declaration - prepend TActual for covariant assertions + // Pick a covariant type param name that doesn't collide with existing generic params + var covariantParam = isCovariant ? CovarianceHelper.GetCovariantTypeParamName(genericParams) : null; + + // Build generic declaration - prepend covariant param for covariant assertions var genericDeclaration = isCovariant ? genericParams.Count > 0 - ? $"" - : "" + ? $"<{covariantParam}, {string.Join(", ", genericParams)}>" + : $"<{covariantParam}>" : genericParams.Count > 0 ? $"<{string.Join(", ", genericParams)}>" : ""; @@ -1007,8 +1010,7 @@ private static void GenerateExtensionMethod(StringBuilder sb, AssertionMethodDat // Method signature sb.Append($" public static {className}{returnGenericDeclaration} {methodName}{genericDeclaration}("); - // For covariant assertions, use IAssertionSource with a type constraint - var sourceTypeName = isCovariant ? "TActual" : targetTypeName; + var sourceTypeName = isCovariant ? covariantParam! : targetTypeName; sb.Append($"this IAssertionSource<{sourceTypeName}> source"); // Additional parameters @@ -1037,7 +1039,7 @@ private static void GenerateExtensionMethod(StringBuilder sb, AssertionMethodDat // Apply covariant type constraint if (isCovariant) { - sb.AppendLine($" where TActual : {data.TargetType.ConstraintTypeName}"); + sb.AppendLine($" where {covariantParam} : {data.TargetType.ConstraintTypeName}"); } // Apply generic constraints if present From aeb032dfd95b6a1fa3d6f265633151e96b337769 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 23 Mar 2026 01:57:24 +0000 Subject: [PATCH 4/6] chore: update public API snapshots for covariant assertion signatures --- ...Has_No_API_Changes.DotNet10_0.verified.txt | 212 +++++++++++++----- ..._Has_No_API_Changes.DotNet8_0.verified.txt | 212 +++++++++++++----- ..._Has_No_API_Changes.DotNet9_0.verified.txt | 212 +++++++++++++----- ...ary_Has_No_API_Changes.Net4_7.verified.txt | 153 ++++++++----- 4 files changed, 579 insertions(+), 210 deletions(-) diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt index 1f7f9729c5..3c92472c75 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt @@ -2563,8 +2563,12 @@ namespace .Extensions public static . IsNotCollectible(this .<.Assembly> source) { } public static . IsNotDynamic(this .<.Assembly> source) { } public static . IsNotFullyTrusted(this .<.Assembly> source) { } - public static ._IsNotSigned_Assertion IsNotSigned(this .<.Assembly> source) { } - public static ._IsSigned_Assertion IsSigned(this .<.Assembly> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotSigned_Assertion IsNotSigned(this . source) + where TActual : .Assembly { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsSigned_Assertion IsSigned(this . source) + where TActual : .Assembly { } } public class AssemblyIsCollectibleAssertion : .<.Assembly> { @@ -3057,15 +3061,27 @@ namespace .Extensions } public static class CultureInfoAssertionExtensions { - public static ._IsEnglish_Assertion IsEnglish(this .<.CultureInfo> source) { } - public static ._IsInvariant_Assertion IsInvariant(this .<.CultureInfo> source) { } - public static ._IsLeftToRight_Assertion IsLeftToRight(this .<.CultureInfo> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsEnglish_Assertion IsEnglish(this . source) + where TActual : .CultureInfo { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsInvariant_Assertion IsInvariant(this . source) + where TActual : .CultureInfo { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsLeftToRight_Assertion IsLeftToRight(this . source) + where TActual : .CultureInfo { } public static . IsNeutralCulture(this .<.CultureInfo> source) { } - public static ._IsNotEnglish_Assertion IsNotEnglish(this .<.CultureInfo> source) { } - public static ._IsNotInvariant_Assertion IsNotInvariant(this .<.CultureInfo> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotEnglish_Assertion IsNotEnglish(this . source) + where TActual : .CultureInfo { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotInvariant_Assertion IsNotInvariant(this . source) + where TActual : .CultureInfo { } public static . IsNotNeutralCulture(this .<.CultureInfo> source) { } public static . IsReadOnly(this .<.CultureInfo> source) { } - public static ._IsRightToLeft_Assertion IsRightToLeft(this .<.CultureInfo> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsRightToLeft_Assertion IsRightToLeft(this . source) + where TActual : .CultureInfo { } } public class CultureInfoIsNeutralCultureAssertion : .<.CultureInfo> { @@ -3631,14 +3647,26 @@ namespace .Extensions } public static class EncodingAssertionExtensions { - public static ._IsASCII_Assertion IsASCII(this .<.Encoding> source) { } - public static ._IsBigEndianUnicode_Assertion IsBigEndianUnicode(this .<.Encoding> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsASCII_Assertion IsASCII(this . source) + where TActual : .Encoding { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsBigEndianUnicode_Assertion IsBigEndianUnicode(this . source) + where TActual : .Encoding { } public static . IsNotSingleByte(this .<.Encoding> source) { } - public static ._IsNotUTF8_Assertion IsNotUTF8(this .<.Encoding> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotUTF8_Assertion IsNotUTF8(this . source) + where TActual : .Encoding { } public static . IsSingleByte(this .<.Encoding> source) { } - public static ._IsUTF32_Assertion IsUTF32(this .<.Encoding> source) { } - public static ._IsUTF8_Assertion IsUTF8(this .<.Encoding> source) { } - public static ._IsUnicode_Assertion IsUnicode(this .<.Encoding> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsUTF32_Assertion IsUTF32(this . source) + where TActual : .Encoding { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsUTF8_Assertion IsUTF8(this . source) + where TActual : .Encoding { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsUnicode_Assertion IsUnicode(this . source) + where TActual : .Encoding { } } public class EncodingIsSingleByteAssertion : .<.Encoding> { @@ -3715,18 +3743,38 @@ namespace .Extensions } public static class ExceptionAssertionExtensions { - public static ._HasHelpLink_Assertion HasHelpLink(this .<> source) { } - public static ._HasInnerException_Assertion HasInnerException(this .<> source) { } - public static ._HasNoData_Assertion HasNoData(this .<> source) { } - public static ._HasNoHelpLink_Assertion HasNoHelpLink(this .<> source) { } - public static ._HasNoInnerException_Assertion HasNoInnerException(this .<> source) { } - public static ._HasNoSource_Assertion HasNoSource(this .<> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasHelpLink_Assertion HasHelpLink(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasInnerException_Assertion HasInnerException(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasNoData_Assertion HasNoData(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasNoHelpLink_Assertion HasNoHelpLink(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasNoInnerException_Assertion HasNoInnerException(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasNoSource_Assertion HasNoSource(this . source) + where TActual : { } [.(" uses reflection which may be trimmed in AOT scenarios")] - public static ._HasNoTargetSite_Assertion HasNoTargetSite(this .<> source) { } - public static ._HasSource_Assertion HasSource(this .<> source) { } - public static ._HasStackTrace_Assertion HasStackTrace(this .<> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasNoTargetSite_Assertion HasNoTargetSite(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasSource_Assertion HasSource(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasStackTrace_Assertion HasStackTrace(this . source) + where TActual : { } [.(" uses reflection which may be trimmed in AOT scenarios")] - public static ._HasTargetSite_Assertion HasTargetSite(this .<> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasTargetSite_Assertion HasTargetSite(this . source) + where TActual : { } } public sealed class Exception_HasHelpLink_Assertion : .<> { @@ -4007,23 +4055,53 @@ namespace .Extensions } public static class HttpResponseMessageAssertionExtensions { - public static ._HasContentType_String_Assertion HasContentType(this .<.> source, string contentType, [.("contentType")] string? contentTypeExpression = null) { } - public static ._HasHeader_String_Assertion HasHeader(this .<.> source, string headerName, [.("headerName")] string? headerNameExpression = null) { } - public static ._HasJsonContent_Assertion HasJsonContent(this .<.> source) { } - public static ._HasStatusCode_HttpStatusCode_Assertion HasStatusCode(this .<.> source, .HttpStatusCode statusCode, [.("statusCode")] string? statusCodeExpression = null) { } - public static ._IsBadRequest_Assertion IsBadRequest(this .<.> source) { } - public static ._IsClientErrorStatusCode_Assertion IsClientErrorStatusCode(this .<.> source) { } - public static ._IsConflict_Assertion IsConflict(this .<.> source) { } - public static ._IsCreated_Assertion IsCreated(this .<.> source) { } - public static ._IsForbidden_Assertion IsForbidden(this .<.> source) { } - public static ._IsNoContent_Assertion IsNoContent(this .<.> source) { } - public static ._IsNotFound_Assertion IsNotFound(this .<.> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasContentType_String_Assertion HasContentType(this . source, string contentType, [.("contentType")] string? contentTypeExpression = null) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasHeader_String_Assertion HasHeader(this . source, string headerName, [.("headerName")] string? headerNameExpression = null) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasJsonContent_Assertion HasJsonContent(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasStatusCode_HttpStatusCode_Assertion HasStatusCode(this . source, .HttpStatusCode statusCode, [.("statusCode")] string? statusCodeExpression = null) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsBadRequest_Assertion IsBadRequest(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsClientErrorStatusCode_Assertion IsClientErrorStatusCode(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsConflict_Assertion IsConflict(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsCreated_Assertion IsCreated(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsForbidden_Assertion IsForbidden(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNoContent_Assertion IsNoContent(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotFound_Assertion IsNotFound(this . source) + where TActual : . { } public static . IsNotSuccessStatusCode(this .<.> source) { } - public static ._IsOk_Assertion IsOk(this .<.> source) { } - public static ._IsRedirectStatusCode_Assertion IsRedirectStatusCode(this .<.> source) { } - public static ._IsServerErrorStatusCode_Assertion IsServerErrorStatusCode(this .<.> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsOk_Assertion IsOk(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsRedirectStatusCode_Assertion IsRedirectStatusCode(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsServerErrorStatusCode_Assertion IsServerErrorStatusCode(this . source) + where TActual : . { } public static . IsSuccessStatusCode(this .<.> source) { } - public static ._IsUnauthorized_Assertion IsUnauthorized(this .<.> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsUnauthorized_Assertion IsUnauthorized(this . source) + where TActual : . { } } public class HttpResponseMessageIsSuccessStatusCodeAssertion : .<.> { @@ -4436,16 +4514,36 @@ namespace .Extensions } public static class JsonNodeAssertionExtensions { - public static ._DoesNotHaveJsonProperty_String_Assertion DoesNotHaveJsonProperty(this .<..JsonNode?> source, string propertyName, [.("propertyName")] string? propertyNameExpression = null) { } - public static ._HasJsonArrayCount_Int_Assertion HasJsonArrayCount(this .<..JsonNode?> source, int expected, [.("expected")] string? expectedExpression = null) { } - public static ._HasJsonProperty_String_Assertion HasJsonProperty(this .<..JsonNode?> source, string propertyName, [.("propertyName")] string? propertyNameExpression = null) { } - public static ._IsDeepEqualTo_JsonNode_Assertion IsDeepEqualTo(this .<..JsonNode?> source, ..JsonNode? expected, [.("expected")] string? expectedExpression = null) { } - public static ._IsJsonArray_Assertion IsJsonArray(this .<..JsonNode?> source) { } - public static ._IsJsonArrayEmpty_Assertion IsJsonArrayEmpty(this .<..JsonNode?> source) { } - public static ._IsJsonArrayNotEmpty_Assertion IsJsonArrayNotEmpty(this .<..JsonNode?> source) { } - public static ._IsJsonObject_Assertion IsJsonObject(this .<..JsonNode?> source) { } - public static ._IsJsonValue_Assertion IsJsonValue(this .<..JsonNode?> source) { } - public static ._IsNotDeepEqualTo_JsonNode_Assertion IsNotDeepEqualTo(this .<..JsonNode?> source, ..JsonNode? expected, [.("expected")] string? expectedExpression = null) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._DoesNotHaveJsonProperty_String_Assertion DoesNotHaveJsonProperty(this . source, string propertyName, [.("propertyName")] string? propertyNameExpression = null) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasJsonArrayCount_Int_Assertion HasJsonArrayCount(this . source, int expected, [.("expected")] string? expectedExpression = null) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasJsonProperty_String_Assertion HasJsonProperty(this . source, string propertyName, [.("propertyName")] string? propertyNameExpression = null) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsDeepEqualTo_JsonNode_Assertion IsDeepEqualTo(this . source, ..JsonNode? expected, [.("expected")] string? expectedExpression = null) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsJsonArray_Assertion IsJsonArray(this . source) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsJsonArrayEmpty_Assertion IsJsonArrayEmpty(this . source) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsJsonArrayNotEmpty_Assertion IsJsonArrayNotEmpty(this . source) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsJsonObject_Assertion IsJsonObject(this . source) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsJsonValue_Assertion IsJsonValue(this . source) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotDeepEqualTo_JsonNode_Assertion IsNotDeepEqualTo(this . source, ..JsonNode? expected, [.("expected")] string? expectedExpression = null) + where TActual : ..JsonNode { } } public sealed class JsonNode_DoesNotHaveJsonProperty_String_Assertion : .<..JsonNode?> { @@ -4885,10 +4983,18 @@ namespace .Extensions public static . CannotSeek(this .<.Stream> source) { } public static . CannotTimeout(this .<.Stream> source) { } public static . CannotWrite(this .<.Stream> source) { } - public static ._IsAtEnd_Assertion IsAtEnd(this .<.Stream> source) { } - public static ._IsAtStart_Assertion IsAtStart(this .<.Stream> source) { } - public static ._IsEmpty_Assertion IsEmpty(this .<.Stream> source) { } - public static ._IsNotEmpty_Assertion IsNotEmpty(this .<.Stream> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsAtEnd_Assertion IsAtEnd(this . source) + where TActual : .Stream { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsAtStart_Assertion IsAtStart(this . source) + where TActual : .Stream { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsEmpty_Assertion IsEmpty(this . source) + where TActual : .Stream { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotEmpty_Assertion IsNotEmpty(this . source) + where TActual : .Stream { } } public class StreamCanReadAssertion : .<.Stream> { diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt index e97fbcac3b..2600b47a79 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt @@ -2542,8 +2542,12 @@ namespace .Extensions public static . IsNotCollectible(this .<.Assembly> source) { } public static . IsNotDynamic(this .<.Assembly> source) { } public static . IsNotFullyTrusted(this .<.Assembly> source) { } - public static ._IsNotSigned_Assertion IsNotSigned(this .<.Assembly> source) { } - public static ._IsSigned_Assertion IsSigned(this .<.Assembly> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotSigned_Assertion IsNotSigned(this . source) + where TActual : .Assembly { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsSigned_Assertion IsSigned(this . source) + where TActual : .Assembly { } } public class AssemblyIsCollectibleAssertion : .<.Assembly> { @@ -3029,15 +3033,27 @@ namespace .Extensions } public static class CultureInfoAssertionExtensions { - public static ._IsEnglish_Assertion IsEnglish(this .<.CultureInfo> source) { } - public static ._IsInvariant_Assertion IsInvariant(this .<.CultureInfo> source) { } - public static ._IsLeftToRight_Assertion IsLeftToRight(this .<.CultureInfo> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsEnglish_Assertion IsEnglish(this . source) + where TActual : .CultureInfo { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsInvariant_Assertion IsInvariant(this . source) + where TActual : .CultureInfo { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsLeftToRight_Assertion IsLeftToRight(this . source) + where TActual : .CultureInfo { } public static . IsNeutralCulture(this .<.CultureInfo> source) { } - public static ._IsNotEnglish_Assertion IsNotEnglish(this .<.CultureInfo> source) { } - public static ._IsNotInvariant_Assertion IsNotInvariant(this .<.CultureInfo> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotEnglish_Assertion IsNotEnglish(this . source) + where TActual : .CultureInfo { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotInvariant_Assertion IsNotInvariant(this . source) + where TActual : .CultureInfo { } public static . IsNotNeutralCulture(this .<.CultureInfo> source) { } public static . IsReadOnly(this .<.CultureInfo> source) { } - public static ._IsRightToLeft_Assertion IsRightToLeft(this .<.CultureInfo> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsRightToLeft_Assertion IsRightToLeft(this . source) + where TActual : .CultureInfo { } } public class CultureInfoIsNeutralCultureAssertion : .<.CultureInfo> { @@ -3594,14 +3610,26 @@ namespace .Extensions } public static class EncodingAssertionExtensions { - public static ._IsASCII_Assertion IsASCII(this .<.Encoding> source) { } - public static ._IsBigEndianUnicode_Assertion IsBigEndianUnicode(this .<.Encoding> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsASCII_Assertion IsASCII(this . source) + where TActual : .Encoding { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsBigEndianUnicode_Assertion IsBigEndianUnicode(this . source) + where TActual : .Encoding { } public static . IsNotSingleByte(this .<.Encoding> source) { } - public static ._IsNotUTF8_Assertion IsNotUTF8(this .<.Encoding> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotUTF8_Assertion IsNotUTF8(this . source) + where TActual : .Encoding { } public static . IsSingleByte(this .<.Encoding> source) { } - public static ._IsUTF32_Assertion IsUTF32(this .<.Encoding> source) { } - public static ._IsUTF8_Assertion IsUTF8(this .<.Encoding> source) { } - public static ._IsUnicode_Assertion IsUnicode(this .<.Encoding> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsUTF32_Assertion IsUTF32(this . source) + where TActual : .Encoding { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsUTF8_Assertion IsUTF8(this . source) + where TActual : .Encoding { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsUnicode_Assertion IsUnicode(this . source) + where TActual : .Encoding { } } public class EncodingIsSingleByteAssertion : .<.Encoding> { @@ -3678,18 +3706,38 @@ namespace .Extensions } public static class ExceptionAssertionExtensions { - public static ._HasHelpLink_Assertion HasHelpLink(this .<> source) { } - public static ._HasInnerException_Assertion HasInnerException(this .<> source) { } - public static ._HasNoData_Assertion HasNoData(this .<> source) { } - public static ._HasNoHelpLink_Assertion HasNoHelpLink(this .<> source) { } - public static ._HasNoInnerException_Assertion HasNoInnerException(this .<> source) { } - public static ._HasNoSource_Assertion HasNoSource(this .<> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasHelpLink_Assertion HasHelpLink(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasInnerException_Assertion HasInnerException(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasNoData_Assertion HasNoData(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasNoHelpLink_Assertion HasNoHelpLink(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasNoInnerException_Assertion HasNoInnerException(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasNoSource_Assertion HasNoSource(this . source) + where TActual : { } [.(" uses reflection which may be trimmed in AOT scenarios")] - public static ._HasNoTargetSite_Assertion HasNoTargetSite(this .<> source) { } - public static ._HasSource_Assertion HasSource(this .<> source) { } - public static ._HasStackTrace_Assertion HasStackTrace(this .<> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasNoTargetSite_Assertion HasNoTargetSite(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasSource_Assertion HasSource(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasStackTrace_Assertion HasStackTrace(this . source) + where TActual : { } [.(" uses reflection which may be trimmed in AOT scenarios")] - public static ._HasTargetSite_Assertion HasTargetSite(this .<> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasTargetSite_Assertion HasTargetSite(this . source) + where TActual : { } } public sealed class Exception_HasHelpLink_Assertion : .<> { @@ -3967,23 +4015,53 @@ namespace .Extensions } public static class HttpResponseMessageAssertionExtensions { - public static ._HasContentType_String_Assertion HasContentType(this .<.> source, string contentType, [.("contentType")] string? contentTypeExpression = null) { } - public static ._HasHeader_String_Assertion HasHeader(this .<.> source, string headerName, [.("headerName")] string? headerNameExpression = null) { } - public static ._HasJsonContent_Assertion HasJsonContent(this .<.> source) { } - public static ._HasStatusCode_HttpStatusCode_Assertion HasStatusCode(this .<.> source, .HttpStatusCode statusCode, [.("statusCode")] string? statusCodeExpression = null) { } - public static ._IsBadRequest_Assertion IsBadRequest(this .<.> source) { } - public static ._IsClientErrorStatusCode_Assertion IsClientErrorStatusCode(this .<.> source) { } - public static ._IsConflict_Assertion IsConflict(this .<.> source) { } - public static ._IsCreated_Assertion IsCreated(this .<.> source) { } - public static ._IsForbidden_Assertion IsForbidden(this .<.> source) { } - public static ._IsNoContent_Assertion IsNoContent(this .<.> source) { } - public static ._IsNotFound_Assertion IsNotFound(this .<.> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasContentType_String_Assertion HasContentType(this . source, string contentType, [.("contentType")] string? contentTypeExpression = null) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasHeader_String_Assertion HasHeader(this . source, string headerName, [.("headerName")] string? headerNameExpression = null) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasJsonContent_Assertion HasJsonContent(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasStatusCode_HttpStatusCode_Assertion HasStatusCode(this . source, .HttpStatusCode statusCode, [.("statusCode")] string? statusCodeExpression = null) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsBadRequest_Assertion IsBadRequest(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsClientErrorStatusCode_Assertion IsClientErrorStatusCode(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsConflict_Assertion IsConflict(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsCreated_Assertion IsCreated(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsForbidden_Assertion IsForbidden(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNoContent_Assertion IsNoContent(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotFound_Assertion IsNotFound(this . source) + where TActual : . { } public static . IsNotSuccessStatusCode(this .<.> source) { } - public static ._IsOk_Assertion IsOk(this .<.> source) { } - public static ._IsRedirectStatusCode_Assertion IsRedirectStatusCode(this .<.> source) { } - public static ._IsServerErrorStatusCode_Assertion IsServerErrorStatusCode(this .<.> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsOk_Assertion IsOk(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsRedirectStatusCode_Assertion IsRedirectStatusCode(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsServerErrorStatusCode_Assertion IsServerErrorStatusCode(this . source) + where TActual : . { } public static . IsSuccessStatusCode(this .<.> source) { } - public static ._IsUnauthorized_Assertion IsUnauthorized(this .<.> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsUnauthorized_Assertion IsUnauthorized(this . source) + where TActual : . { } } public class HttpResponseMessageIsSuccessStatusCodeAssertion : .<.> { @@ -4379,16 +4457,36 @@ namespace .Extensions } public static class JsonNodeAssertionExtensions { - public static ._DoesNotHaveJsonProperty_String_Assertion DoesNotHaveJsonProperty(this .<..JsonNode?> source, string propertyName, [.("propertyName")] string? propertyNameExpression = null) { } - public static ._HasJsonArrayCount_Int_Assertion HasJsonArrayCount(this .<..JsonNode?> source, int expected, [.("expected")] string? expectedExpression = null) { } - public static ._HasJsonProperty_String_Assertion HasJsonProperty(this .<..JsonNode?> source, string propertyName, [.("propertyName")] string? propertyNameExpression = null) { } - public static ._IsDeepEqualTo_JsonNode_Assertion IsDeepEqualTo(this .<..JsonNode?> source, ..JsonNode? expected, [.("expected")] string? expectedExpression = null) { } - public static ._IsJsonArray_Assertion IsJsonArray(this .<..JsonNode?> source) { } - public static ._IsJsonArrayEmpty_Assertion IsJsonArrayEmpty(this .<..JsonNode?> source) { } - public static ._IsJsonArrayNotEmpty_Assertion IsJsonArrayNotEmpty(this .<..JsonNode?> source) { } - public static ._IsJsonObject_Assertion IsJsonObject(this .<..JsonNode?> source) { } - public static ._IsJsonValue_Assertion IsJsonValue(this .<..JsonNode?> source) { } - public static ._IsNotDeepEqualTo_JsonNode_Assertion IsNotDeepEqualTo(this .<..JsonNode?> source, ..JsonNode? expected, [.("expected")] string? expectedExpression = null) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._DoesNotHaveJsonProperty_String_Assertion DoesNotHaveJsonProperty(this . source, string propertyName, [.("propertyName")] string? propertyNameExpression = null) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasJsonArrayCount_Int_Assertion HasJsonArrayCount(this . source, int expected, [.("expected")] string? expectedExpression = null) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasJsonProperty_String_Assertion HasJsonProperty(this . source, string propertyName, [.("propertyName")] string? propertyNameExpression = null) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsDeepEqualTo_JsonNode_Assertion IsDeepEqualTo(this . source, ..JsonNode? expected, [.("expected")] string? expectedExpression = null) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsJsonArray_Assertion IsJsonArray(this . source) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsJsonArrayEmpty_Assertion IsJsonArrayEmpty(this . source) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsJsonArrayNotEmpty_Assertion IsJsonArrayNotEmpty(this . source) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsJsonObject_Assertion IsJsonObject(this . source) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsJsonValue_Assertion IsJsonValue(this . source) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotDeepEqualTo_JsonNode_Assertion IsNotDeepEqualTo(this . source, ..JsonNode? expected, [.("expected")] string? expectedExpression = null) + where TActual : ..JsonNode { } } public sealed class JsonNode_DoesNotHaveJsonProperty_String_Assertion : .<..JsonNode?> { @@ -4825,10 +4923,18 @@ namespace .Extensions public static . CannotSeek(this .<.Stream> source) { } public static . CannotTimeout(this .<.Stream> source) { } public static . CannotWrite(this .<.Stream> source) { } - public static ._IsAtEnd_Assertion IsAtEnd(this .<.Stream> source) { } - public static ._IsAtStart_Assertion IsAtStart(this .<.Stream> source) { } - public static ._IsEmpty_Assertion IsEmpty(this .<.Stream> source) { } - public static ._IsNotEmpty_Assertion IsNotEmpty(this .<.Stream> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsAtEnd_Assertion IsAtEnd(this . source) + where TActual : .Stream { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsAtStart_Assertion IsAtStart(this . source) + where TActual : .Stream { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsEmpty_Assertion IsEmpty(this . source) + where TActual : .Stream { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotEmpty_Assertion IsNotEmpty(this . source) + where TActual : .Stream { } } public class StreamCanReadAssertion : .<.Stream> { diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt index 478b6783f9..87b3a330a5 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt @@ -2563,8 +2563,12 @@ namespace .Extensions public static . IsNotCollectible(this .<.Assembly> source) { } public static . IsNotDynamic(this .<.Assembly> source) { } public static . IsNotFullyTrusted(this .<.Assembly> source) { } - public static ._IsNotSigned_Assertion IsNotSigned(this .<.Assembly> source) { } - public static ._IsSigned_Assertion IsSigned(this .<.Assembly> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotSigned_Assertion IsNotSigned(this . source) + where TActual : .Assembly { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsSigned_Assertion IsSigned(this . source) + where TActual : .Assembly { } } public class AssemblyIsCollectibleAssertion : .<.Assembly> { @@ -3057,15 +3061,27 @@ namespace .Extensions } public static class CultureInfoAssertionExtensions { - public static ._IsEnglish_Assertion IsEnglish(this .<.CultureInfo> source) { } - public static ._IsInvariant_Assertion IsInvariant(this .<.CultureInfo> source) { } - public static ._IsLeftToRight_Assertion IsLeftToRight(this .<.CultureInfo> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsEnglish_Assertion IsEnglish(this . source) + where TActual : .CultureInfo { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsInvariant_Assertion IsInvariant(this . source) + where TActual : .CultureInfo { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsLeftToRight_Assertion IsLeftToRight(this . source) + where TActual : .CultureInfo { } public static . IsNeutralCulture(this .<.CultureInfo> source) { } - public static ._IsNotEnglish_Assertion IsNotEnglish(this .<.CultureInfo> source) { } - public static ._IsNotInvariant_Assertion IsNotInvariant(this .<.CultureInfo> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotEnglish_Assertion IsNotEnglish(this . source) + where TActual : .CultureInfo { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotInvariant_Assertion IsNotInvariant(this . source) + where TActual : .CultureInfo { } public static . IsNotNeutralCulture(this .<.CultureInfo> source) { } public static . IsReadOnly(this .<.CultureInfo> source) { } - public static ._IsRightToLeft_Assertion IsRightToLeft(this .<.CultureInfo> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsRightToLeft_Assertion IsRightToLeft(this . source) + where TActual : .CultureInfo { } } public class CultureInfoIsNeutralCultureAssertion : .<.CultureInfo> { @@ -3631,14 +3647,26 @@ namespace .Extensions } public static class EncodingAssertionExtensions { - public static ._IsASCII_Assertion IsASCII(this .<.Encoding> source) { } - public static ._IsBigEndianUnicode_Assertion IsBigEndianUnicode(this .<.Encoding> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsASCII_Assertion IsASCII(this . source) + where TActual : .Encoding { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsBigEndianUnicode_Assertion IsBigEndianUnicode(this . source) + where TActual : .Encoding { } public static . IsNotSingleByte(this .<.Encoding> source) { } - public static ._IsNotUTF8_Assertion IsNotUTF8(this .<.Encoding> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotUTF8_Assertion IsNotUTF8(this . source) + where TActual : .Encoding { } public static . IsSingleByte(this .<.Encoding> source) { } - public static ._IsUTF32_Assertion IsUTF32(this .<.Encoding> source) { } - public static ._IsUTF8_Assertion IsUTF8(this .<.Encoding> source) { } - public static ._IsUnicode_Assertion IsUnicode(this .<.Encoding> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsUTF32_Assertion IsUTF32(this . source) + where TActual : .Encoding { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsUTF8_Assertion IsUTF8(this . source) + where TActual : .Encoding { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsUnicode_Assertion IsUnicode(this . source) + where TActual : .Encoding { } } public class EncodingIsSingleByteAssertion : .<.Encoding> { @@ -3715,18 +3743,38 @@ namespace .Extensions } public static class ExceptionAssertionExtensions { - public static ._HasHelpLink_Assertion HasHelpLink(this .<> source) { } - public static ._HasInnerException_Assertion HasInnerException(this .<> source) { } - public static ._HasNoData_Assertion HasNoData(this .<> source) { } - public static ._HasNoHelpLink_Assertion HasNoHelpLink(this .<> source) { } - public static ._HasNoInnerException_Assertion HasNoInnerException(this .<> source) { } - public static ._HasNoSource_Assertion HasNoSource(this .<> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasHelpLink_Assertion HasHelpLink(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasInnerException_Assertion HasInnerException(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasNoData_Assertion HasNoData(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasNoHelpLink_Assertion HasNoHelpLink(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasNoInnerException_Assertion HasNoInnerException(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasNoSource_Assertion HasNoSource(this . source) + where TActual : { } [.(" uses reflection which may be trimmed in AOT scenarios")] - public static ._HasNoTargetSite_Assertion HasNoTargetSite(this .<> source) { } - public static ._HasSource_Assertion HasSource(this .<> source) { } - public static ._HasStackTrace_Assertion HasStackTrace(this .<> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasNoTargetSite_Assertion HasNoTargetSite(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasSource_Assertion HasSource(this . source) + where TActual : { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasStackTrace_Assertion HasStackTrace(this . source) + where TActual : { } [.(" uses reflection which may be trimmed in AOT scenarios")] - public static ._HasTargetSite_Assertion HasTargetSite(this .<> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasTargetSite_Assertion HasTargetSite(this . source) + where TActual : { } } public sealed class Exception_HasHelpLink_Assertion : .<> { @@ -4007,23 +4055,53 @@ namespace .Extensions } public static class HttpResponseMessageAssertionExtensions { - public static ._HasContentType_String_Assertion HasContentType(this .<.> source, string contentType, [.("contentType")] string? contentTypeExpression = null) { } - public static ._HasHeader_String_Assertion HasHeader(this .<.> source, string headerName, [.("headerName")] string? headerNameExpression = null) { } - public static ._HasJsonContent_Assertion HasJsonContent(this .<.> source) { } - public static ._HasStatusCode_HttpStatusCode_Assertion HasStatusCode(this .<.> source, .HttpStatusCode statusCode, [.("statusCode")] string? statusCodeExpression = null) { } - public static ._IsBadRequest_Assertion IsBadRequest(this .<.> source) { } - public static ._IsClientErrorStatusCode_Assertion IsClientErrorStatusCode(this .<.> source) { } - public static ._IsConflict_Assertion IsConflict(this .<.> source) { } - public static ._IsCreated_Assertion IsCreated(this .<.> source) { } - public static ._IsForbidden_Assertion IsForbidden(this .<.> source) { } - public static ._IsNoContent_Assertion IsNoContent(this .<.> source) { } - public static ._IsNotFound_Assertion IsNotFound(this .<.> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasContentType_String_Assertion HasContentType(this . source, string contentType, [.("contentType")] string? contentTypeExpression = null) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasHeader_String_Assertion HasHeader(this . source, string headerName, [.("headerName")] string? headerNameExpression = null) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasJsonContent_Assertion HasJsonContent(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasStatusCode_HttpStatusCode_Assertion HasStatusCode(this . source, .HttpStatusCode statusCode, [.("statusCode")] string? statusCodeExpression = null) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsBadRequest_Assertion IsBadRequest(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsClientErrorStatusCode_Assertion IsClientErrorStatusCode(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsConflict_Assertion IsConflict(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsCreated_Assertion IsCreated(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsForbidden_Assertion IsForbidden(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNoContent_Assertion IsNoContent(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotFound_Assertion IsNotFound(this . source) + where TActual : . { } public static . IsNotSuccessStatusCode(this .<.> source) { } - public static ._IsOk_Assertion IsOk(this .<.> source) { } - public static ._IsRedirectStatusCode_Assertion IsRedirectStatusCode(this .<.> source) { } - public static ._IsServerErrorStatusCode_Assertion IsServerErrorStatusCode(this .<.> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsOk_Assertion IsOk(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsRedirectStatusCode_Assertion IsRedirectStatusCode(this . source) + where TActual : . { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsServerErrorStatusCode_Assertion IsServerErrorStatusCode(this . source) + where TActual : . { } public static . IsSuccessStatusCode(this .<.> source) { } - public static ._IsUnauthorized_Assertion IsUnauthorized(this .<.> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsUnauthorized_Assertion IsUnauthorized(this . source) + where TActual : . { } } public class HttpResponseMessageIsSuccessStatusCodeAssertion : .<.> { @@ -4436,16 +4514,36 @@ namespace .Extensions } public static class JsonNodeAssertionExtensions { - public static ._DoesNotHaveJsonProperty_String_Assertion DoesNotHaveJsonProperty(this .<..JsonNode?> source, string propertyName, [.("propertyName")] string? propertyNameExpression = null) { } - public static ._HasJsonArrayCount_Int_Assertion HasJsonArrayCount(this .<..JsonNode?> source, int expected, [.("expected")] string? expectedExpression = null) { } - public static ._HasJsonProperty_String_Assertion HasJsonProperty(this .<..JsonNode?> source, string propertyName, [.("propertyName")] string? propertyNameExpression = null) { } - public static ._IsDeepEqualTo_JsonNode_Assertion IsDeepEqualTo(this .<..JsonNode?> source, ..JsonNode? expected, [.("expected")] string? expectedExpression = null) { } - public static ._IsJsonArray_Assertion IsJsonArray(this .<..JsonNode?> source) { } - public static ._IsJsonArrayEmpty_Assertion IsJsonArrayEmpty(this .<..JsonNode?> source) { } - public static ._IsJsonArrayNotEmpty_Assertion IsJsonArrayNotEmpty(this .<..JsonNode?> source) { } - public static ._IsJsonObject_Assertion IsJsonObject(this .<..JsonNode?> source) { } - public static ._IsJsonValue_Assertion IsJsonValue(this .<..JsonNode?> source) { } - public static ._IsNotDeepEqualTo_JsonNode_Assertion IsNotDeepEqualTo(this .<..JsonNode?> source, ..JsonNode? expected, [.("expected")] string? expectedExpression = null) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._DoesNotHaveJsonProperty_String_Assertion DoesNotHaveJsonProperty(this . source, string propertyName, [.("propertyName")] string? propertyNameExpression = null) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasJsonArrayCount_Int_Assertion HasJsonArrayCount(this . source, int expected, [.("expected")] string? expectedExpression = null) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._HasJsonProperty_String_Assertion HasJsonProperty(this . source, string propertyName, [.("propertyName")] string? propertyNameExpression = null) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsDeepEqualTo_JsonNode_Assertion IsDeepEqualTo(this . source, ..JsonNode? expected, [.("expected")] string? expectedExpression = null) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsJsonArray_Assertion IsJsonArray(this . source) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsJsonArrayEmpty_Assertion IsJsonArrayEmpty(this . source) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsJsonArrayNotEmpty_Assertion IsJsonArrayNotEmpty(this . source) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsJsonObject_Assertion IsJsonObject(this . source) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsJsonValue_Assertion IsJsonValue(this . source) + where TActual : ..JsonNode { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotDeepEqualTo_JsonNode_Assertion IsNotDeepEqualTo(this . source, ..JsonNode? expected, [.("expected")] string? expectedExpression = null) + where TActual : ..JsonNode { } } public sealed class JsonNode_DoesNotHaveJsonProperty_String_Assertion : .<..JsonNode?> { @@ -4885,10 +4983,18 @@ namespace .Extensions public static . CannotSeek(this .<.Stream> source) { } public static . CannotTimeout(this .<.Stream> source) { } public static . CannotWrite(this .<.Stream> source) { } - public static ._IsAtEnd_Assertion IsAtEnd(this .<.Stream> source) { } - public static ._IsAtStart_Assertion IsAtStart(this .<.Stream> source) { } - public static ._IsEmpty_Assertion IsEmpty(this .<.Stream> source) { } - public static ._IsNotEmpty_Assertion IsNotEmpty(this .<.Stream> source) { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsAtEnd_Assertion IsAtEnd(this . source) + where TActual : .Stream { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsAtStart_Assertion IsAtStart(this . source) + where TActual : .Stream { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsEmpty_Assertion IsEmpty(this . source) + where TActual : .Stream { } + [.("Trimming", "IL2091", Justification="Generic type parameter is only used for property access, not instantiation")] + public static ._IsNotEmpty_Assertion IsNotEmpty(this . source) + where TActual : .Stream { } } public class StreamCanReadAssertion : .<.Stream> { diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt index db43114adc..1fcf252a29 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -2306,8 +2306,10 @@ namespace .Extensions public static . IsFullyTrusted(this .<.Assembly> source) { } public static . IsNotDynamic(this .<.Assembly> source) { } public static . IsNotFullyTrusted(this .<.Assembly> source) { } - public static ._IsNotSigned_Assertion IsNotSigned(this .<.Assembly> source) { } - public static ._IsSigned_Assertion IsSigned(this .<.Assembly> source) { } + public static ._IsNotSigned_Assertion IsNotSigned(this . source) + where TActual : .Assembly { } + public static ._IsSigned_Assertion IsSigned(this . source) + where TActual : .Assembly { } } public class AssemblyIsDynamicAssertion : .<.Assembly> { @@ -2751,15 +2753,21 @@ namespace .Extensions } public static class CultureInfoAssertionExtensions { - public static ._IsEnglish_Assertion IsEnglish(this .<.CultureInfo> source) { } - public static ._IsInvariant_Assertion IsInvariant(this .<.CultureInfo> source) { } - public static ._IsLeftToRight_Assertion IsLeftToRight(this .<.CultureInfo> source) { } + public static ._IsEnglish_Assertion IsEnglish(this . source) + where TActual : .CultureInfo { } + public static ._IsInvariant_Assertion IsInvariant(this . source) + where TActual : .CultureInfo { } + public static ._IsLeftToRight_Assertion IsLeftToRight(this . source) + where TActual : .CultureInfo { } public static . IsNeutralCulture(this .<.CultureInfo> source) { } - public static ._IsNotEnglish_Assertion IsNotEnglish(this .<.CultureInfo> source) { } - public static ._IsNotInvariant_Assertion IsNotInvariant(this .<.CultureInfo> source) { } + public static ._IsNotEnglish_Assertion IsNotEnglish(this . source) + where TActual : .CultureInfo { } + public static ._IsNotInvariant_Assertion IsNotInvariant(this . source) + where TActual : .CultureInfo { } public static . IsNotNeutralCulture(this .<.CultureInfo> source) { } public static . IsReadOnly(this .<.CultureInfo> source) { } - public static ._IsRightToLeft_Assertion IsRightToLeft(this .<.CultureInfo> source) { } + public static ._IsRightToLeft_Assertion IsRightToLeft(this . source) + where TActual : .CultureInfo { } } public class CultureInfoIsNeutralCultureAssertion : .<.CultureInfo> { @@ -3215,14 +3223,20 @@ namespace .Extensions } public static class EncodingAssertionExtensions { - public static ._IsASCII_Assertion IsASCII(this .<.Encoding> source) { } - public static ._IsBigEndianUnicode_Assertion IsBigEndianUnicode(this .<.Encoding> source) { } + public static ._IsASCII_Assertion IsASCII(this . source) + where TActual : .Encoding { } + public static ._IsBigEndianUnicode_Assertion IsBigEndianUnicode(this . source) + where TActual : .Encoding { } public static . IsNotSingleByte(this .<.Encoding> source) { } - public static ._IsNotUTF8_Assertion IsNotUTF8(this .<.Encoding> source) { } + public static ._IsNotUTF8_Assertion IsNotUTF8(this . source) + where TActual : .Encoding { } public static . IsSingleByte(this .<.Encoding> source) { } - public static ._IsUTF32_Assertion IsUTF32(this .<.Encoding> source) { } - public static ._IsUTF8_Assertion IsUTF8(this .<.Encoding> source) { } - public static ._IsUnicode_Assertion IsUnicode(this .<.Encoding> source) { } + public static ._IsUTF32_Assertion IsUTF32(this . source) + where TActual : .Encoding { } + public static ._IsUTF8_Assertion IsUTF8(this . source) + where TActual : .Encoding { } + public static ._IsUnicode_Assertion IsUnicode(this . source) + where TActual : .Encoding { } } public class EncodingIsSingleByteAssertion : .<.Encoding> { @@ -3293,16 +3307,26 @@ namespace .Extensions } public static class ExceptionAssertionExtensions { - public static ._HasHelpLink_Assertion HasHelpLink(this .<> source) { } - public static ._HasInnerException_Assertion HasInnerException(this .<> source) { } - public static ._HasNoData_Assertion HasNoData(this .<> source) { } - public static ._HasNoHelpLink_Assertion HasNoHelpLink(this .<> source) { } - public static ._HasNoInnerException_Assertion HasNoInnerException(this .<> source) { } - public static ._HasNoSource_Assertion HasNoSource(this .<> source) { } - public static ._HasNoTargetSite_Assertion HasNoTargetSite(this .<> source) { } - public static ._HasSource_Assertion HasSource(this .<> source) { } - public static ._HasStackTrace_Assertion HasStackTrace(this .<> source) { } - public static ._HasTargetSite_Assertion HasTargetSite(this .<> source) { } + public static ._HasHelpLink_Assertion HasHelpLink(this . source) + where TActual : { } + public static ._HasInnerException_Assertion HasInnerException(this . source) + where TActual : { } + public static ._HasNoData_Assertion HasNoData(this . source) + where TActual : { } + public static ._HasNoHelpLink_Assertion HasNoHelpLink(this . source) + where TActual : { } + public static ._HasNoInnerException_Assertion HasNoInnerException(this . source) + where TActual : { } + public static ._HasNoSource_Assertion HasNoSource(this . source) + where TActual : { } + public static ._HasNoTargetSite_Assertion HasNoTargetSite(this . source) + where TActual : { } + public static ._HasSource_Assertion HasSource(this . source) + where TActual : { } + public static ._HasStackTrace_Assertion HasStackTrace(this . source) + where TActual : { } + public static ._HasTargetSite_Assertion HasTargetSite(this . source) + where TActual : { } } public sealed class Exception_HasHelpLink_Assertion : .<> { @@ -3556,23 +3580,38 @@ namespace .Extensions } public static class HttpResponseMessageAssertionExtensions { - public static ._HasContentType_String_Assertion HasContentType(this .<.> source, string contentType, [.("contentType")] string? contentTypeExpression = null) { } - public static ._HasHeader_String_Assertion HasHeader(this .<.> source, string headerName, [.("headerName")] string? headerNameExpression = null) { } - public static ._HasJsonContent_Assertion HasJsonContent(this .<.> source) { } - public static ._HasStatusCode_HttpStatusCode_Assertion HasStatusCode(this .<.> source, .HttpStatusCode statusCode, [.("statusCode")] string? statusCodeExpression = null) { } - public static ._IsBadRequest_Assertion IsBadRequest(this .<.> source) { } - public static ._IsClientErrorStatusCode_Assertion IsClientErrorStatusCode(this .<.> source) { } - public static ._IsConflict_Assertion IsConflict(this .<.> source) { } - public static ._IsCreated_Assertion IsCreated(this .<.> source) { } - public static ._IsForbidden_Assertion IsForbidden(this .<.> source) { } - public static ._IsNoContent_Assertion IsNoContent(this .<.> source) { } - public static ._IsNotFound_Assertion IsNotFound(this .<.> source) { } + public static ._HasContentType_String_Assertion HasContentType(this . source, string contentType, [.("contentType")] string? contentTypeExpression = null) + where TActual : . { } + public static ._HasHeader_String_Assertion HasHeader(this . source, string headerName, [.("headerName")] string? headerNameExpression = null) + where TActual : . { } + public static ._HasJsonContent_Assertion HasJsonContent(this . source) + where TActual : . { } + public static ._HasStatusCode_HttpStatusCode_Assertion HasStatusCode(this . source, .HttpStatusCode statusCode, [.("statusCode")] string? statusCodeExpression = null) + where TActual : . { } + public static ._IsBadRequest_Assertion IsBadRequest(this . source) + where TActual : . { } + public static ._IsClientErrorStatusCode_Assertion IsClientErrorStatusCode(this . source) + where TActual : . { } + public static ._IsConflict_Assertion IsConflict(this . source) + where TActual : . { } + public static ._IsCreated_Assertion IsCreated(this . source) + where TActual : . { } + public static ._IsForbidden_Assertion IsForbidden(this . source) + where TActual : . { } + public static ._IsNoContent_Assertion IsNoContent(this . source) + where TActual : . { } + public static ._IsNotFound_Assertion IsNotFound(this . source) + where TActual : . { } public static . IsNotSuccessStatusCode(this .<.> source) { } - public static ._IsOk_Assertion IsOk(this .<.> source) { } - public static ._IsRedirectStatusCode_Assertion IsRedirectStatusCode(this .<.> source) { } - public static ._IsServerErrorStatusCode_Assertion IsServerErrorStatusCode(this .<.> source) { } + public static ._IsOk_Assertion IsOk(this . source) + where TActual : . { } + public static ._IsRedirectStatusCode_Assertion IsRedirectStatusCode(this . source) + where TActual : . { } + public static ._IsServerErrorStatusCode_Assertion IsServerErrorStatusCode(this . source) + where TActual : . { } public static . IsSuccessStatusCode(this .<.> source) { } - public static ._IsUnauthorized_Assertion IsUnauthorized(this .<.> source) { } + public static ._IsUnauthorized_Assertion IsUnauthorized(this . source) + where TActual : . { } } public class HttpResponseMessageIsSuccessStatusCodeAssertion : .<.> { @@ -3953,14 +3992,22 @@ namespace .Extensions } public static class JsonNodeAssertionExtensions { - public static ._DoesNotHaveJsonProperty_String_Assertion DoesNotHaveJsonProperty(this .<..JsonNode?> source, string propertyName, [.("propertyName")] string? propertyNameExpression = null) { } - public static ._HasJsonArrayCount_Int_Assertion HasJsonArrayCount(this .<..JsonNode?> source, int expected, [.("expected")] string? expectedExpression = null) { } - public static ._HasJsonProperty_String_Assertion HasJsonProperty(this .<..JsonNode?> source, string propertyName, [.("propertyName")] string? propertyNameExpression = null) { } - public static ._IsJsonArray_Assertion IsJsonArray(this .<..JsonNode?> source) { } - public static ._IsJsonArrayEmpty_Assertion IsJsonArrayEmpty(this .<..JsonNode?> source) { } - public static ._IsJsonArrayNotEmpty_Assertion IsJsonArrayNotEmpty(this .<..JsonNode?> source) { } - public static ._IsJsonObject_Assertion IsJsonObject(this .<..JsonNode?> source) { } - public static ._IsJsonValue_Assertion IsJsonValue(this .<..JsonNode?> source) { } + public static ._DoesNotHaveJsonProperty_String_Assertion DoesNotHaveJsonProperty(this . source, string propertyName, [.("propertyName")] string? propertyNameExpression = null) + where TActual : ..JsonNode { } + public static ._HasJsonArrayCount_Int_Assertion HasJsonArrayCount(this . source, int expected, [.("expected")] string? expectedExpression = null) + where TActual : ..JsonNode { } + public static ._HasJsonProperty_String_Assertion HasJsonProperty(this . source, string propertyName, [.("propertyName")] string? propertyNameExpression = null) + where TActual : ..JsonNode { } + public static ._IsJsonArray_Assertion IsJsonArray(this . source) + where TActual : ..JsonNode { } + public static ._IsJsonArrayEmpty_Assertion IsJsonArrayEmpty(this . source) + where TActual : ..JsonNode { } + public static ._IsJsonArrayNotEmpty_Assertion IsJsonArrayNotEmpty(this . source) + where TActual : ..JsonNode { } + public static ._IsJsonObject_Assertion IsJsonObject(this . source) + where TActual : ..JsonNode { } + public static ._IsJsonValue_Assertion IsJsonValue(this . source) + where TActual : ..JsonNode { } } public sealed class JsonNode_DoesNotHaveJsonProperty_String_Assertion : .<..JsonNode?> { @@ -4229,10 +4276,14 @@ namespace .Extensions public static . CannotSeek(this .<.Stream> source) { } public static . CannotTimeout(this .<.Stream> source) { } public static . CannotWrite(this .<.Stream> source) { } - public static ._IsAtEnd_Assertion IsAtEnd(this .<.Stream> source) { } - public static ._IsAtStart_Assertion IsAtStart(this .<.Stream> source) { } - public static ._IsEmpty_Assertion IsEmpty(this .<.Stream> source) { } - public static ._IsNotEmpty_Assertion IsNotEmpty(this .<.Stream> source) { } + public static ._IsAtEnd_Assertion IsAtEnd(this . source) + where TActual : .Stream { } + public static ._IsAtStart_Assertion IsAtStart(this . source) + where TActual : .Stream { } + public static ._IsEmpty_Assertion IsEmpty(this . source) + where TActual : .Stream { } + public static ._IsNotEmpty_Assertion IsNotEmpty(this . source) + where TActual : .Stream { } } public class StreamCanReadAssertion : .<.Stream> { From 2faa66eabede1fd050d93deb0703f17df9ed2aa9 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 23 Mar 2026 02:03:21 +0000 Subject: [PATCH 5/6] refactor: minor cleanup in covariance helpers - Cache typeParam.ToDisplayString() to avoid duplicate call in AssertionExtensionGenerator covariant path - Replace HashSet with linear Contains() in GetCovariantTypeParamName (typical input has 0-2 items) - Inline single-use GetNullableCastType into GetCovariantContextExpr --- .../Generators/AssertionExtensionGenerator.cs | 8 ++++---- .../Generators/CovarianceHelper.cs | 18 ++++-------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs b/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs index 7e98fb8277..85bc5b6f06 100644 --- a/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs +++ b/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs @@ -354,13 +354,14 @@ private static void GenerateExtensionMethod( var isCovariantCandidate = !isNullableOverload && CovarianceHelper.IsCovariantCandidate(typeParam); + var typeParamDisplay = typeParam.ToDisplayString(); if (isNullableOverload) { // For nullable reference types, we can't use two separate overloads for T and T? // because NRT annotations are erased at runtime - they're the same type to the CLR. // Instead, just use the nullable version and accept both nullable and non-nullable sources. - sourceType = $"IAssertionSource<{typeParam.ToDisplayString()}>"; + sourceType = $"IAssertionSource<{typeParamDisplay}>"; genericTypeParam = null; genericConstraint = null; } @@ -370,7 +371,6 @@ private static void GenerateExtensionMethod( } else if (isCovariantCandidate) { - var typeParamDisplay = typeParam.ToDisplayString(); var covariantParam = CovarianceHelper.GetCovariantTypeParamName(genericParams); sourceType = $"IAssertionSource<{covariantParam}>"; genericTypeParam = covariantParam; @@ -378,7 +378,7 @@ private static void GenerateExtensionMethod( } else { - sourceType = $"IAssertionSource<{typeParam.ToDisplayString()}>"; + sourceType = $"IAssertionSource<{typeParamDisplay}>"; } sourceBuilder.Append($" public static {returnType} {methodName}"); @@ -467,7 +467,7 @@ private static void GenerateExtensionMethod( } else if (isCovariantCandidate) { - sourceBuilder.Append(CovarianceHelper.GetCovariantContextExpr(typeParam.ToDisplayString())); + sourceBuilder.Append(CovarianceHelper.GetCovariantContextExpr(typeParamDisplay)); } else { diff --git a/TUnit.Assertions.SourceGenerator/Generators/CovarianceHelper.cs b/TUnit.Assertions.SourceGenerator/Generators/CovarianceHelper.cs index 7d0c0eb6bb..f64109883c 100644 --- a/TUnit.Assertions.SourceGenerator/Generators/CovarianceHelper.cs +++ b/TUnit.Assertions.SourceGenerator/Generators/CovarianceHelper.cs @@ -17,15 +17,13 @@ internal static class CovarianceHelper /// public static string GetCovariantTypeParamName(IEnumerable existingGenericParams) { - var existing = new HashSet(existingGenericParams); - if (!existing.Contains(PreferredName)) + if (!existingGenericParams.Contains(PreferredName)) { return PreferredName; } - // Append underscores until unique var candidate = PreferredName + "_"; - while (existing.Contains(candidate)) + while (existingGenericParams.Contains(candidate)) { candidate += "_"; } @@ -55,21 +53,13 @@ public static string GetConstraintTypeName(string typeName, ITypeSymbol type) return typeName; } - /// - /// Returns the nullable cast form of a type name for use in Map lambdas. - /// Map's Func takes TValue? and returns TNew?, so the cast must be to the nullable form. - /// - public static string GetNullableCastType(string typeName) - { - return typeName.EndsWith("?") ? typeName : $"{typeName}?"; - } - /// /// Generates the context mapping expression for covariant assertions. + /// Uses nullable cast since Map's Func takes TValue? and returns TNew?. /// public static string GetCovariantContextExpr(string targetTypeName) { - var nullableCastType = GetNullableCastType(targetTypeName); + var nullableCastType = targetTypeName.EndsWith("?") ? targetTypeName : $"{targetTypeName}?"; return $"source.Context.Map<{targetTypeName}>(static x => ({nullableCastType})x)"; } From 3c2cd4734e60b4c90342a495853b324d76e14bc9 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Mon, 23 Mar 2026 09:36:33 +0000 Subject: [PATCH 6/6] fix: ensure netstandard2.0 TUnit.Mocks is built before SourceGenerator.Tests Add explicit ProjectReference with SetTargetFramework to guarantee the netstandard2.0 TFM of TUnit.Mocks is built before the test project tries to copy the DLL, fixing CI build failures on both ubuntu and windows. --- .../TUnit.Mocks.SourceGenerator.Tests.csproj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/TUnit.Mocks.SourceGenerator.Tests/TUnit.Mocks.SourceGenerator.Tests.csproj b/TUnit.Mocks.SourceGenerator.Tests/TUnit.Mocks.SourceGenerator.Tests.csproj index 9c297ecc5b..9924064873 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/TUnit.Mocks.SourceGenerator.Tests.csproj +++ b/TUnit.Mocks.SourceGenerator.Tests/TUnit.Mocks.SourceGenerator.Tests.csproj @@ -14,6 +14,14 @@ + + + + +