diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 112c2ecbeb..31b833ae8d 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -66,6 +66,12 @@ jobs: - name: Build run: dotnet build -c Release + - name: Publish AOT + run: dotnet publish TUnit.TestProject/TUnit.TestProject.csproj -c Release --use-current-runtime -p:Aot=true -o TESTPROJECT_AOT --framework net8.0 + + - name: Publish Single File + run: dotnet publish TUnit.TestProject/TUnit.TestProject.csproj -c Release --use-current-runtime -p:SingleFile=true -o TESTPROJECT_SINGLEFILE --framework net8.0 + - name: Run Pipeline uses: ./.github/actions/execute-pipeline with: diff --git a/Directory.Packages.props b/Directory.Packages.props index c743e90186..2914315e37 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -80,6 +80,7 @@ + diff --git a/TUnit.Analyzers/AnalyzerReleases.Unshipped.md b/TUnit.Analyzers/AnalyzerReleases.Unshipped.md index 366f682b07..c52332fd49 100644 --- a/TUnit.Analyzers/AnalyzerReleases.Unshipped.md +++ b/TUnit.Analyzers/AnalyzerReleases.Unshipped.md @@ -1,5 +1,4 @@ ### New Rules -Rule ID | Category | Severity | Notes ---------|----------|----------|------- -TUnit0052 | Usage | Warning | Multiple constructors found without [TestConstructor] attribute \ No newline at end of file +Rule ID | Category | Severity | Notes +--------|----------|----------|------- \ No newline at end of file diff --git a/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs b/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs index df27b7c2c7..0c42f0d347 100644 --- a/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs +++ b/TUnit.Assertions.SourceGenerator/Generators/AssertionExtensionGenerator.cs @@ -92,13 +92,13 @@ public void Initialize(IncrementalGeneratorInitializationContext context) return null; } - // Check for RequiresDynamicCode attribute - var requiresDynamicCodeAttr = classSymbol.GetAttributes() - .FirstOrDefault(attr => attr.AttributeClass?.Name == "RequiresDynamicCodeAttribute"); - string? requiresDynamicCodeMessage = null; - if (requiresDynamicCodeAttr != null && requiresDynamicCodeAttr.ConstructorArguments.Length > 0) + // Check for RequiresUnreferencedCode attribute + var RequiresUnreferencedCodeAttr = classSymbol.GetAttributes() + .FirstOrDefault(attr => attr.AttributeClass?.Name == "RequiresUnreferencedCodeAttribute"); + string? RequiresUnreferencedCodeMessage = null; + if (RequiresUnreferencedCodeAttr != null && RequiresUnreferencedCodeAttr.ConstructorArguments.Length > 0) { - requiresDynamicCodeMessage = requiresDynamicCodeAttr.ConstructorArguments[0].Value?.ToString(); + RequiresUnreferencedCodeMessage = RequiresUnreferencedCodeAttr.ConstructorArguments[0].Value?.ToString(); } return new AssertionExtensionData( @@ -108,7 +108,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) assertionBaseType, constructors, overloadPriority, - requiresDynamicCodeMessage + RequiresUnreferencedCodeMessage ); } @@ -153,7 +153,7 @@ private static void GenerateExtensionMethods(SourceProductionContext context, As sourceBuilder.AppendLine($"namespace TUnit.Assertions.Extensions;"); sourceBuilder.AppendLine(); - + // Extension class var extensionClassName = $"{data.ClassSymbol.Name}Extensions"; sourceBuilder.AppendLine($"/// "); @@ -309,11 +309,11 @@ private static void GenerateExtensionMethod( sourceBuilder.AppendLine($" /// Extension method for {assertionType.Name}."); sourceBuilder.AppendLine(" /// "); - // Add RequiresDynamicCode attribute if present - if (!string.IsNullOrEmpty(data.RequiresDynamicCodeMessage)) + // Add RequiresUnreferencedCode attribute if present + if (!string.IsNullOrEmpty(data.RequiresUnreferencedCodeMessage)) { - var escapedMessage = data.RequiresDynamicCodeMessage.Replace("\"", "\\\""); - sourceBuilder.AppendLine($" [global::System.Diagnostics.CodeAnalysis.RequiresDynamicCode(\"{escapedMessage}\")]"); + var escapedMessage = data.RequiresUnreferencedCodeMessage!.Replace("\"", "\\\""); + sourceBuilder.AppendLine($" [global::System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode(\"{escapedMessage}\")]"); } // Add OverloadResolutionPriority attribute only if priority > 0 @@ -454,6 +454,6 @@ private record AssertionExtensionData( INamedTypeSymbol AssertionBaseType, ImmutableArray Constructors, int OverloadResolutionPriority, - string? RequiresDynamicCodeMessage + string? RequiresUnreferencedCodeMessage ); } diff --git a/TUnit.Assertions.Tests/DictionaryCollectionTests.cs b/TUnit.Assertions.Tests/DictionaryCollectionTests.cs index 466ef9f457..46db66210d 100644 --- a/TUnit.Assertions.Tests/DictionaryCollectionTests.cs +++ b/TUnit.Assertions.Tests/DictionaryCollectionTests.cs @@ -184,7 +184,7 @@ public async Task Dictionary_IsEquivalentTo_Works() // IsEquivalentTo works on collections regardless of order // Cast both to IEnumerable to use collection equivalency await Assert.That((IEnumerable>)dictionary1) - .IsEquivalentTo((IEnumerable>)dictionary2); + .IsEquivalentTo(dictionary2); } [Test] diff --git a/TUnit.Assertions.Tests/Old/DictionaryAssertionTests.cs b/TUnit.Assertions.Tests/Old/DictionaryAssertionTests.cs index 499cf6447f..e602b77e8b 100644 --- a/TUnit.Assertions.Tests/Old/DictionaryAssertionTests.cs +++ b/TUnit.Assertions.Tests/Old/DictionaryAssertionTests.cs @@ -32,7 +32,7 @@ public async Task String_ReadOnlyDictionary_Contains_Key() { var dictionary = new ReadDictionary(); - await TUnitAssert.That((IReadOnlyDictionary)dictionary).ContainsKey("Blah"); + await TUnitAssert.That(dictionary).ContainsKey("Blah"); } [Test] diff --git a/TUnit.Assertions.Tests/Old/EquivalentAssertionTests.cs b/TUnit.Assertions.Tests/Old/EquivalentAssertionTests.cs index 8b2fc9b555..ea82d335f6 100644 --- a/TUnit.Assertions.Tests/Old/EquivalentAssertionTests.cs +++ b/TUnit.Assertions.Tests/Old/EquivalentAssertionTests.cs @@ -98,7 +98,7 @@ public async Task Different_Dictionaries_Are_Equivalent_With_Different_Ordered_K // Dictionaries are equivalent regardless of key order by default // Cast both to IEnumerable to use collection equivalency await TUnitAssert.That((IEnumerable>)dict1) - .IsEquivalentTo((IEnumerable>)dict2); + .IsEquivalentTo(dict2); } [Test] diff --git a/TUnit.Assertions.Tests/TypeOfTests.cs b/TUnit.Assertions.Tests/TypeOfTests.cs index 630c418606..2af746a842 100644 --- a/TUnit.Assertions.Tests/TypeOfTests.cs +++ b/TUnit.Assertions.Tests/TypeOfTests.cs @@ -242,7 +242,7 @@ public async Task CollectionAssertion_NullableByteArray_CanUseCollectionMethods( // Should be able to use collection assertion methods await Assert.That(nullableBytes).HasCount(5); - await Assert.That(nullableBytes).Contains((byte)3); + await Assert.That(nullableBytes).Contains(3); await Assert.That(nullableBytes).IsInOrder(); } diff --git a/TUnit.Assertions/Assertions/Enums/EnumAssertions.cs b/TUnit.Assertions/Assertions/Enums/EnumAssertions.cs index bcbc0d1eb2..9aec4b26ff 100644 --- a/TUnit.Assertions/Assertions/Enums/EnumAssertions.cs +++ b/TUnit.Assertions/Assertions/Enums/EnumAssertions.cs @@ -30,8 +30,8 @@ protected override Task CheckAsync(EvaluationMetadata me } // Use HasFlag method for enum flag checking - var enumValue = (Enum)(object)value; - var enumFlag = (Enum)(object)_expectedFlag; + var enumValue = (Enum)value; + var enumFlag = (Enum)_expectedFlag; if (enumValue.HasFlag(enumFlag)) { @@ -70,8 +70,8 @@ protected override Task CheckAsync(EvaluationMetadata me return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}: {exception.Message}")); } - var enumValue = (Enum)(object)value; - var enumFlag = (Enum)(object)_unexpectedFlag; + var enumValue = (Enum)value; + var enumFlag = (Enum)_unexpectedFlag; if (!enumValue.HasFlag(enumFlag)) { @@ -106,7 +106,11 @@ protected override Task CheckAsync(EvaluationMetadata me return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}: {exception.Message}")); } +#if NET + if (Enum.IsDefined(value)) +#else if (Enum.IsDefined(typeof(TEnum), value)) +#endif { return Task.FromResult(AssertionResult.Passed); } @@ -139,7 +143,11 @@ protected override Task CheckAsync(EvaluationMetadata me return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}: {exception.Message}")); } +#if NET + if (!Enum.IsDefined(value)) +#else if (!Enum.IsDefined(typeof(TEnum), value)) +#endif { return Task.FromResult(AssertionResult.Passed); } diff --git a/TUnit.Assertions/Conditions/Helpers/StructuralEqualityComparer.cs b/TUnit.Assertions/Conditions/Helpers/StructuralEqualityComparer.cs index 1c0a7448da..af1a1876e9 100644 --- a/TUnit.Assertions/Conditions/Helpers/StructuralEqualityComparer.cs +++ b/TUnit.Assertions/Conditions/Helpers/StructuralEqualityComparer.cs @@ -10,7 +10,7 @@ namespace TUnit.Assertions.Conditions.Helpers; /// For complex objects, performs deep comparison of properties and fields. /// /// The type of objects to compare -[RequiresDynamicCode("Structural equality comparison uses reflection to access object members and is not compatible with AOT")] +[RequiresUnreferencedCode("Structural equality comparison uses reflection to access object members and is not compatible with AOT")] public sealed class StructuralEqualityComparer : IEqualityComparer { /// diff --git a/TUnit.Assertions/Conditions/IsEquivalentToAssertion.cs b/TUnit.Assertions/Conditions/IsEquivalentToAssertion.cs index ec82f1e13f..6eaf69bf8b 100644 --- a/TUnit.Assertions/Conditions/IsEquivalentToAssertion.cs +++ b/TUnit.Assertions/Conditions/IsEquivalentToAssertion.cs @@ -14,7 +14,7 @@ namespace TUnit.Assertions.Conditions; /// Inherits from CollectionComparerBasedAssertion to preserve collection type awareness in And/Or chains. /// [AssertionExtension("IsEquivalentTo")] -[RequiresDynamicCode("Collection equivalency uses structural comparison for complex objects, which requires reflection and is not compatible with AOT")] +[RequiresUnreferencedCode("Collection equivalency uses structural comparison for complex objects, which requires reflection and is not compatible with AOT")] public class IsEquivalentToAssertion : CollectionComparerBasedAssertion where TCollection : IEnumerable { diff --git a/TUnit.Assertions/Conditions/NotEquivalentToAssertion.cs b/TUnit.Assertions/Conditions/NotEquivalentToAssertion.cs index 66444cef0d..5fbb18d0d7 100644 --- a/TUnit.Assertions/Conditions/NotEquivalentToAssertion.cs +++ b/TUnit.Assertions/Conditions/NotEquivalentToAssertion.cs @@ -13,7 +13,7 @@ namespace TUnit.Assertions.Conditions; /// Inherits from CollectionComparerBasedAssertion to preserve collection type awareness in And/Or chains. /// [AssertionExtension("IsNotEquivalentTo")] -[RequiresDynamicCode("Collection equivalency uses structural comparison for complex objects, which requires reflection and is not compatible with AOT")] +[RequiresUnreferencedCode("Collection equivalency uses structural comparison for complex objects, which requires reflection and is not compatible with AOT")] public class NotEquivalentToAssertion : CollectionComparerBasedAssertion where TCollection : IEnumerable { diff --git a/TUnit.Assertions/Conditions/NotStructuralEquivalencyAssertion.cs b/TUnit.Assertions/Conditions/NotStructuralEquivalencyAssertion.cs index 50b4ebf588..4e58fd0bb6 100644 --- a/TUnit.Assertions/Conditions/NotStructuralEquivalencyAssertion.cs +++ b/TUnit.Assertions/Conditions/NotStructuralEquivalencyAssertion.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Text; using TUnit.Assertions.Core; @@ -6,6 +7,7 @@ namespace TUnit.Assertions.Conditions; /// /// Asserts that two objects are NOT structurally equivalent. /// +[RequiresUnreferencedCode("Uses reflection for structural equivalency comparison")] public class NotStructuralEquivalencyAssertion : Assertion { private readonly object? _notExpected; diff --git a/TUnit.Assertions/Conditions/StructuralEquivalencyAssertion.cs b/TUnit.Assertions/Conditions/StructuralEquivalencyAssertion.cs index 8eee381ec2..8591bf4da9 100644 --- a/TUnit.Assertions/Conditions/StructuralEquivalencyAssertion.cs +++ b/TUnit.Assertions/Conditions/StructuralEquivalencyAssertion.cs @@ -10,6 +10,7 @@ namespace TUnit.Assertions.Conditions; /// Asserts that two objects are structurally equivalent by comparing their properties and fields. /// Supports partial equivalency and member exclusion. /// +[RequiresUnreferencedCode("Uses reflection to compare object properties and fields.")] public class StructuralEquivalencyAssertion : Assertion { private readonly object? _expected; @@ -166,9 +167,7 @@ internal AssertionResult CompareObjects(object? actual, object? expected, string } // Compare properties and fields -#pragma warning disable IL2072 // GetType() does not preserve DynamicallyAccessedMembers - acceptable for runtime structural comparison var expectedMembers = GetMembersToCompare(expectedType); -#pragma warning restore IL2072 foreach (var member in expectedMembers) { @@ -199,9 +198,7 @@ internal AssertionResult CompareObjects(object? actual, object? expected, string // In partial equivalency mode, skip members that don't exist on actual if (_usePartialEquivalency) { -#pragma warning disable IL2072 // GetType() does not preserve DynamicallyAccessedMembers - acceptable for runtime structural comparison var actualMember = GetMemberInfo(actualType, member.Name); -#pragma warning restore IL2072 if (actualMember == null) { continue; @@ -211,9 +208,7 @@ internal AssertionResult CompareObjects(object? actual, object? expected, string } else { -#pragma warning disable IL2072 // GetType() does not preserve DynamicallyAccessedMembers - acceptable for runtime structural comparison var actualMember = GetMemberInfo(actualType, member.Name); -#pragma warning restore IL2072 if (actualMember == null) { return AssertionResult.Failed($"Property {memberPath} did not match{Environment.NewLine}Expected: {FormatValue(expectedValue)}{Environment.NewLine}Received: null"); @@ -231,9 +226,7 @@ internal AssertionResult CompareObjects(object? actual, object? expected, string // In non-partial mode, check for extra properties on actual if (!_usePartialEquivalency) { -#pragma warning disable IL2072 // GetType() does not preserve DynamicallyAccessedMembers - acceptable for runtime structural comparison var actualMembers = GetMembersToCompare(actualType); -#pragma warning restore IL2072 var expectedMemberNames = new HashSet(expectedMembers.Select(m => m.Name)); foreach (var member in actualMembers) diff --git a/TUnit.Assertions/Extensions/AssertionExtensions.cs b/TUnit.Assertions/Extensions/AssertionExtensions.cs index 55d966c3d5..aa2eedb6b1 100644 --- a/TUnit.Assertions/Extensions/AssertionExtensions.cs +++ b/TUnit.Assertions/Extensions/AssertionExtensions.cs @@ -548,6 +548,7 @@ public static StringLengthAssertion HasLength( /// Performs deep comparison of properties and fields. /// Supports .WithPartialEquivalency() and .IgnoringMember() for advanced scenarios. /// + [RequiresUnreferencedCode("Uses reflection to compare members")] public static StructuralEquivalencyAssertion IsEquivalentTo( this IAssertionSource source, object? expected, @@ -562,6 +563,7 @@ public static StructuralEquivalencyAssertion IsEquivalentTo( /// Performs deep comparison of properties and fields. /// Supports .WithPartialEquivalency() and .IgnoringMember() for advanced scenarios. /// + [RequiresUnreferencedCode("Uses reflection to compare members")] public static NotStructuralEquivalencyAssertion IsNotEquivalentTo( this IAssertionSource source, object? expected, diff --git a/TUnit.Core.SourceGenerator/CodeGenerators/StaticPropertyInitializationGenerator.cs b/TUnit.Core.SourceGenerator/CodeGenerators/StaticPropertyInitializationGenerator.cs index ffcdfb1a15..5dafe1d93f 100644 --- a/TUnit.Core.SourceGenerator/CodeGenerators/StaticPropertyInitializationGenerator.cs +++ b/TUnit.Core.SourceGenerator/CodeGenerators/StaticPropertyInitializationGenerator.cs @@ -294,7 +294,7 @@ private static void GenerateAsyncDataSourceGeneratorWithPropertyWithAssignment(C writer.Indent(); writer.AppendLine("Type = global::TUnit.Core.Enums.DataGeneratorType.Property,"); writer.AppendLine("TestBuilderContext = new global::TUnit.Core.TestBuilderContextAccessor(globalContext),"); - writer.AppendLine("MembersToGenerate = new global::TUnit.Core.MemberMetadata[] { propertyMetadata },"); + writer.AppendLine("MembersToGenerate = new global::TUnit.Core.IMemberMetadata[] { propertyMetadata },"); writer.AppendLine("TestInformation = null,"); writer.AppendLine("TestSessionId = global::TUnit.Core.TestSessionContext.Current?.Id ?? \"static-property-init\","); writer.AppendLine("TestClassInstance = null,"); diff --git a/TUnit.Core.SourceGenerator/CodeGenerators/Writers/AttributeWriter.cs b/TUnit.Core.SourceGenerator/CodeGenerators/Writers/AttributeWriter.cs index 44dcf0c647..fe12ca38aa 100644 --- a/TUnit.Core.SourceGenerator/CodeGenerators/Writers/AttributeWriter.cs +++ b/TUnit.Core.SourceGenerator/CodeGenerators/Writers/AttributeWriter.cs @@ -20,6 +20,12 @@ public static void WriteAttributes(ICodeWriter sourceCodeWriter, Compilation com // Include attributes without syntax reference (from other assemblies) as long as they have an AttributeClass if (attributeData.ApplicationSyntaxReference is not null || attributeData.AttributeClass is not null) { + // Skip compiler-internal and assembly-level attributes + if (ShouldSkipCompilerInternalAttribute(attributeData)) + { + continue; + } + // Skip framework-specific attributes when targeting older frameworks // We determine this by checking if we can compile the attribute if (ShouldSkipFrameworkSpecificAttribute(compilation, attributeData)) @@ -28,8 +34,8 @@ public static void WriteAttributes(ICodeWriter sourceCodeWriter, Compilation com } // Skip attributes with compiler-generated type arguments - if (attributeData.ConstructorArguments.Any(arg => - arg is { Kind: TypedConstantKind.Type, Value: ITypeSymbol typeSymbol } && + if (attributeData.ConstructorArguments.Any(arg => + arg is { Kind: TypedConstantKind.Type, Value: ITypeSymbol typeSymbol } && typeSymbol.IsCompilerGeneratedType())) { continue; @@ -274,4 +280,30 @@ private static bool IsNullableAttribute(string fullyQualifiedName) fullyQualifiedName.Contains("NullablePublicOnlyAttribute"); } + private static bool ShouldSkipCompilerInternalAttribute(AttributeData attributeData) + { + if (attributeData.AttributeClass == null) + { + return false; + } + + var fullyQualifiedName = attributeData.AttributeClass.ToDisplayString(); + + // Skip compiler-internal attributes that should never be re-emitted + // System.Runtime.CompilerServices contains compiler-generated and structural metadata attributes + if (fullyQualifiedName.StartsWith("System.Runtime.CompilerServices.")) + { + return true; + } + + // Skip debugger attributes (compiler-generated for debugging support) + if (fullyQualifiedName.StartsWith("System.Diagnostics.Debugger")) + { + return true; + } + + // Skip ParamArrayAttribute (compiler-generated for params keyword) + return fullyQualifiedName == "System.ParamArrayAttribute"; + } + } diff --git a/TUnit.Core.SourceGenerator/Generators/AotConverterGenerator.cs b/TUnit.Core.SourceGenerator/Generators/AotConverterGenerator.cs index 958a97fa7e..e7f8fe57f8 100644 --- a/TUnit.Core.SourceGenerator/Generators/AotConverterGenerator.cs +++ b/TUnit.Core.SourceGenerator/Generators/AotConverterGenerator.cs @@ -20,51 +20,252 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Select((compilation, _) => { var conversionInfos = new List(); - - foreach (var tree in compilation.SyntaxTrees) - { - var root = tree.GetRoot(); - var semanticModel = compilation.GetSemanticModel(tree); - - var conversionOperators = root.DescendantNodes() - .OfType() - .ToList(); - - foreach (var operatorDecl in conversionOperators) - { - var conversionInfo = GetConversionInfo(operatorDecl, semanticModel); - if (conversionInfo != null) - { - conversionInfos.Add(conversionInfo); - } - } - } - + + // Scan ALL types in the compilation (including source-generated) for conversion operators + // This must come first to ensure we catch all types before filtering + ScanAllTypesInCompilation(compilation, conversionInfos); + + // Scan referenced assemblies for conversion operators + ScanReferencedAssemblies(compilation, conversionInfos); + + // Scan method parameters for closed generic types like OneOf + ScanClosedGenericTypesInParameters(compilation, conversionInfos); + return conversionInfos.ToImmutableArray(); }); context.RegisterSourceOutput(allTypes, GenerateConverters!); } - private ConversionInfo? GetConversionInfo(ConversionOperatorDeclarationSyntax operatorDeclaration, SemanticModel semanticModel) + private void ScanAllTypesInCompilation(Compilation compilation, List conversionInfos) { - var isImplicit = operatorDeclaration.ImplicitOrExplicitKeyword.IsKind(SyntaxKind.ImplicitKeyword); - var isExplicit = operatorDeclaration.ImplicitOrExplicitKeyword.IsKind(SyntaxKind.ExplicitKeyword); - - if (!isImplicit && !isExplicit) + // Scan ALL types declared in this compilation (including source-generated types) + // This catches types generated by other source generators like OneOf.SourceGenerator + var compilationTypes = new HashSet(SymbolEqualityComparer.Default); + + // Scan from the assembly's global namespace - this includes all types in the compilation + CollectTypesFromNamespace(compilation.Assembly.GlobalNamespace, compilationTypes); + + // For each type in the compilation, check if it has conversion operators + foreach (var type in compilationTypes) { - return null; + // Only process public types + if (type.DeclaredAccessibility != Accessibility.Public) + { + continue; + } + + // Special handling for OneOf types - generate converters from base class info + // This works even if OneOf.SourceGenerator hasn't run yet + if (InheritsFromOneOfBase(type, out var oneOfTypeArguments) && oneOfTypeArguments != null) + { + // Generate implicit converters from each type argument to the OneOf type + foreach (var typeArg in oneOfTypeArguments) + { + // Create a synthetic conversion info for Enum4/Enum5/string -> MixedMatrixTestsUnion1 + var syntheticConversion = new ConversionInfo + { + ContainingType = type, + SourceType = typeArg, + TargetType = type, + IsImplicit = true, + MethodSymbol = null! // We'll generate this synthetically + }; + conversionInfos.Add(syntheticConversion); + } + } + + // Get existing conversion operators for this type (in case they're already generated) + var conversionOperators = type.GetMembers() + .OfType() + .Where(m => (m.Name == "op_Implicit" || m.Name == "op_Explicit") && + m.IsStatic && + m.Parameters.Length == 1); + + foreach (var method in conversionOperators) + { + var conversionInfo = GetConversionInfoFromSymbol(method); + if (conversionInfo != null) + { + conversionInfos.Add(conversionInfo); + } + } } + } + + private bool InheritsFromOneOfBase(INamedTypeSymbol type, out ImmutableArray? typeArguments) + { + typeArguments = null; - var methodSymbol = semanticModel.GetDeclaredSymbol(operatorDeclaration) as IMethodSymbol; - if (methodSymbol == null || !methodSymbol.IsStatic || methodSymbol.Parameters.Length != 1) + var currentType = type.BaseType; + while (currentType != null) { - return null; + // Check if this is OneOfBase + if (currentType.Name == "OneOfBase" && currentType.ContainingNamespace?.ToDisplayString() == "OneOf") + { + typeArguments = currentType.TypeArguments; + return true; + } + currentType = currentType.BaseType; + } + + return false; + } + + private void ScanClosedGenericTypesInParameters(Compilation compilation, List conversionInfos) + { + // Find all closed generic types used in method parameters + var closedGenericTypesInUse = new HashSet(SymbolEqualityComparer.Default); + + foreach (var tree in compilation.SyntaxTrees) + { + var semanticModel = compilation.GetSemanticModel(tree); + var root = tree.GetRoot(); + + // Find all method declarations + var methods = root.DescendantNodes() + .OfType(); + + foreach (var method in methods) + { + var methodSymbol = semanticModel.GetDeclaredSymbol(method); + if (methodSymbol == null) + { + continue; + } + + // Collect parameter types + foreach (var parameter in methodSymbol.Parameters) + { + CollectClosedGenericTypes(parameter.Type, closedGenericTypesInUse); + } + } + } + + // For each closed generic type, check if it has conversion operators + foreach (var type in closedGenericTypesInUse) + { + // Get all conversion operators from this type + var conversionOperators = type.GetMembers() + .OfType() + .Where(m => (m.Name == "op_Implicit" || m.Name == "op_Explicit") && + m.IsStatic && + m.Parameters.Length == 1); + + foreach (var method in conversionOperators) + { + var conversionInfo = GetConversionInfoFromSymbol(method); + if (conversionInfo != null) + { + conversionInfos.Add(conversionInfo); + } + } + } + } + + private void CollectClosedGenericTypes(ITypeSymbol type, HashSet types) + { + if (type is INamedTypeSymbol { IsGenericType: true } namedType && !namedType.IsUnboundGenericType) + { + types.Add(namedType); + + // Recursively collect type arguments + foreach (var typeArg in namedType.TypeArguments) + { + CollectClosedGenericTypes(typeArg, types); + } + } + + // Handle arrays + if (type is IArrayTypeSymbol arrayType) + { + CollectClosedGenericTypes(arrayType.ElementType, types); + } + } + + private void ScanReferencedAssemblies(Compilation compilation, List conversionInfos) + { + // Get all types from referenced assemblies + var referencedTypes = new HashSet(SymbolEqualityComparer.Default); + + foreach (var reference in compilation.References) + { + if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol) + { + // Skip System assemblies and other common assemblies that won't have test-relevant converters + var assemblyName = assemblySymbol.Name; + if (assemblyName.StartsWith("System.") || + assemblyName.StartsWith("Microsoft.") || + assemblyName == "mscorlib" || + assemblyName == "netstandard") + { + continue; + } + + CollectTypesFromNamespace(assemblySymbol.GlobalNamespace, referencedTypes); + } + } + + // Find conversion operators in referenced types + foreach (var type in referencedTypes) + { + // Only process public types + if (type.DeclaredAccessibility != Accessibility.Public) + { + continue; + } + + // Get all members and filter for conversion operators + var conversionOperators = type.GetMembers() + .OfType() + .Where(m => (m.Name == "op_Implicit" || m.Name == "op_Explicit") && + m.IsStatic && + m.Parameters.Length == 1); + + foreach (var method in conversionOperators) + { + var conversionInfo = GetConversionInfoFromSymbol(method); + if (conversionInfo != null) + { + conversionInfos.Add(conversionInfo); + } + } } + } + private void CollectTypesFromNamespace(INamespaceSymbol namespaceSymbol, HashSet types) + { + foreach (var member in namespaceSymbol.GetMembers()) + { + if (member is INamedTypeSymbol type) + { + types.Add(type); + + // Recursively collect nested types + CollectNestedTypes(type, types); + } + else if (member is INamespaceSymbol childNamespace) + { + CollectTypesFromNamespace(childNamespace, types); + } + } + } + + private void CollectNestedTypes(INamedTypeSymbol type, HashSet types) + { + foreach (var nestedType in type.GetTypeMembers()) + { + types.Add(nestedType); + CollectNestedTypes(nestedType, types); + } + } + + private ConversionInfo? GetConversionInfoFromSymbol(IMethodSymbol methodSymbol) + { var containingType = methodSymbol.ContainingType; var sourceType = methodSymbol.Parameters[0].Type; var targetType = methodSymbol.ReturnType; + var isImplicit = methodSymbol.Name == "op_Implicit"; // Skip conversion operators with unbound generic type parameters // These cannot be properly represented in AOT converters at runtime @@ -73,15 +274,34 @@ public void Initialize(IncrementalGeneratorInitializationContext context) return null; } + // Skip ref structs (Span, ReadOnlySpan, etc.) - they cannot be boxed to object + if (sourceType.IsRefLikeType || targetType.IsRefLikeType) + { + return null; + } + + // Skip pointer types and void - they cannot be used as object + if (sourceType.TypeKind == TypeKind.Pointer || targetType.TypeKind == TypeKind.Pointer || + sourceType.SpecialType == SpecialType.System_Void || targetType.SpecialType == SpecialType.System_Void) + { + return null; + } + // Skip conversion operators where the containing type is not publicly accessible // The generated code won't be able to reference private/internal types if (containingType.DeclaredAccessibility != Accessibility.Public) { return null; } - - // Also skip if the target type of the conversion is not publicly accessible + + // Also skip if the source or target type is not publicly accessible // (unless it's a built-in type) + if (sourceType is INamedTypeSymbol { SpecialType: SpecialType.None } namedSourceType && + namedSourceType.DeclaredAccessibility != Accessibility.Public) + { + return null; + } + if (targetType is INamedTypeSymbol { SpecialType: SpecialType.None } namedTargetType && namedTargetType.DeclaredAccessibility != Accessibility.Public) { @@ -102,7 +322,7 @@ private void GenerateConverters(SourceProductionContext context, ImmutableArray< { var writer = new CodeWriter(); writer.AppendLine("#nullable enable"); - + if (conversions.IsEmpty) { writer.AppendLine(); @@ -110,24 +330,38 @@ private void GenerateConverters(SourceProductionContext context, ImmutableArray< context.AddSource("AotConverters.g.cs", writer.ToString()); return; } - + + // Deduplicate conversions based on source and target types + var seenConversions = new HashSet<(ITypeSymbol Source, ITypeSymbol Target)>( + new TypePairEqualityComparer()); + var uniqueConversions = new List(); + + foreach (var conversion in conversions) + { + if (conversion == null) + { + continue; + } + + var key = (conversion.SourceType, conversion.TargetType); + if (seenConversions.Add(key)) + { + uniqueConversions.Add(conversion); + } + } + writer.AppendLine(); writer.AppendLine("using System;"); writer.AppendLine("using TUnit.Core.Converters;"); writer.AppendLine(); writer.AppendLine("namespace TUnit.Generated;"); writer.AppendLine(); - + var converterIndex = 0; var registrations = new List(); - - foreach (var conversion in conversions) - { - if (conversion == null) - { - continue; - } + foreach (var conversion in uniqueConversions) + { var converterClassName = $"AotConverter_{converterIndex++}"; var sourceTypeName = conversion.SourceType.GloballyQualified(); var targetTypeName = conversion.TargetType.GloballyQualified(); @@ -143,12 +377,25 @@ private void GenerateConverters(SourceProductionContext context, ImmutableArray< writer.AppendLine("public object? Convert(object? value)"); writer.AppendLine("{"); writer.Indent(); - + writer.AppendLine("if (value == null) return null;"); - writer.AppendLine($"if (value is {sourceTypeName} typedValue)"); + + // For nullable value types, we need to use the underlying type in the pattern + // because you can't use nullable types in patterns in older C# versions + var sourceType = conversion.SourceType; + var underlyingType = sourceType.IsValueType && sourceType is INamedTypeSymbol named && named.OriginalDefinition?.SpecialType == SpecialType.System_Nullable_T + ? ((INamedTypeSymbol)sourceType).TypeArguments[0] + : sourceType; + + var patternTypeName = underlyingType.GloballyQualified(); + + writer.AppendLine($"if (value is {patternTypeName} typedValue)"); writer.AppendLine("{"); writer.Indent(); + + // Use regular cast syntax - it works fine in AOT when types are known at compile-time writer.AppendLine($"return ({targetTypeName})typedValue;"); + writer.Unindent(); writer.AppendLine("}"); writer.AppendLine("return value; // Return original value if type doesn't match"); @@ -196,4 +443,24 @@ private class ConversionInfo public required bool IsImplicit { get; init; } public required IMethodSymbol MethodSymbol { get; init; } } + + private class TypePairEqualityComparer : IEqualityComparer<(ITypeSymbol Source, ITypeSymbol Target)> + { + public bool Equals((ITypeSymbol Source, ITypeSymbol Target) x, (ITypeSymbol Source, ITypeSymbol Target) y) + { + return SymbolEqualityComparer.Default.Equals(x.Source, y.Source) && + SymbolEqualityComparer.Default.Equals(x.Target, y.Target); + } + + public int GetHashCode((ITypeSymbol Source, ITypeSymbol Target) obj) + { + unchecked + { + var hash = 17; + hash = hash * 31 + SymbolEqualityComparer.Default.GetHashCode(obj.Source); + hash = hash * 31 + SymbolEqualityComparer.Default.GetHashCode(obj.Target); + return hash; + } + } + } } \ No newline at end of file diff --git a/TUnit.Core.SourceGenerator/Generators/PropertyInjectionSourceGenerator.cs b/TUnit.Core.SourceGenerator/Generators/PropertyInjectionSourceGenerator.cs index 39327816d3..18a68b1e70 100644 --- a/TUnit.Core.SourceGenerator/Generators/PropertyInjectionSourceGenerator.cs +++ b/TUnit.Core.SourceGenerator/Generators/PropertyInjectionSourceGenerator.cs @@ -119,7 +119,7 @@ private static bool CanSetProperty(IPropertySymbol property) private static bool IsPubliclyAccessible(INamedTypeSymbol typeSymbol) { // Check if the type itself is public or internal - if (typeSymbol.DeclaredAccessibility != Accessibility.Public && + if (typeSymbol.DeclaredAccessibility != Accessibility.Public && typeSymbol.DeclaredAccessibility != Accessibility.Internal) { return false; @@ -130,7 +130,7 @@ private static bool IsPubliclyAccessible(INamedTypeSymbol typeSymbol) var containingType = typeSymbol.ContainingType; while (containingType != null) { - if (containingType.DeclaredAccessibility != Accessibility.Public && + if (containingType.DeclaredAccessibility != Accessibility.Public && containingType.DeclaredAccessibility != Accessibility.Internal) { return false; @@ -161,10 +161,10 @@ private static void GenerateIndividualPropertyInjectionSource(SourceProductionCo var sourceBuilder = new StringBuilder(); WriteFileHeader(sourceBuilder); - + // Generate individual module initializer for this class GenerateIndividualModuleInitializer(sourceBuilder, classInfo, sourceClassName); - + // Generate property source for this class GeneratePropertySource(sourceBuilder, classInfo, sourceClassName); @@ -188,7 +188,7 @@ private static string GetSafeClassName(INamedTypeSymbol classSymbol) private static void GenerateIndividualModuleInitializer(StringBuilder sb, ClassWithDataSourceProperties classInfo, string sourceClassName) { var safeName = GetSafeClassName(classInfo.ClassSymbol); - + sb.AppendLine($"internal static class {safeName}_PropertyInjectionInitializer"); sb.AppendLine("{"); sb.AppendLine(" [global::System.Runtime.CompilerServices.ModuleInitializer]"); @@ -238,7 +238,7 @@ private static void GenerateUnsafeAccessorMethods(StringBuilder sb, ClassWithDat { if (propInfo.Property.SetMethod?.IsInitOnly == true) { - var propertyType = propInfo.Property.Type.ToDisplayString(); + var propertyType = GetNonNullableTypeName(propInfo.Property.Type); var backingFieldName = $"<{propInfo.Property.Name}>k__BackingField"; // Use the property's containing type for the UnsafeAccessor, not the derived class @@ -276,7 +276,7 @@ private static void GenerateGetPropertyMetadata(StringBuilder sb, ClassWithDataS private static void GeneratePropertyMetadata(StringBuilder sb, PropertyWithDataSourceAttribute propInfo, INamedTypeSymbol classSymbol, string classTypeName) { var propertyName = propInfo.Property.Name; - var propertyType = propInfo.Property.Type.ToDisplayString(); + var propertyType = GetNonNullableTypeName(propInfo.Property.Type); var propertyTypeForTypeof = GetNonNullableTypeString(propInfo.Property.Type); var attributeTypeName = propInfo.DataSourceAttribute.AttributeClass!.ToDisplayString(); var attributeClass = propInfo.DataSourceAttribute.AttributeClass!; @@ -324,7 +324,7 @@ private static void GenerateDataSourceCreation(StringBuilder sb, AttributeData a // which is called by PropertyDataResolver.GetInitializedDataSourceAsync sb.AppendLine(" return dataSource;"); } - + private static bool IsCustomDataSource(INamedTypeSymbol attributeClass) { // Check if this class implements IDataSourceAttribute @@ -339,7 +339,7 @@ private static void GeneratePropertySetting(StringBuilder sb, PropertyWithDataSo sb.AppendLine("#if NET8_0_OR_GREATER"); // Cast to the property's containing type if needed - var containingType = propInfo.Property.ContainingType.ToDisplayString(); + var containingType = GetNonNullableTypeName(propInfo.Property.ContainingType); if (containingType != classTypeName) { sb.AppendLine($" Get{propInfo.Property.Name}BackingField(({containingType}){instanceVariableName}) = {castExpression};"); @@ -349,14 +349,14 @@ private static void GeneratePropertySetting(StringBuilder sb, PropertyWithDataSo sb.AppendLine($" Get{propInfo.Property.Name}BackingField({instanceVariableName}) = {castExpression};"); } sb.AppendLine("#else"); - sb.AppendLine($" var backingField = typeof({propInfo.Property.ContainingType.ToDisplayString()}).GetField(\"<{propInfo.Property.Name}>k__BackingField\","); + sb.AppendLine($" var backingField = typeof({GetNonNullableTypeName(propInfo.Property.ContainingType)}).GetField(\"<{propInfo.Property.Name}>k__BackingField\","); sb.AppendLine(" global::System.Reflection.BindingFlags.Instance | global::System.Reflection.BindingFlags.NonPublic);"); - sb.AppendLine($" backingField?.SetValue({instanceVariableName}, value);"); + sb.AppendLine($" backingField.SetValue({instanceVariableName}, value);"); sb.AppendLine("#endif"); } else if (propInfo.Property.IsStatic) { - var className = propInfo.Property.ContainingType.ToDisplayString(); + var className = GetNonNullableTypeName(propInfo.Property.ContainingType); var castExpression = GetPropertyCastExpression(propInfo.Property, propertyType); sb.AppendLine($" {className}.{propInfo.Property.Name} = {castExpression};"); } @@ -428,16 +428,16 @@ private static string GetNonNullableTypeString(ITypeSymbol typeSymbol) { if (typeSymbol is INamedTypeSymbol { IsReferenceType: true }) { - return typeSymbol.WithNullableAnnotation(NullableAnnotation.NotAnnotated).ToDisplayString(); + return typeSymbol.WithNullableAnnotation(NullableAnnotation.NotAnnotated).GloballyQualified(); } } if (typeSymbol is INamedTypeSymbol { IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T } namedType) { - return namedType.TypeArguments[0].ToDisplayString(); + return namedType.TypeArguments[0].GloballyQualified(); } - var displayString = typeSymbol.ToDisplayString(); + var displayString = typeSymbol.GloballyQualified(); if (displayString.EndsWith("?")) { @@ -446,6 +446,9 @@ private static string GetNonNullableTypeString(ITypeSymbol typeSymbol) return displayString; } + + // Alias for consistency + private static string GetNonNullableTypeName(ITypeSymbol typeSymbol) => GetNonNullableTypeString(typeSymbol); } internal sealed class ClassWithDataSourceProperties diff --git a/TUnit.Core.SourceGenerator/Helpers/GenericTypeInference.cs b/TUnit.Core.SourceGenerator/Helpers/GenericTypeInference.cs index 547bfe31b2..52cfbbd9ce 100644 --- a/TUnit.Core.SourceGenerator/Helpers/GenericTypeInference.cs +++ b/TUnit.Core.SourceGenerator/Helpers/GenericTypeInference.cs @@ -166,7 +166,7 @@ internal static class GenericTypeInference { // Look for IInfersType in the attribute's interfaces var infersTypeInterface = attr.AttributeClass.AllInterfaces - .FirstOrDefault(i => ((ISymbol)i).GloballyQualifiedNonGeneric() == "global::TUnit.Core.Interfaces.IInfersType" && + .FirstOrDefault(i => i.GloballyQualifiedNonGeneric() == "global::TUnit.Core.Interfaces.IInfersType" && i.IsGenericType && i.TypeArguments.Length == 1); diff --git a/TUnit.Core.Tests/Helpers/ArgumentFormatterTests.cs b/TUnit.Core.Tests/Helpers/ArgumentFormatterTests.cs index 0ea03ce2af..e0df369cf3 100644 --- a/TUnit.Core.Tests/Helpers/ArgumentFormatterTests.cs +++ b/TUnit.Core.Tests/Helpers/ArgumentFormatterTests.cs @@ -58,9 +58,39 @@ public void FormatDefault_LargeTuple_FormatsAllElements() public void FormatDefault_TupleWithNull_HandlesNullCorrectly() { var tuple = (1, null, "test"); - + var result = ArgumentFormatter.Format(tuple, []); - + Assert.That(result, Is.EqualTo("(1, null, test)")); } + + [Test] + public void FormatDefault_StringWithDots_EscapesDotsWithMiddleDot() + { + var stringWithDots = "1.2.3"; + + var result = ArgumentFormatter.Format(stringWithDots, []); + + Assert.That(result, Is.EqualTo("1·2·3")); + } + + [Test] + public void FormatDefault_StringWithoutDots_ReturnsUnchanged() + { + var stringWithoutDots = "hello world"; + + var result = ArgumentFormatter.Format(stringWithoutDots, []); + + Assert.That(result, Is.EqualTo("hello world")); + } + + [Test] + public void FormatArguments_WithStringsContainingDots_EscapesDots() + { + var args = new object?[] { "hello", "with.dot", "1.2.3" }; + + var result = ArgumentFormatter.FormatArguments(args); + + Assert.That(result, Is.EqualTo("hello, with·dot, 1·2·3")); + } } \ No newline at end of file diff --git a/TUnit.Core/AotCompatibility/GenericTestRegistry.cs b/TUnit.Core/AotCompatibility/GenericTestRegistry.cs index 3e5a8f1b74..35e3955885 100644 --- a/TUnit.Core/AotCompatibility/GenericTestRegistry.cs +++ b/TUnit.Core/AotCompatibility/GenericTestRegistry.cs @@ -91,7 +91,7 @@ public static void MarkAsAotCompatible(MethodInfo method) AotCompatibleMethods.Add(method); } - private static readonly HashSet AotCompatibleMethods = new(); + private static readonly HashSet AotCompatibleMethods = []; /// /// Checks if a method has been marked as AOT-compatible. diff --git a/TUnit.Core/AsyncConvert.cs b/TUnit.Core/AsyncConvert.cs index d793369cf3..bb660a847b 100644 --- a/TUnit.Core/AsyncConvert.cs +++ b/TUnit.Core/AsyncConvert.cs @@ -55,10 +55,7 @@ public static async ValueTask Convert(Func action) | MethodImplOptions.AggressiveOptimization #endif )] - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("ConvertObject uses reflection to handle custom awaitable types and F# async. For AOT compatibility, use Task or ValueTask directly.")] - [RequiresDynamicCode("ConvertObject may require dynamic invocation for custom awaitable types. For AOT compatibility, use Task or ValueTask directly.")] - #endif + public static async ValueTask ConvertObject(object? invoke) { if (invoke is Delegate @delegate) @@ -116,17 +113,23 @@ public static async ValueTask ConvertObject(object? invoke) } } - [System.Diagnostics.CodeAnalysis.DynamicDependency(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods, "Microsoft.FSharp.Control.FSharpAsync", "FSharp.Core")] - [System.Diagnostics.CodeAnalysis.DynamicDependency("StartAsTask", "Microsoft.FSharp.Control.FSharpAsync", "FSharp.Core")] - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("F# async support requires FSharp.Core types and reflection. For AOT, use Task-based APIs.")] - [RequiresDynamicCode("F# async interop requires MakeGenericMethod. For AOT, use Task-based APIs.")] - #endif + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "Microsoft.FSharp.Control.FSharpAsync", "FSharp.Core")] + [DynamicDependency("StartAsTask", "Microsoft.FSharp.Control.FSharpAsync", "FSharp.Core")] + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic access otherwise can break functionality when trimming application code")] + [UnconditionalSuppressMessage("Trimming", "IL2060:Call to \'System.Reflection.MethodInfo.MakeGenericMethod\' can not be statically analyzed. It\'s not possible to guarantee the availability of requirements of the generic method.")] + [UnconditionalSuppressMessage("Trimming", "IL2072:Target parameter argument does not satisfy \'DynamicallyAccessedMembersAttribute\' in call to target method. The return value of the source method does not have matching annotations.")] + [UnconditionalSuppressMessage("Trimming", "IL2077:Target parameter argument does not satisfy \'DynamicallyAccessedMembersAttribute\' in call to target method. The source field does not have matching annotations.")] + [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with \'RequiresDynamicCodeAttribute\' may break functionality when AOT compiling.")] private static ValueTask StartAsFSharpTask(object invoke, Type type) { var startAsTaskOpenGenericMethod = (_fSharpAsyncType ??= type.Assembly.GetType("Microsoft.FSharp.Control.FSharpAsync"))! .GetRuntimeMethods() - .First(m => m.Name == "StartAsTask"); + .FirstOrDefault(m => m.Name == "StartAsTask"); + + if (startAsTaskOpenGenericMethod is null) + { + return default; + } var fSharpTask = (Task) startAsTaskOpenGenericMethod.MakeGenericMethod(type.GetGenericArguments()[0]) .Invoke(null, [invoke, null, null])!; @@ -143,16 +146,11 @@ private static ValueTask StartAsFSharpTask(object invoke, Type type) /// /// Safely invokes F# async conversion with proper suppression for the call site. /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("F# async is an optional feature. AOT applications should use Task-based APIs.")] - [RequiresDynamicCode("F# async requires runtime code generation. This is documented as not AOT-compatible.")] - #endif private static async ValueTask StartAsFSharpTaskSafely(object invoke, Type type) { if (IsFSharpAsyncSupported()) { await StartAsFSharpTask(invoke, type); - return; } } @@ -181,10 +179,7 @@ private static bool IsFSharpAsyncSupported() } } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("GetAwaiter pattern detection requires reflection for custom awaitable types. For AOT, use Task/ValueTask.")] - [RequiresDynamicCode("Custom awaitable handling may require dynamic invocation. For AOT, use Task/ValueTask.")] - #endif + [UnconditionalSuppressMessage("Trimming", "IL2075:\'this\' argument does not satisfy \'DynamicallyAccessedMembersAttribute\' in call to target method. The return value of the source method does not have matching annotations.")] public static bool TryGetAwaitableTask(object awaitable, [NotNullWhen(true)] out Task? task) { var getAwaiter = awaitable.GetType().GetMethod("GetAwaiter", Type.EmptyTypes); diff --git a/TUnit.Core/Attributes/TestData/AsyncDataSourceGeneratorAttribute.cs b/TUnit.Core/Attributes/TestData/AsyncDataSourceGeneratorAttribute.cs index 3bb6cf0ff4..8045badda6 100644 --- a/TUnit.Core/Attributes/TestData/AsyncDataSourceGeneratorAttribute.cs +++ b/TUnit.Core/Attributes/TestData/AsyncDataSourceGeneratorAttribute.cs @@ -3,7 +3,7 @@ namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)] -public abstract class AsyncDataSourceGeneratorAttribute<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] T> : TypedDataSourceAttribute +public abstract class AsyncDataSourceGeneratorAttribute<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T> : TypedDataSourceAttribute { protected abstract IAsyncEnumerable>> GenerateDataSourcesAsync(DataGeneratorMetadata dataGeneratorMetadata); @@ -18,9 +18,9 @@ public sealed override async IAsyncEnumerable>> GetTypedDataRowsAsy [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] public abstract class AsyncDataSourceGeneratorAttribute< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T1, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T2> : TypedDataSourceAttribute<(T1, T2)> { protected abstract IAsyncEnumerable>> GenerateDataSourcesAsync(DataGeneratorMetadata dataGeneratorMetadata); @@ -36,11 +36,11 @@ public abstract class AsyncDataSourceGeneratorAttribute< [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] public abstract class AsyncDataSourceGeneratorAttribute< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T1, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T2, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T3> : TypedDataSourceAttribute<(T1, T2, T3)> { protected abstract IAsyncEnumerable>> GenerateDataSourcesAsync(DataGeneratorMetadata dataGeneratorMetadata); @@ -56,13 +56,13 @@ public abstract class AsyncDataSourceGeneratorAttribute< [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] public abstract class AsyncDataSourceGeneratorAttribute< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T1, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T2, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T3, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T4> : TypedDataSourceAttribute<(T1, T2, T3, T4)> { protected abstract IAsyncEnumerable>> GenerateDataSourcesAsync(DataGeneratorMetadata dataGeneratorMetadata); @@ -78,15 +78,15 @@ public abstract class AsyncDataSourceGeneratorAttribute< [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] public abstract class AsyncDataSourceGeneratorAttribute< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T1, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T2, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T3, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T4, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T5> : TypedDataSourceAttribute<(T1, T2, T3, T4, T5)> { protected abstract IAsyncEnumerable>> GenerateDataSourcesAsync(DataGeneratorMetadata dataGeneratorMetadata); diff --git a/TUnit.Core/Attributes/TestData/AsyncUntypedDataSourceSourceGeneratorAttribute.cs b/TUnit.Core/Attributes/TestData/AsyncUntypedDataSourceSourceGeneratorAttribute.cs index e483358c28..d2bf7ce4c8 100644 --- a/TUnit.Core/Attributes/TestData/AsyncUntypedDataSourceSourceGeneratorAttribute.cs +++ b/TUnit.Core/Attributes/TestData/AsyncUntypedDataSourceSourceGeneratorAttribute.cs @@ -3,10 +3,6 @@ namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("AsyncUntypedDataSourceGeneratorAttribute requires dynamic code generation for runtime data source creation. Consider using strongly-typed AsyncDataSourceGeneratorAttribute overloads for AOT compatibility.")] -[RequiresUnreferencedCode("AsyncUntypedDataSourceGeneratorAttribute may require unreferenced code for runtime data source creation. Consider using strongly-typed AsyncDataSourceGeneratorAttribute overloads for AOT compatibility.")] -#endif public abstract class AsyncUntypedDataSourceGeneratorAttribute : Attribute, IAsyncUntypedDataSourceGeneratorAttribute { protected abstract IAsyncEnumerable>> GenerateDataSourcesAsync(DataGeneratorMetadata dataGeneratorMetadata); diff --git a/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute.cs b/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute.cs index 371ab024ba..f1fc523760 100644 --- a/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute.cs +++ b/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute.cs @@ -4,7 +4,7 @@ namespace TUnit.Core; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)] -public sealed class ClassDataSourceAttribute<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T> +public sealed class ClassDataSourceAttribute<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T> : DataSourceGeneratorAttribute { public SharedType Shared { get; set; } = SharedType.None; @@ -25,88 +25,66 @@ protected override IEnumerable> GenerateDataSources(DataGeneratorMetadat } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)] -#if NET6_0_OR_GREATER -[RequiresUnreferencedCode("ClassDataSourceAttribute uses reflection to instantiate and access test data classes. For AOT compatibility, use strongly-typed ClassDataSourceAttribute instead.")] -[RequiresDynamicCode("ClassDataSourceAttribute may require runtime type generation. For AOT compatibility, use strongly-typed ClassDataSourceAttribute instead.")] -#endif public sealed class ClassDataSourceAttribute : UntypedDataSourceGeneratorAttribute { private readonly Type[] _types; - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("ClassDataSourceAttribute uses reflection to instantiate and access test data classes. For AOT compatibility, use strongly-typed ClassDataSourceAttribute instead.")] - [RequiresDynamicCode("ClassDataSourceAttribute may require runtime type generation. For AOT compatibility, use strongly-typed ClassDataSourceAttribute instead.")] - #endif + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Non-params constructor calls params one with proper annotations.")] public ClassDataSourceAttribute( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type) : this([type]) { } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("ClassDataSourceAttribute uses reflection to instantiate and access test data classes. For AOT compatibility, use strongly-typed ClassDataSourceAttribute instead.")] - [RequiresDynamicCode("ClassDataSourceAttribute may require runtime type generation. For AOT compatibility, use strongly-typed ClassDataSourceAttribute instead.")] - #endif + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Non-params constructor calls params one with proper annotations.")] public ClassDataSourceAttribute( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type2) : this([type, type2]) { } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("ClassDataSourceAttribute uses reflection to instantiate and access test data classes. For AOT compatibility, use strongly-typed ClassDataSourceAttribute instead.")] - [RequiresDynamicCode("ClassDataSourceAttribute may require runtime type generation. For AOT compatibility, use strongly-typed ClassDataSourceAttribute instead.")] - #endif + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Non-params constructor calls params one with proper annotations.")] public ClassDataSourceAttribute( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type2, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type3) : this([type, type2, type3]) { } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("ClassDataSourceAttribute uses reflection to instantiate and access test data classes. For AOT compatibility, use strongly-typed ClassDataSourceAttribute instead.")] - [RequiresDynamicCode("ClassDataSourceAttribute may require runtime type generation. For AOT compatibility, use strongly-typed ClassDataSourceAttribute instead.")] - #endif + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Non-params constructor calls params one with proper annotations.")] public ClassDataSourceAttribute( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type2, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type3, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type4) : this([type, type2, type3, type4]) { } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("ClassDataSourceAttribute uses reflection to instantiate and access test data classes. For AOT compatibility, use strongly-typed ClassDataSourceAttribute instead.")] - [RequiresDynamicCode("ClassDataSourceAttribute may require runtime type generation. For AOT compatibility, use strongly-typed ClassDataSourceAttribute instead.")] - #endif + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Non-params constructor calls params one with proper annotations.")] public ClassDataSourceAttribute( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type2, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type3, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type4, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type5) : this([type, type2, type3, type4, type5]) { } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Reflection")] - [RequiresDynamicCode("Reflection")] - #endif + [RequiresUnreferencedCode("Uses dynamically accessed types.")] public ClassDataSourceAttribute(params Type[] types) { _types = types; @@ -115,10 +93,8 @@ public ClassDataSourceAttribute(params Type[] types) public SharedType[] Shared { get; set; } = [SharedType.None]; public string[] Keys { get; set; } = []; - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Reflection")] - [RequiresDynamicCode("Reflection")] - #endif + [UnconditionalSuppressMessage("Trimming", "IL2062:The parameter of method has a DynamicallyAccessedMembersAttribute, but the value passed to it can not be statically analyzed.", + Justification = "Constructor parameter is annotated with DynamicallyAccessedMembers, so _types elements have the required annotations.")] protected override IEnumerable> GenerateDataSources(DataGeneratorMetadata dataGeneratorMetadata) { yield return () => diff --git a/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute_2.cs b/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute_2.cs index 402427739d..b6a06a98d9 100644 --- a/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute_2.cs +++ b/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute_2.cs @@ -5,8 +5,8 @@ namespace TUnit.Core; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] public sealed class ClassDataSourceAttribute< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T1, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T2> + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T1, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T2> : DataSourceGeneratorAttribute where T1 : new() where T2 : new() diff --git a/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute_3.cs b/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute_3.cs index 62393fd596..010a7dbde6 100644 --- a/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute_3.cs +++ b/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute_3.cs @@ -5,9 +5,9 @@ namespace TUnit.Core; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] public sealed class ClassDataSourceAttribute< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T1, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T2, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T3> + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T1, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T2, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T3> : DataSourceGeneratorAttribute where T1 : new() where T2 : new() diff --git a/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute_4.cs b/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute_4.cs index 221c47c382..ed4c73fa1c 100644 --- a/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute_4.cs +++ b/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute_4.cs @@ -5,10 +5,10 @@ namespace TUnit.Core; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] public sealed class ClassDataSourceAttribute< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T1, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T2, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T3, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T4> + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T1, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T2, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T3, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T4> : DataSourceGeneratorAttribute where T1 : new() where T2 : new() diff --git a/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute_5.cs b/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute_5.cs index 1c74d6da23..f65b362fd5 100644 --- a/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute_5.cs +++ b/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute_5.cs @@ -5,11 +5,11 @@ namespace TUnit.Core; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] public sealed class ClassDataSourceAttribute< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T1, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T2, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T3, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T4, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T5> + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T1, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T2, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T3, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T4, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T5> : DataSourceGeneratorAttribute where T1 : new() where T2 : new() diff --git a/TUnit.Core/Attributes/TestData/ClassDataSources.cs b/TUnit.Core/Attributes/TestData/ClassDataSources.cs index b66dd6cdfd..a933e860b4 100644 --- a/TUnit.Core/Attributes/TestData/ClassDataSources.cs +++ b/TUnit.Core/Attributes/TestData/ClassDataSources.cs @@ -18,7 +18,7 @@ public static ClassDataSources Get(string sessionId) return SourcesPerSession.GetOrAdd(sessionId, static _ => new ClassDataSources()); } - public (T, SharedType, string) GetItemForIndexAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(int index, Type testClassType, SharedType[] sharedTypes, string[] keys, DataGeneratorMetadata dataGeneratorMetadata) where T : new() + public (T, SharedType, string) GetItemForIndexAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(int index, Type testClassType, SharedType[] sharedTypes, string[] keys, DataGeneratorMetadata dataGeneratorMetadata) where T : new() { var shared = sharedTypes.ElementAtOrDefault(index); @@ -39,7 +39,7 @@ private string GetKey(int index, SharedType[] sharedTypes, string[] keys) return keys.ElementAtOrDefault(keyedIndex) ?? throw new ArgumentException($"Key at index {keyedIndex} not found"); } - public T Get<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(SharedType sharedType, Type testClassType, string key, DataGeneratorMetadata dataGeneratorMetadata) + public T Get<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(SharedType sharedType, Type testClassType, string key, DataGeneratorMetadata dataGeneratorMetadata) { return sharedType switch { @@ -52,7 +52,7 @@ private string GetKey(int index, SharedType[] sharedTypes, string[] keys) }; } - public object? Get(SharedType sharedType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type type, Type testClassType, string? key, DataGeneratorMetadata dataGeneratorMetadata) + public object? Get(SharedType sharedType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type, Type testClassType, string? key, DataGeneratorMetadata dataGeneratorMetadata) { return sharedType switch { @@ -66,16 +66,31 @@ private string GetKey(int index, SharedType[] sharedTypes, string[] keys) } [return: NotNull] - private static T Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(DataGeneratorMetadata dataGeneratorMetadata) + private static T Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(DataGeneratorMetadata dataGeneratorMetadata) { return ((T) Create(typeof(T), dataGeneratorMetadata))!; } - private static object Create([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type type, DataGeneratorMetadata dataGeneratorMetadata) + private static object Create([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type, DataGeneratorMetadata dataGeneratorMetadata) { + return CreateWithNestedDependencies(type, dataGeneratorMetadata, recursionDepth: 0); + } + + private const int MaxRecursionDepth = 10; + + [UnconditionalSuppressMessage("Trimming", "IL2072:Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' requirements", + Justification = "PropertyType from PropertyInjectionMetadata has the required DynamicallyAccessedMembers annotations")] + private static object CreateWithNestedDependencies([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type, DataGeneratorMetadata dataGeneratorMetadata, int recursionDepth) + { + if (recursionDepth >= MaxRecursionDepth) + { + throw new InvalidOperationException($"Maximum recursion depth ({MaxRecursionDepth}) exceeded when creating nested ClassDataSource dependencies. This may indicate a circular dependency."); + } + + object instance; try { - return Activator.CreateInstance(type)!; + instance = Activator.CreateInstance(type)!; } catch (TargetInvocationException targetInvocationException) { @@ -86,5 +101,21 @@ private static object Create([DynamicallyAccessedMembers(DynamicallyAccessedMemb throw; } + + // Populate nested ClassDataSource properties recursively + var propertySource = PropertySourceRegistry.GetSource(type); + if (propertySource?.ShouldInitialize == true) + { + var propertyMetadata = propertySource.GetPropertyMetadata(); + foreach (var metadata in propertyMetadata) + { + // Recursively create the property value using CreateWithNestedDependencies + // This will handle nested ClassDataSource properties + var propertyValue = CreateWithNestedDependencies(metadata.PropertyType, dataGeneratorMetadata, recursionDepth + 1); + metadata.SetProperty(instance, propertyValue); + } + } + + return instance; } } diff --git a/TUnit.Core/Attributes/TestData/DataSourceGeneratorAttribute.cs b/TUnit.Core/Attributes/TestData/DataSourceGeneratorAttribute.cs index 134099ca1b..93ade8f6ab 100644 --- a/TUnit.Core/Attributes/TestData/DataSourceGeneratorAttribute.cs +++ b/TUnit.Core/Attributes/TestData/DataSourceGeneratorAttribute.cs @@ -4,7 +4,7 @@ namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)] public abstract class DataSourceGeneratorAttribute< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T> : AsyncDataSourceGeneratorAttribute { protected abstract IEnumerable> GenerateDataSources(DataGeneratorMetadata dataGeneratorMetadata); @@ -22,9 +22,9 @@ protected override async IAsyncEnumerable>> GenerateDataSourcesAsyn [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] public abstract class DataSourceGeneratorAttribute< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T1, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T2> : AsyncDataSourceGeneratorAttribute { protected abstract IEnumerable> GenerateDataSources(DataGeneratorMetadata dataGeneratorMetadata); @@ -42,11 +42,11 @@ public abstract class DataSourceGeneratorAttribute< [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] public abstract class DataSourceGeneratorAttribute< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T1, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T2, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T3> : AsyncDataSourceGeneratorAttribute { protected abstract IEnumerable> GenerateDataSources(DataGeneratorMetadata dataGeneratorMetadata); @@ -64,13 +64,13 @@ public abstract class DataSourceGeneratorAttribute< [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] public abstract class DataSourceGeneratorAttribute< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T1, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T2, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T3, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T4> : AsyncDataSourceGeneratorAttribute { protected abstract IEnumerable> GenerateDataSources(DataGeneratorMetadata dataGeneratorMetadata); @@ -88,15 +88,15 @@ public abstract class DataSourceGeneratorAttribute< [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] public abstract class DataSourceGeneratorAttribute< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T1, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T2, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T3, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T4, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T5> : AsyncDataSourceGeneratorAttribute { protected abstract IEnumerable> GenerateDataSources(DataGeneratorMetadata dataGeneratorMetadata); diff --git a/TUnit.Core/Attributes/TestData/DependencyInjectionDataSourceSourceAttribute.cs b/TUnit.Core/Attributes/TestData/DependencyInjectionDataSourceSourceAttribute.cs index 817035eba9..3d7ea2468b 100644 --- a/TUnit.Core/Attributes/TestData/DependencyInjectionDataSourceSourceAttribute.cs +++ b/TUnit.Core/Attributes/TestData/DependencyInjectionDataSourceSourceAttribute.cs @@ -2,10 +2,6 @@ namespace TUnit.Core; -#if NET6_0_OR_GREATER -[RequiresDynamicCode("DependencyInjectionDataSourceAttribute requires dynamic code generation for dependency injection container access. This attribute is inherently incompatible with AOT compilation.")] -[RequiresUnreferencedCode("DependencyInjectionDataSourceAttribute may require unreferenced code for dependency injection container access. This attribute is inherently incompatible with AOT compilation.")] -#endif public abstract class DependencyInjectionDataSourceAttribute : UntypedDataSourceGeneratorAttribute { protected override IEnumerable> GenerateDataSources(DataGeneratorMetadata dataGeneratorMetadata) @@ -16,23 +12,27 @@ public abstract class DependencyInjectionDataSourceAttribute : UntypedDa var scope = CreateScope(dataGeneratorMetadata); // Set up disposal for this specific scope in the current test context - if (dataGeneratorMetadata.TestBuilderContext != null) + dataGeneratorMetadata.TestBuilderContext.Current.Events.OnDispose += async (_, _) => { - dataGeneratorMetadata.TestBuilderContext.Current.Events.OnDispose += async (_, _) => + if (scope is IAsyncDisposable asyncDisposable) { - if (scope is IAsyncDisposable asyncDisposable) - { - await asyncDisposable.DisposeAsync().ConfigureAwait(false); - } - else if (scope is IDisposable disposable) - { - disposable.Dispose(); - } - }; - } + await asyncDisposable.DisposeAsync().ConfigureAwait(false); + } + else if (scope is IDisposable disposable) + { + disposable.Dispose(); + } + }; return dataGeneratorMetadata.MembersToGenerate - .Select(m => m.Type) + .Select(m => m switch + { + PropertyMetadata prop => prop.Type, + ParameterMetadata param => param.Type, + ClassMetadata cls => cls.Type, + MethodMetadata method => method.Type, + _ => throw new InvalidOperationException($"Unknown member type: {m.GetType()}") + }) .Select(x => Create(scope, x)) .ToArray(); }; diff --git a/TUnit.Core/Attributes/TestData/MatrixDataSourceAttribute.cs b/TUnit.Core/Attributes/TestData/MatrixDataSourceAttribute.cs index ef6a40bdc8..1d975a8dbc 100644 --- a/TUnit.Core/Attributes/TestData/MatrixDataSourceAttribute.cs +++ b/TUnit.Core/Attributes/TestData/MatrixDataSourceAttribute.cs @@ -5,16 +5,8 @@ namespace TUnit.Core; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] -#if NET6_0_OR_GREATER -[RequiresUnreferencedCode("MatrixDataSource uses reflection to access parameter attributes and test metadata. For AOT compatibility, consider using explicit data sources.")] -[RequiresDynamicCode("MatrixDataSource may process enum types dynamically")] -#endif public sealed class MatrixDataSourceAttribute : UntypedDataSourceGeneratorAttribute, IAccessesInstanceData { - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Matrix generation requires reflection")] - [RequiresDynamicCode("Matrix generation may process enum types dynamically")] - #endif protected override IEnumerable> GenerateDataSources(DataGeneratorMetadata dataGeneratorMetadata) { var parameterInformation = dataGeneratorMetadata @@ -95,9 +87,6 @@ private bool IsExcluded(object?[] exclusion, IEnumerable row) .ToArray(); } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Test parameter types accessed through reflection")] - #endif private IReadOnlyList GetAllArguments(DataGeneratorMetadata dataGeneratorMetadata, ParameterMetadata sourceGeneratedParameterInformation) { @@ -171,13 +160,13 @@ private bool IsExcluded(object?[] exclusion, IEnumerable row) if (resolvedType.IsEnum) { -#if NET - var enumValues = Enum.GetValuesAsUnderlyingType(resolvedType) - .Cast(); +#if NET5_0_OR_GREATER + var enumValues = Enum.GetValuesAsUnderlyingType(resolvedType) + .Cast(); #else - var enumValues = Enum.GetValues(resolvedType) - .Cast(); + var enumValues = Enum.GetValues(resolvedType).Cast(); #endif + if (isNullable) { enumValues = enumValues.Append(null); @@ -188,11 +177,7 @@ private bool IsExcluded(object?[] exclusion, IEnumerable row) } return enumValues -#if NET .Except(matrixAttribute?.Excluding?.Select(e => Convert.ChangeType(e, Enum.GetUnderlyingType(resolvedType))) ?? []) -#else - .Except(matrixAttribute?.Excluding ?? []) -#endif .ToArray(); } diff --git a/TUnit.Core/Attributes/TestData/UntypedDataSourceGeneratorAttribute.cs b/TUnit.Core/Attributes/TestData/UntypedDataSourceGeneratorAttribute.cs index 52c1b3b349..7ec7179063 100644 --- a/TUnit.Core/Attributes/TestData/UntypedDataSourceGeneratorAttribute.cs +++ b/TUnit.Core/Attributes/TestData/UntypedDataSourceGeneratorAttribute.cs @@ -3,10 +3,6 @@ namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)] -#if NET6_0_OR_GREATER -[RequiresDynamicCode("UntypedDataSourceGeneratorAttribute requires dynamic code generation for runtime data source creation. Consider using strongly-typed AsyncDataSourceGeneratorAttribute overloads for AOT compatibility.")] -[RequiresUnreferencedCode("UntypedDataSourceGeneratorAttribute may require unreferenced code for runtime data source creation. Consider using strongly-typed AsyncDataSourceGeneratorAttribute overloads for AOT compatibility.")] -#endif public abstract class UntypedDataSourceGeneratorAttribute : AsyncUntypedDataSourceGeneratorAttribute { protected abstract IEnumerable> GenerateDataSources(DataGeneratorMetadata dataGeneratorMetadata); diff --git a/TUnit.Core/DataGeneratorMetadataCreator.cs b/TUnit.Core/DataGeneratorMetadataCreator.cs index 1d70ca3742..1d86ee4f87 100644 --- a/TUnit.Core/DataGeneratorMetadataCreator.cs +++ b/TUnit.Core/DataGeneratorMetadataCreator.cs @@ -23,7 +23,7 @@ public static DataGeneratorMetadata CreateDataGeneratorMetadata( if (generatorType == DataGeneratorType.TestParameters && parametersToGenerate.Length > 0) { var lastParam = parametersToGenerate[parametersToGenerate.Length - 1]; - if (lastParam.Type == typeof(System.Threading.CancellationToken)) + if (lastParam.Type == typeof(CancellationToken)) { var newArray = new ParameterMetadata[parametersToGenerate.Length - 1]; Array.Copy(parametersToGenerate, 0, newArray, 0, parametersToGenerate.Length - 1); @@ -31,7 +31,7 @@ public static DataGeneratorMetadata CreateDataGeneratorMetadata( } } - MemberMetadata[] membersToGenerate; + IMemberMetadata[] membersToGenerate; if (generatorType == DataGeneratorType.Property) { if (testMetadata.PropertyDataSources.Length > 0) @@ -82,7 +82,7 @@ public static DataGeneratorMetadata CreateForReflectionDiscovery( string testSessionId = "reflection-discovery") { // Determine which members we're generating for based on type - MemberMetadata[] membersToGenerate = generatorType switch + IMemberMetadata[] membersToGenerate = generatorType switch { DataGeneratorType.ClassParameters => methodMetadata.Class.Parameters, DataGeneratorType.TestParameters => FilterOutCancellationToken(methodMetadata.Parameters), @@ -200,10 +200,15 @@ public static DataGeneratorMetadata CreateForPropertyInjection( /// /// Creates DataGeneratorMetadata for property injection using PropertyInfo (reflection mode). + /// This method is only called in reflection mode, not in source-generated/AOT scenarios. /// #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Property types are resolved through reflection")] - #endif + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access", + Justification = "This method is only used in reflection mode. In AOT/source-gen mode, property injection uses compile-time generated PropertyMetadata.")] + [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling", + Justification = "This method is only used in reflection mode. In AOT/source-gen mode, property injection uses compile-time generated PropertyMetadata.")] + [UnconditionalSuppressMessage("Trimming", "IL2072:Target parameter argument does not satisfy \'DynamicallyAccessedMembersAttribute\' in call to target method. The return value of the source method does not have matching annotations.")] +#endif public static DataGeneratorMetadata CreateForPropertyInjection( PropertyInfo property, Type containingType, @@ -240,7 +245,7 @@ private static ParameterMetadata[] FilterOutCancellationToken(ParameterMetadata[ if (parameters.Length > 0) { var lastParam = parameters[parameters.Length - 1]; - if (lastParam.Type == typeof(System.Threading.CancellationToken)) + if (lastParam.Type == typeof(CancellationToken)) { var newArray = new ParameterMetadata[parameters.Length - 1]; Array.Copy(parameters, 0, newArray, 0, parameters.Length - 1); @@ -251,8 +256,13 @@ private static ParameterMetadata[] FilterOutCancellationToken(ParameterMetadata[ } #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Class metadata creation requires reflection")] - #endif + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access", + Justification = "This helper is only used in reflection mode. In AOT/source-gen mode, class metadata is generated at compile time.")] + [UnconditionalSuppressMessage("Trimming", "IL2070:Target method return value does not satisfy 'DynamicallyAccessedMembersAttribute'", + Justification = "This helper is only used in reflection mode. In AOT/source-gen mode, class metadata is generated at compile time.")] + [UnconditionalSuppressMessage("Trimming", "IL2072:Target parameter argument does not satisfy \'DynamicallyAccessedMembersAttribute\' in call to target method. The return value of the source method does not have matching annotations.")] + [UnconditionalSuppressMessage("Trimming", "IL2067:Target parameter argument does not satisfy \'DynamicallyAccessedMembersAttribute\' in call to target method. The parameter of method does not have matching annotations.")] +#endif private static ClassMetadata GetClassMetadataForType(Type type) { return ClassMetadata.GetOrAdd(type.FullName ?? type.Name, () => @@ -265,7 +275,7 @@ private static ClassMetadata GetClassMetadataForType(Type type) Name = p.Name ?? $"param{i}", TypeInfo = new ConcreteType(p.ParameterType), ReflectionInfo = p - }).ToArray() ?? Array.Empty(); + }).ToArray() ?? []; return new ClassMetadata { diff --git a/TUnit.Core/DataSources/DataSourceProcessor.cs b/TUnit.Core/DataSources/DataSourceProcessor.cs deleted file mode 100644 index 98d8c131fd..0000000000 --- a/TUnit.Core/DataSources/DataSourceProcessor.cs +++ /dev/null @@ -1,390 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Reflection; - -namespace TUnit.Core.DataSources; - -/// -/// Shared processor for data sources that ensures consistent behavior between AOT and reflection modes -/// -public static class DataSourceProcessor -{ - /// - /// AOT-compatible method to process typed generator items - /// - public static async Task> ProcessTypedGeneratorItemAsync(Func> taskFunc) - { - var items = new List(); - var data = await taskFunc().ConfigureAwait(false); - - if (data is object?[] array) - { - items.Add(array); - } - else if (data != null) - { - items.Add([(object?)data]); - } - - return items; - } - - /// - /// AOT-compatible method to process typed array generator items - /// - public static async Task> ProcessTypedArrayGeneratorItemAsync(Func> taskFunc) - { - var items = new List(); - var data = await taskFunc().ConfigureAwait(false); - - if (data != null) - { - // Direct allocation - ArrayPool doesn't help here since we need to keep the array - var array = new object?[data.Length]; - for (var i = 0; i < data.Length; i++) - { - array[i] = data[i]; - } - items.Add(array); - } - - return items; - } - - /// - /// Processes a generator result item and extracts the data values - /// This method uses reflection and is only suitable for reflection mode - /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to process data sources")] - [RequiresDynamicCode("This method may create types at runtime")] - #endif - public static async Task> ProcessGeneratorItemAsync(object? item) - { - var items = new List(); - - if (item == null) - { - return items; - } - - // Handle Func> patterns - if (item is Func> taskArrayFunc) - { - var data = await taskArrayFunc().ConfigureAwait(false); - if (data != null) - { - items.Add(data); - } - } - else if (item is Func> taskFunc) - { - var data = await taskFunc().ConfigureAwait(false); - items.Add([data]); - } - // Handle direct Task patterns - else if (item is Task taskArray) - { - var data = await taskArray.ConfigureAwait(false); - if (data != null) - { - items.Add(data); - } - } - else if (item is Task task) - { - var data = await task.ConfigureAwait(false); - items.Add([data]); - } - // Handle synchronous Func patterns - else if (item is Func func) - { - var data = func(); - if (data != null) - { - items.Add(data); - } - } - else if (item is Func singleFunc) - { - var data = singleFunc(); - items.Add([data]); - } - // Handle direct arrays - else if (item is object?[] array) - { - items.Add(array); - } - // Handle single values - else - { - items.Add([item]); - } - - return items; - } - - /// - /// AOT-compatible typed value resolver for known types - /// - public static async Task ResolveTypedValueAsync(Func> taskFunc) - { - return await taskFunc().ConfigureAwait(false); - } - - /// - /// AOT-compatible synchronous typed value resolver - /// - public static object? ResolveTypedValue(Func func) - { - return func(); - } - - /// - /// Resolves a value that might be wrapped in a Func or Task - /// This method uses reflection and is only suitable for reflection mode - /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection to resolve values")] - [RequiresDynamicCode("This method may invoke methods dynamically")] - #endif - public static async Task ResolveValueAsync(object? value) - { - if (value == null) - { - return null; - } - - var type = value.GetType(); - - // Check if it's a Task - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>)) - { - var task = (Task)value; - await task.ConfigureAwait(false); - - var resultProperty = GetTaskResultProperty(type); - return resultProperty?.GetValue(task); - } - - // Check if it's a ValueTask - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ValueTask<>)) - { - // Convert to Task and await - var asTaskMethod = type.GetMethod("AsTask"); - if (asTaskMethod != null) - { - var task = (Task?)asTaskMethod.Invoke(value, null); - if (task != null) - { - await task.ConfigureAwait(false); - var resultProperty = GetTaskResultProperty(task.GetType()); - return resultProperty?.GetValue(task); - } - } - } - - // Check if it's a Func - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Func<>)) - { - var invokeMethod = GetDelegateInvokeMethod(type); - var result = invokeMethod?.Invoke(value, null); - - // Recursively resolve in case Func returns Task - return await ResolveValueAsync(result).ConfigureAwait(false); - } - - // Check for other delegate types that might need invocation - if (typeof(Delegate).IsAssignableFrom(type)) - { - var invokeMethod = GetDelegateInvokeMethod(type); - if (invokeMethod != null && invokeMethod.GetParameters().Length == 0) - { - var result = invokeMethod.Invoke(value, null); - - // Recursively resolve in case delegate returns Task - return await ResolveValueAsync(result).ConfigureAwait(false); - } - } - - return value; - } - - /// - /// Processes method data source results into a consistent format - /// This method uses reflection for tuple processing and is not AOT-compatible - /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("This method uses reflection for tuple processing")] - #endif - public static IEnumerable ProcessMethodDataSourceResult(object? result) - { - if (result == null) - { - yield break; - } - - var resultType = result.GetType(); - - // Handle IEnumerable - if (result is IEnumerable objectArrayEnum) - { - foreach (var item in objectArrayEnum) - { - yield return item ?? [ - ]; - } - yield break; - } - - // Handle IEnumerable (but not string) - if (result is IEnumerable objectEnum && !(result is string)) - { - foreach (var item in objectEnum) - { - yield return [item]; - } - yield break; - } - - // Handle arrays of tuples - if (TryProcessTupleArray(result, resultType)) - { - foreach (var values in ProcessTupleArray(result, resultType)) - { - yield return values; - } - yield break; - } - - // Handle single tuple - if (TryProcessSingleTuple(result, resultType)) - { - yield return ProcessSingleTuple(result, resultType); - yield break; - } - - // Handle direct array - if (result is object?[] directArray) - { - yield return directArray; - yield break; - } - - // Handle IEnumerable (generic catch-all) - if (result is System.Collections.IEnumerable enumerable) - { - var items = new List(); - foreach (var item in enumerable) - { - items.Add(item); - } - - if (items.Count > 0) - { - // Optimize: Use pre-sized array instead of ToArray() - var itemArray = new object?[items.Count]; - items.CopyTo(itemArray, 0); - yield return itemArray; - } - yield break; - } - - // Single value - yield return [result]; - } - - #region Tuple Processing Helpers (Reflection-based, not AOT-compatible) - - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Tuple processing requires reflection")] - #endif - private static bool TryProcessTupleArray(object result, Type resultType) - { - return resultType.IsArray && resultType.GetElementType()?.Name.StartsWith("ValueTuple") == true; - } - - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Tuple processing requires reflection")] - #endif - private static IEnumerable ProcessTupleArray(object result, Type resultType) - { - var array = (Array)result; - foreach (var item in array) - { - if (item != null) - { - var tupleType = item.GetType(); - var fields = GetTupleFields(tupleType); - // Optimize: Pre-allocate array instead of LINQ Select().ToArray() - var values = new object?[fields.Length]; - for (var i = 0; i < fields.Length; i++) - { - values[i] = fields[i].GetValue(item); - } - yield return values; - } - } - } - - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Tuple processing requires reflection")] - #endif - private static bool TryProcessSingleTuple(object result, Type resultType) - { - return resultType.Name.StartsWith("ValueTuple"); - } - - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Tuple processing requires reflection")] - #endif - private static object?[] ProcessSingleTuple(object result, Type resultType) - { - var fields = GetTupleFields(resultType); - // Optimize: Pre-allocate array instead of LINQ Select().ToArray() - var values = new object?[fields.Length]; - for (var i = 0; i < fields.Length; i++) - { - values[i] = fields[i].GetValue(result); - } - return values; - } - - #endregion - - #region Helper Methods with Proper Attribution - - /// - /// Gets the Result property from a Task type with proper AOT attribution - /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Property access requires reflection")] - #endif - private static PropertyInfo? GetTaskResultProperty([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type taskType) - { - return taskType.GetProperty("Result"); - } - - /// - /// Gets the Invoke method from a delegate type with proper AOT attribution - /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Method access requires reflection")] - #endif - private static MethodInfo? GetDelegateInvokeMethod([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type delegateType) - { - return delegateType.GetMethod("Invoke"); - } - - /// - /// Gets fields from a tuple type with proper AOT attribution - /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Field access requires reflection")] - #endif - private static FieldInfo[] GetTupleFields([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type tupleType) - { - return tupleType.GetFields(); - } - - #endregion -} diff --git a/TUnit.Core/DynamicTestBuilderContext.cs b/TUnit.Core/DynamicTestBuilderContext.cs index c827b7a99d..f73e84f4c9 100644 --- a/TUnit.Core/DynamicTestBuilderContext.cs +++ b/TUnit.Core/DynamicTestBuilderContext.cs @@ -23,7 +23,7 @@ public DynamicTestBuilderContext(string filePath, int lineNumber) public IReadOnlyList Tests => _tests.AsReadOnly(); #if NET6_0_OR_GREATER - [RequiresDynamicCode("Adding dynamic tests requires reflection which is not supported in native AOT scenarios.")] + [RequiresUnreferencedCode("Adding dynamic tests requires reflection which is not supported in native AOT scenarios.")] #endif public void AddTest(AbstractDynamicTest test) { diff --git a/TUnit.Core/Extensions/ReflectionExtensions.cs b/TUnit.Core/Extensions/ReflectionExtensions.cs index 2094ddc1c1..07b12e3a66 100644 --- a/TUnit.Core/Extensions/ReflectionExtensions.cs +++ b/TUnit.Core/Extensions/ReflectionExtensions.cs @@ -111,7 +111,6 @@ public static T[] GetCustomAttributesSafe(this ICustomAttributeProvider provi #if NET6_0_OR_GREATER [RequiresUnreferencedCode("Attribute instantiation uses reflection for .NET Framework compatibility")] - [RequiresDynamicCode("Activator.CreateInstance required for attribute instantiation")] #endif private static Attribute[] GetAttributesViaCustomAttributeData(ICustomAttributeProvider provider, Type attributeType, bool inherit) { @@ -177,7 +176,6 @@ private static Attribute[] GetAttributesViaCustomAttributeData(ICustomAttributeP #if NET6_0_OR_GREATER [RequiresUnreferencedCode("Attribute instantiation uses reflection for .NET Framework compatibility")] - [RequiresDynamicCode("Activator.CreateInstance required for attribute instantiation")] #endif private static Attribute? CreateAttributeInstance(CustomAttributeData attributeData) { @@ -203,7 +201,7 @@ private static Attribute[] GetAttributesViaCustomAttributeData(ICustomAttributeP // Check if constructor expects params array var constructor = attributeType.GetConstructors() .FirstOrDefault(c => c.GetParameters().Length == constructorArgs.Length); - + if (constructor != null && constructor.GetParameters().Length == 1) { var param = constructor.GetParameters()[0]; @@ -277,7 +275,7 @@ private static Attribute[] GetAttributesViaCustomAttributeData(ICustomAttributeP return value; } - + /// /// Gets the "Value" property from a type in an AOT-safer manner. /// @@ -288,7 +286,7 @@ private static Attribute[] GetAttributesViaCustomAttributeData(ICustomAttributeP { return type.GetProperty("Value"); } - + /// /// Gets the "Value" property from a runtime type. /// diff --git a/TUnit.Core/Extensions/TestContextExtensions.cs b/TUnit.Core/Extensions/TestContextExtensions.cs index 7dfa3eabb1..4361cf0f50 100644 --- a/TUnit.Core/Extensions/TestContextExtensions.cs +++ b/TUnit.Core/Extensions/TestContextExtensions.cs @@ -33,7 +33,6 @@ public static string GetClassTypeName(this TestContext context) #if NET6_0_OR_GREATER [RequiresUnreferencedCode("Dynamic test metadata creation uses reflection")] - [RequiresDynamicCode("Adding dynamic tests requires reflection which is not supported in native AOT scenarios.")] #endif public static async Task AddDynamicTest<[DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicConstructors diff --git a/TUnit.Core/GenericTestMetadata.cs b/TUnit.Core/GenericTestMetadata.cs index 09d677610d..4f588c8725 100644 --- a/TUnit.Core/GenericTestMetadata.cs +++ b/TUnit.Core/GenericTestMetadata.cs @@ -126,7 +126,7 @@ public override Func().ToArray() ?? Type.EmptyTypes; } - var instance = InstanceFactory(typeArgs, context.ClassArguments ?? Array.Empty()); + var instance = InstanceFactory(typeArgs, context.ClassArguments ?? []); // Property injection is handled by SingleTestExecutor after instance creation return instance; diff --git a/TUnit.Core/Helpers/ArgumentFormatter.cs b/TUnit.Core/Helpers/ArgumentFormatter.cs index 5a71b4645a..bcf81189d8 100644 --- a/TUnit.Core/Helpers/ArgumentFormatter.cs +++ b/TUnit.Core/Helpers/ArgumentFormatter.cs @@ -63,11 +63,18 @@ private static string FormatDefault(object? o) return toString; } - if (type.IsPrimitive || o is string) + if (type.IsPrimitive) { return toString; } + if (o is string str) + { + // Replace dots with middle dot (·) to prevent VS Test Explorer from interpreting them as namespace separators + // Only do this if the string contains dots, to avoid unnecessary allocations + return str.Contains('.') ? str.Replace(".", "·") : str; + } + if (toString == type.FullName || toString == type.AssemblyQualifiedName) { return type.Name; diff --git a/TUnit.Core/Helpers/CastHelper.cs b/TUnit.Core/Helpers/CastHelper.cs index 43521bfe57..7edeaa9779 100644 --- a/TUnit.Core/Helpers/CastHelper.cs +++ b/TUnit.Core/Helpers/CastHelper.cs @@ -1,4 +1,5 @@ -using System.Collections; +using System.Collections; +using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; @@ -6,390 +7,346 @@ namespace TUnit.Core.Helpers; -[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.")] -[UnconditionalSuppressMessage("Trimming", "IL2072:Target parameter argument does not satisfy \'DynamicallyAccessedMembersAttribute\' in call to target method. The return value of the source method does not have matching annotations.")] public static class CastHelper { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T? Cast<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(object? value) + // Cache for conversion methods to avoid repeated reflection lookups + private static readonly ConcurrentDictionary<(Type Source, Type Target), MethodInfo?> ConversionMethodCache = new(); + + /// + /// Attempts to cast or convert a value to the specified type T. + /// Uses a layered approach: fast paths first (AOT-safe), then reflection fallbacks. + /// + public static T? Cast<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>(object? value) { + if (value is T t) + { + return t; + } + + return (T?)Cast(typeof(T), value); + } + + /// + /// Attempts to cast or convert a value to the specified type. + /// Conversion priority: + /// 1. Fast path: null handling, direct cast, nullable unwrapping + /// 2. AOT-safe: AotConverterRegistry, primitives, enums + /// 3. Reflection fallback: custom operators, arrays (throws in AOT) + /// + [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.")] + public static object? Cast([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type, object? value) + { + // Fast path: handle null if (value is null) { - return default(T?); + return type.IsValueType && Nullable.GetUnderlyingType(type) == null + ? Activator.CreateInstance(type) // default(T) for non-nullable value types + : null; + } + + var targetType = Nullable.GetUnderlyingType(type) ?? type; + var sourceType = value.GetType(); + + // Fast path: direct cast if types are assignable + if (sourceType.IsAssignableTo(targetType)) + { + return value; } - if (value is T successfulCast) + // Fast path: generic parameter types (can't convert) + if (type.IsGenericParameter) { - return successfulCast; + return value; } - var underlyingType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); + // Layer 1: AOT-safe conversions (no reflection) + if (TryAotSafeConversion(targetType, sourceType, value, out var result)) + { + return result; + } - if (value.GetType().IsAssignableTo(underlyingType)) + // Layer 2: Reflection-based conversions (not AOT-compatible) + if (TryReflectionConversion(targetType, sourceType, value, out result)) { - return (T) value; + return result; } + // Last resort: return value as-is and hope for the best + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryAotSafeConversion(Type targetType, Type sourceType, object value, out object? result) + { // Try AOT converter registry first - if (AotConverterRegistry.TryConvert(value.GetType(), underlyingType, value, out var converted)) + if (AotConverterRegistry.TryConvert(sourceType, targetType, value, out result)) { - return (T?) converted; + return true; } - if (value is IConvertible && underlyingType.IsPrimitive) + // Handle IConvertible primitives (int, string, double, etc.) + if (value is IConvertible && targetType.IsPrimitive) { try { - return (T?) Convert.ChangeType(value, underlyingType); + result = Convert.ChangeType(value, targetType); + return true; } catch { - // If direct conversion fails, continue with other approaches + // Conversion failed, continue with other strategies } } - if (value is not string - && value is IEnumerable enumerable - && !value.GetType().IsArray // Don't unwrap arrays - && !typeof(IEnumerable).IsAssignableFrom(typeof(T))) + // Handle enum conversions + if (targetType.IsEnum) { - value = enumerable.Cast().ElementAtOrDefault(0); + try + { + result = Enum.ToObject(targetType, value); + return true; + } + catch + { + // Conversion failed + } } - if (underlyingType.IsEnum) + // Unwrap single-element enumerables (but not strings or arrays) + if (value is not string && !sourceType.IsArray && value is IEnumerable enumerable && !typeof(IEnumerable).IsAssignableFrom(targetType)) { - return (T?) Enum.ToObject(underlyingType, value!); + var firstElement = enumerable.Cast().FirstOrDefault(); + if (firstElement != null) + { + // Recursively try to cast the first element + return TryAotSafeConversion(targetType, firstElement.GetType(), firstElement, out result); + } } - // Special handling for array types - check this before IConvertible - if (underlyingType.IsArray) - { - var targetElementType = underlyingType.GetElementType()!; + result = null; + return false; + } - // Handle null -> empty array - if (value is null) - { - ThrowOnAot(value, underlyingType); - return (T?)(object)Array.CreateInstance(targetElementType, 0); - } + [RequiresDynamicCode("Uses reflection to find custom conversion operators and create arrays, which is not compatible with AOT compilation.")] + [UnconditionalSuppressMessage("Trimming", "IL2072:Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute'")] + private static bool TryReflectionConversion(Type targetType, Type sourceType, object value, out object? result) + { + // Check if we're in AOT mode - if so, skip reflection-based conversions + var isAotMode = IsAotMode(); - // Handle single value -> single element array - if (!value.GetType().IsArray) + // Handle array conversions (requires dynamic code) + if (!isAotMode && targetType.IsArray && TryConvertArray(targetType, sourceType, value, out result)) + { + return true; + } + + // Try custom conversion operators (op_Implicit, op_Explicit) - requires dynamic code + if (!isAotMode) + { + var conversionMethod = GetConversionMethodCached(sourceType, targetType); + if (conversionMethod != null) { - if (value is IConvertible) + try { - ThrowOnAot(value, underlyingType); - + result = conversionMethod.Invoke(null, [value]); + return true; + } + catch (Exception ex) when (ex is NotSupportedException || ex is InvalidOperationException) + { + // Reflection invoke failed - likely in AOT scenario despite our check + // Try direct cast as fallback try { - var convertedValue = Convert.ChangeType(value, targetElementType); - var array = Array.CreateInstance(targetElementType, 1); - array.SetValue(convertedValue, 0); - return (T?)(object)array; + result = value; + return true; } catch { - // If direct conversion fails, continue with other approaches + // Give up } } } - // Handle array -> array with element type conversion - else if (value is Array sourceArray) - { - var sourceElementType = value.GetType().GetElementType()!; - - // If element types match, return as-is - if (sourceElementType == targetElementType) - { - return (T?)value; - } - - // Otherwise, convert each element - try - { - ThrowOnAot(value, underlyingType); - var targetArray = Array.CreateInstance(targetElementType, sourceArray.Length); - for (var i = 0; i < sourceArray.Length; i++) - { - var sourceElement = sourceArray.GetValue(i); - var convertedElement = sourceElement is IConvertible - ? Convert.ChangeType(sourceElement, targetElementType) - : sourceElement; - targetArray.SetValue(convertedElement, i); - } - return (T?)(object)targetArray; - } - catch - { - // If conversion fails, continue with other approaches - } - } } - var conversionMethod = GetConversionMethod(value!.GetType(), underlyingType); - - if (conversionMethod is null && value is IConvertible) + // Try IConvertible for non-primitives + if (value is IConvertible) { - return (T?) Convert.ChangeType(value, underlyingType); - } - - if (conversionMethod is null) - { - // Check if we can do unboxing directly for value types - if (underlyingType.IsValueType && value.GetType() == typeof(object)) + try { - try - { - return (T)value; - } - catch - { - // If unboxing fails, continue with the original approach - } + result = Convert.ChangeType(value, targetType); + return true; } - - // Log diagnostic information for debugging single file mode issues - if (Environment.GetEnvironmentVariable("TUNIT_DIAGNOSTIC_CAST") == "true") + catch { - Console.WriteLine($"[CastHelper] No conversion found from {value.GetType().FullName} to {underlyingType.FullName}"); + // Conversion failed } - - return (T?) value; } - // Even in source generation mode, we use reflection as a fallback for custom conversions - // The AOT analyzer will warn about incompatibility at compile time - try - { - return (T?) conversionMethod.Invoke(null, [value]); - } - catch (Exception ex) when (ex is NotSupportedException || ex is InvalidOperationException) + // Check for unboxing scenario + if (targetType.IsValueType && sourceType == typeof(object)) { - // In AOT scenarios, reflection invoke might fail - // Try a direct cast as a last resort try { - return (T)value; + result = value; + return true; } catch { - // If all else fails, return the value as-is and let the runtime handle it - return (T?)value; + // Unboxing failed } } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static object? Cast([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type type, object? value) - { - if (value is null) + // Diagnostic logging for debugging + if (Environment.GetEnvironmentVariable("TUNIT_DIAGNOSTIC_CAST") == "true") { - return null; + Console.WriteLine($"[CastHelper] No conversion found from {sourceType.FullName} to {targetType.FullName}"); } - var underlyingType = Nullable.GetUnderlyingType(type) ?? type; - - if (value.GetType().IsAssignableTo(underlyingType)) - { - return value; - } + result = null; + return false; + } - // Try AOT converter registry first - if (AotConverterRegistry.TryConvert(value.GetType(), underlyingType, value, out var converted)) - { - return converted; - } + [RequiresDynamicCode("Array element conversion requires dynamic code for type inspection and conversion.")] + [UnconditionalSuppressMessage("Trimming", "IL2072:Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute'")] + private static bool TryConvertArray(Type targetType, Type sourceType, object value, out object? result) + { + var targetElementType = targetType.GetElementType()!; - if (type.IsGenericParameter) + // Handle null -> empty array + if (value is null) { - return value; + result = Array.CreateInstance(targetElementType, 0); + return true; } - if (value is not string - && value is IEnumerable enumerable - && !value.GetType().IsArray // Don't unwrap arrays - && !typeof(IEnumerable).IsAssignableFrom(type)) + // Handle single value -> single element array + if (!sourceType.IsArray) { - // Special handling for CustomAttributeTypedArgument collections in .NET Framework - var typeName = value.GetType().FullName; - if (typeName != null && typeName.Contains("CustomAttributeTypedArgument")) + if (value is IConvertible) { - // For ReadOnlyCollection, we need to extract the actual values - var firstItem = enumerable.Cast().FirstOrDefault(); - if (firstItem != null) + try { - ThrowOnAot(value, underlyingType); - // Use reflection to get the Value property - var valueProperty = GetValuePropertySafe(firstItem.GetType()); - if (valueProperty != null) - { - value = valueProperty.GetValue(firstItem); - } - else - { - value = firstItem; - } + var convertedValue = Convert.ChangeType(value, targetElementType); + var array = Array.CreateInstance(targetElementType, 1); + array.SetValue(convertedValue, 0); + result = array; + return true; } - else + catch { - value = null; + // Conversion failed } } - else - { - value = enumerable.Cast().ElementAtOrDefault(0); - } - } - if (underlyingType.IsEnum) - { - return Enum.ToObject(underlyingType, value!); + result = null; + return false; } - // Special handling for array types - check this before IConvertible - if (underlyingType.IsArray) + // Handle array -> array with element type conversion + if (value is Array sourceArray) { - var targetElementType = underlyingType.GetElementType()!; + var sourceElementType = sourceType.GetElementType()!; - // Handle null -> empty array - if (value is null) + // If element types match, return as-is + if (sourceElementType == targetElementType) { - ThrowOnAot(value, underlyingType); - return Array.CreateInstance(targetElementType, 0); + result = value; + return true; } - // Handle single value -> single element array - if (!value.GetType().IsArray) + // Convert each element + try { - if (value is IConvertible) + var targetArray = Array.CreateInstance(targetElementType, sourceArray.Length); + for (var i = 0; i < sourceArray.Length; i++) { - ThrowOnAot(value, underlyingType); - try - { - var convertedValue = Convert.ChangeType(value, targetElementType); - var array = Array.CreateInstance(targetElementType, 1); - array.SetValue(convertedValue, 0); - return array; - } - catch - { - // If direct conversion fails, continue with other approaches - } + var sourceElement = sourceArray.GetValue(i); + var convertedElement = sourceElement is IConvertible + ? Convert.ChangeType(sourceElement, targetElementType) + : sourceElement; + targetArray.SetValue(convertedElement, i); } + result = targetArray; + return true; } - // Handle array -> array with element type conversion - else if (value is Array sourceArray) + catch { - var sourceElementType = value.GetType().GetElementType()!; - - // If element types match, return as-is - if (sourceElementType == targetElementType) - { - return value; - } - - // Otherwise, convert each element - try - { - ThrowOnAot(value, underlyingType); - var targetArray = Array.CreateInstance(targetElementType, sourceArray.Length); - for (var i = 0; i < sourceArray.Length; i++) - { - var sourceElement = sourceArray.GetValue(i); - var convertedElement = sourceElement is IConvertible - ? Convert.ChangeType(sourceElement, targetElementType) - : sourceElement; - targetArray.SetValue(convertedElement, i); - } - return targetArray; - } - catch - { - // If conversion fails, continue with other approaches - } + // Array conversion failed } } - var conversionMethod = GetConversionMethod(value!.GetType(), underlyingType); + result = null; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.")] + private static MethodInfo? GetConversionMethodCached(Type sourceType, Type targetType) + { + return ConversionMethodCache.GetOrAdd( + (sourceType, targetType), + static key => FindConversionMethod(key.Item1, key.Item2)); + } - if (conversionMethod is null && value is IConvertible) + [RequiresDynamicCode("Finding conversion operators requires reflection which is not compatible with AOT compilation.")] + [UnconditionalSuppressMessage("Trimming", "IL2070:Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute'")] + private static MethodInfo? FindConversionMethod(Type sourceType, Type targetType) + { + // Get all static methods from both types + var sourceMethods = sourceType.GetMethods(BindingFlags.Public | BindingFlags.Static); + var targetMethods = targetType.GetMethods(BindingFlags.Public | BindingFlags.Static); + + // Look for implicit conversion first + foreach (var method in sourceMethods) { - return Convert.ChangeType(value, underlyingType); + if (method.Name == "op_Implicit" && method.ReturnType == targetType && HasCorrectInputType(sourceType, method)) + { + return method; + } } - if (conversionMethod is null) + foreach (var method in targetMethods) { - // Check if we can do unboxing directly for value types - if (underlyingType.IsValueType && value.GetType() == typeof(object)) + if (method.Name == "op_Implicit" && method.ReturnType == targetType && HasCorrectInputType(sourceType, method)) { - try - { - return value; - } - catch - { - // If unboxing fails, continue with the original approach - } + return method; } - return value; } - // Even in source generation mode, we use reflection as a fallback for custom conversions - // The AOT analyzer will warn about incompatibility at compile time - return conversionMethod.Invoke(null, [value]); - } - - public static MethodInfo? GetConversionMethod([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type baseType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type targetType) - { - var baseMethods = baseType.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) - .Concat(baseType.GetMethods(BindingFlags.Public | BindingFlags.Static)); - - var targetMethods = targetType.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) - .Concat(targetType.GetMethods(BindingFlags.Public | BindingFlags.Static)); - - var methods = baseMethods.Concat(targetMethods).Distinct().ToArray(); - - // Look for implicit conversion first - var implicitMethod = methods - .FirstOrDefault(mi => - mi.Name == "op_Implicit" && mi.ReturnType == targetType && HasCorrectInputType(baseType, mi)); - - if (implicitMethod != null) + // Look for explicit conversion + foreach (var method in sourceMethods) { - return implicitMethod; + if (method.Name == "op_Explicit" && method.ReturnType == targetType && HasCorrectInputType(sourceType, method)) + { + return method; + } } - // Then look for explicit conversion - return methods - .FirstOrDefault(mi => - mi.Name == "op_Explicit" && mi.ReturnType == targetType && HasCorrectInputType(baseType, mi)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ThrowOnAot(object? value, Type? targetType) - { -#if NET - if (!RuntimeFeature.IsDynamicCodeSupported) + foreach (var method in targetMethods) { - throw new InvalidOperationException( - $"Cannot cast {value?.GetType()?.Name ?? "null"} to {targetType?.Name} in AOT mode. " + - "Consider using AotConverterRegistry.Register() for custom type conversions."); + if (method.Name == "op_Explicit" && method.ReturnType == targetType && HasCorrectInputType(sourceType, method)) + { + return method; + } } -#endif + + return null; } - private static bool HasCorrectInputType(Type baseType, MethodInfo mi) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool HasCorrectInputType(Type expectedType, MethodInfo method) { - var pi = mi.GetParameters().FirstOrDefault(); - return pi != null && pi.ParameterType == baseType; + var parameters = method.GetParameters(); + return parameters.Length == 1 && parameters[0].ParameterType == expectedType; } - /// - /// Gets the "Value" property from a type in an AOT-safer manner. - /// - private static PropertyInfo? GetValuePropertySafe([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsAotMode() { - return type.GetProperty("Value"); +#if NET + return !RuntimeFeature.IsDynamicCodeSupported; +#else + return false; +#endif } - } diff --git a/TUnit.Core/Helpers/DataSourceHelpers.cs b/TUnit.Core/Helpers/DataSourceHelpers.cs index 2ad1d53134..df9366accd 100644 --- a/TUnit.Core/Helpers/DataSourceHelpers.cs +++ b/TUnit.Core/Helpers/DataSourceHelpers.cs @@ -331,7 +331,7 @@ public static T InvokeIfFunc(object? value) // Only arrays and tuples are expanded (handled above) return [item]; } - + /// /// Converts an item to an object array, considering the expected parameter types. /// This version handles nested tuples correctly by checking if parameters expect tuples. @@ -376,7 +376,7 @@ public static T InvokeIfFunc(object? value) { return [item]; } - + return UnwrapTupleWithTypes(item, expectedTypes); } // Fall back to default unwrapping if no type info @@ -386,7 +386,7 @@ public static T InvokeIfFunc(object? value) // Don't expand IEnumerable - test methods expect the IEnumerable itself as a parameter return [item]; } - + /// /// Unwraps a tuple considering the expected parameter types. /// Preserves nested tuples when parameters expect tuple types. @@ -404,12 +404,12 @@ public static T InvokeIfFunc(object? value) { var result = new List(); var typeIndex = 0; - + for (var i = 0; i < tuple.Length && typeIndex < expectedTypes.Length; i++) { var element = tuple[i]; var expectedType = expectedTypes[typeIndex]; - + // Check if the expected type is a tuple type if (IsTupleType(expectedType) && IsTuple(element)) { @@ -424,7 +424,7 @@ public static T InvokeIfFunc(object? value) typeIndex++; } } - + return result.ToArray(); } #endif @@ -432,7 +432,7 @@ public static T InvokeIfFunc(object? value) // Fallback to default unwrapping return UnwrapTupleAot(value); } - + /// /// Checks if a Type represents a tuple type. /// @@ -471,7 +471,7 @@ public static bool IsTuple(object? obj) #if NET5_0_OR_GREATER || NETCOREAPP3_0_OR_GREATER // Fast path for modern .NET: ITuple covers all tuple types - return obj is System.Runtime.CompilerServices.ITuple; + return obj is ITuple; #else // Fallback: check for known tuple types var type = obj.GetType(); @@ -497,7 +497,7 @@ public static bool IsTuple(object? obj) genericType == typeof(Tuple<,,,,,,>); #endif } - + /// /// Tries to create an instance using a generated creation method that handles init-only properties. /// Returns true if successful, false if no creator is available. @@ -509,14 +509,14 @@ public static bool IsTuple(object? obj) { return (false, null); } - + // Use the creator to create and initialize the instance var createdInstance = await creator(testInformation, testSessionId).ConfigureAwait(false); return (true, createdInstance); } - + private static readonly Dictionary>> TypeCreators = new(); - + /// /// Registers a type creator function for types with init-only data source properties. /// Called by generated code. @@ -525,14 +525,17 @@ public static void RegisterTypeCreator(Func> { TypeCreators[typeof(T)] = async (metadata, sessionId) => (await creator(metadata, sessionId))!; } - + /// /// Resolves a data source property value at runtime. /// This method handles all IDataSourceAttribute implementations generically. + /// Only used in reflection mode - in AOT/source-gen mode, property injection is handled by generated code. /// #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Property types are resolved through reflection")] - [RequiresDynamicCode("Data source resolution may require dynamic code generation")] + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access", + Justification = "This method is only used in reflection mode. In AOT/source-gen mode, property injection uses compile-time generated code.")] + [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling", + Justification = "This method is only used in reflection mode. In AOT/source-gen mode, property injection uses compile-time generated code.")] #endif public static async Task ResolveDataSourceForPropertyAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] Type containingType, string propertyName, MethodMetadata testInformation, string testSessionId) { diff --git a/TUnit.Core/Helpers/GenericTypeHelper.cs b/TUnit.Core/Helpers/GenericTypeHelper.cs index 563b80202b..3d9fa023d8 100644 --- a/TUnit.Core/Helpers/GenericTypeHelper.cs +++ b/TUnit.Core/Helpers/GenericTypeHelper.cs @@ -17,8 +17,9 @@ public static class GenericTypeHelper /// Thrown when genericTypeDefinition is null /// Thrown when type arguments don't match the generic type definition #if NET6_0_OR_GREATER + [RequiresUnreferencedCode("MakeGenericType requires runtime code generation")] [RequiresDynamicCode("MakeGenericType requires runtime code generation")] - #endif +#endif public static Type MakeGenericTypeSafe(Type genericTypeDefinition, params Type[] typeArguments) { if (genericTypeDefinition == null) @@ -50,12 +51,7 @@ public static Type MakeGenericTypeSafe(Type genericTypeDefinition, params Type[] // The AOT analyzer will warn about incompatibility at compile time try { - // Reflection mode - use MakeGenericType directly - // Method is already annotated with RequiresDynamicCode, suppressing IL2055 - [UnconditionalSuppressMessage("Trimming", "IL2055:MakeGenericType", Justification = "Method is already properly annotated with RequiresDynamicCode to indicate AOT incompatibility")] - static Type MakeGenericTypeUnsafe(Type genericType, Type[] args) => genericType.MakeGenericType(args); - - return MakeGenericTypeUnsafe(genericTypeDefinition, typeArguments); + return genericTypeDefinition.MakeGenericType(typeArguments); } catch (ArgumentException ex) { diff --git a/TUnit.Core/Interfaces/ITestRegistry.cs b/TUnit.Core/Interfaces/ITestRegistry.cs index 6c98b90363..9dbfcd5da0 100644 --- a/TUnit.Core/Interfaces/ITestRegistry.cs +++ b/TUnit.Core/Interfaces/ITestRegistry.cs @@ -15,8 +15,7 @@ public interface ITestRegistry /// The dynamic test instance to add /// A task that completes when the test has been queued for execution #if NET6_0_OR_GREATER - [RequiresDynamicCode("Adding dynamic tests requires runtime compilation and reflection which are not supported in native AOT scenarios.")] - [RequiresUnreferencedCode("Dynamic test metadata creation uses reflection")] + [RequiresUnreferencedCode("Adding dynamic tests requires runtime compilation and reflection which are not supported in native AOT scenarios.")] #endif Task AddDynamicTest<[DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicConstructors diff --git a/TUnit.Core/Interfaces/SourceGenerator/PropertyInjectionMetadata.cs b/TUnit.Core/Interfaces/SourceGenerator/PropertyInjectionMetadata.cs index cfe62f5f46..d4c8309fc3 100644 --- a/TUnit.Core/Interfaces/SourceGenerator/PropertyInjectionMetadata.cs +++ b/TUnit.Core/Interfaces/SourceGenerator/PropertyInjectionMetadata.cs @@ -18,8 +18,6 @@ public sealed class PropertyInjectionMetadata [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | - DynamicallyAccessedMemberTypes.PublicMethods | - DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)] public required Type PropertyType { get; init; } diff --git a/TUnit.Core/Models/DataGeneratorMetadata.cs b/TUnit.Core/Models/DataGeneratorMetadata.cs index ce176ef8a0..8ce749f28a 100644 --- a/TUnit.Core/Models/DataGeneratorMetadata.cs +++ b/TUnit.Core/Models/DataGeneratorMetadata.cs @@ -5,7 +5,7 @@ namespace TUnit.Core; public record DataGeneratorMetadata { public required TestBuilderContextAccessor TestBuilderContext { get; init; } - public required MemberMetadata[] MembersToGenerate { get; init; } + public required IMemberMetadata[] MembersToGenerate { get; init; } public required MethodMetadata? TestInformation { get; init; } public required DataGeneratorType Type { get; init; } public required string TestSessionId { get; init; } diff --git a/TUnit.Core/Models/TestModels/ClassMetadata.cs b/TUnit.Core/Models/TestModels/ClassMetadata.cs index d6c1dbed93..925bdcd8ec 100644 --- a/TUnit.Core/Models/TestModels/ClassMetadata.cs +++ b/TUnit.Core/Models/TestModels/ClassMetadata.cs @@ -5,7 +5,7 @@ namespace TUnit.Core; [DebuggerDisplay("{Type}")] -public record ClassMetadata : MemberMetadata +public record ClassMetadata : IMemberMetadata { private static readonly ConcurrentDictionary Cache = []; @@ -45,9 +45,10 @@ public override int GetHashCode() DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods - | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)] - public override required Type Type { get; init; } + public required Type Type { get; init; } + + public required string Name { get; init; } public required string? Namespace { get; init; } public required AssemblyMetadata Assembly { get; init; } diff --git a/TUnit.Core/Models/TestModels/IMemberMetadata.cs b/TUnit.Core/Models/TestModels/IMemberMetadata.cs new file mode 100644 index 0000000000..1531751a66 --- /dev/null +++ b/TUnit.Core/Models/TestModels/IMemberMetadata.cs @@ -0,0 +1,10 @@ +namespace TUnit.Core; + +/// +/// Marker interface for metadata types (Property, Parameter, Class, Method). +/// Does not define Type property to allow each implementation to have its own AOT annotations. +/// +public interface IMemberMetadata +{ + string Name { get; } +} diff --git a/TUnit.Core/Models/TestModels/MemberMetadata.cs b/TUnit.Core/Models/TestModels/MemberMetadata.cs index e668caf96d..fdcde665ec 100644 --- a/TUnit.Core/Models/TestModels/MemberMetadata.cs +++ b/TUnit.Core/Models/TestModels/MemberMetadata.cs @@ -8,7 +8,6 @@ public abstract record MemberMetadata [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods - | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)] public abstract Type Type { get; init; } diff --git a/TUnit.Core/Models/TestModels/MethodMetadata.cs b/TUnit.Core/Models/TestModels/MethodMetadata.cs index 8e0dab02c1..2b4f95e440 100644 --- a/TUnit.Core/Models/TestModels/MethodMetadata.cs +++ b/TUnit.Core/Models/TestModels/MethodMetadata.cs @@ -5,7 +5,7 @@ namespace TUnit.Core; [DebuggerDisplay("{Type}.{Name}")] -public record MethodMetadata : MemberMetadata +public record MethodMetadata : IMemberMetadata { public required ParameterMetadata[] Parameters { get; init; } @@ -28,9 +28,11 @@ public record MethodMetadata : MemberMetadata | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)] - public override required Type Type { get; init; } + public required Type Type { get; init; } - protected override bool PrintMembers(StringBuilder stringBuilder) + public required string Name { get; init; } + + protected virtual bool PrintMembers(StringBuilder stringBuilder) { stringBuilder.Append($"ReturnTypeInfo = {ReturnTypeInfo},"); stringBuilder.Append($"GenericTypeCount = {GenericTypeCount},"); diff --git a/TUnit.Core/Models/TestModels/ParameterMetadata.cs b/TUnit.Core/Models/TestModels/ParameterMetadata.cs index 8e4bc538e6..83234b1447 100644 --- a/TUnit.Core/Models/TestModels/ParameterMetadata.cs +++ b/TUnit.Core/Models/TestModels/ParameterMetadata.cs @@ -7,24 +7,20 @@ namespace TUnit.Core; [DebuggerDisplay("{Type} {Name}")] public record ParameterMetadata<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors - | DynamicallyAccessedMemberTypes.PublicMethods - | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)] T>() : ParameterMetadata(typeof(T)); [DebuggerDisplay("{Type} {Name}")] public record ParameterMetadata([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors - | DynamicallyAccessedMemberTypes.PublicMethods - | DynamicallyAccessedMemberTypes.NonPublicMethods - | DynamicallyAccessedMemberTypes.PublicProperties)] Type Type) : MemberMetadata + | DynamicallyAccessedMemberTypes.PublicProperties)] Type Type) : IMemberMetadata { [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors - | DynamicallyAccessedMemberTypes.PublicMethods - | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)] - public override Type Type { get; init; } = Type; + public Type Type { get; init; } = Type; + + public required string Name { get; init; } public required TypeInfo TypeInfo { get; init; } public required ParameterInfo ReflectionInfo { get; set; } diff --git a/TUnit.Core/Models/TestModels/PropertyMetadata.cs b/TUnit.Core/Models/TestModels/PropertyMetadata.cs index d89d43b048..9181daea62 100644 --- a/TUnit.Core/Models/TestModels/PropertyMetadata.cs +++ b/TUnit.Core/Models/TestModels/PropertyMetadata.cs @@ -5,14 +5,14 @@ namespace TUnit.Core; [DebuggerDisplay("{Type} {Name})")] -public record PropertyMetadata : MemberMetadata +public record PropertyMetadata : IMemberMetadata { [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors - | DynamicallyAccessedMemberTypes.PublicMethods - | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)] - public override required Type Type { get; init; } + public required Type Type { get; init; } + + public required string Name { get; init; } public required PropertyInfo ReflectionInfo { get; init; } diff --git a/TUnit.Core/Models/TestSessionContext.cs b/TUnit.Core/Models/TestSessionContext.cs index 88d573b6a3..0151d607eb 100644 --- a/TUnit.Core/Models/TestSessionContext.cs +++ b/TUnit.Core/Models/TestSessionContext.cs @@ -27,7 +27,7 @@ internal set Name = "StaticPropertyInitialization", TypeInfo = new ConcreteType(typeof(object)), ReturnTypeInfo = new ConcreteType(typeof(void)), - Parameters = Array.Empty(), + Parameters = [], GenericTypeCount = 0, Class = new ClassMetadata { @@ -36,8 +36,8 @@ internal set Namespace = "TUnit.Core", TypeInfo = new ConcreteType(typeof(object)), Assembly = AssemblyMetadata.GetOrAdd("TUnit.Core", () => new AssemblyMetadata { Name = "TUnit.Core" }), - Properties = Array.Empty(), - Parameters = Array.Empty(), + Properties = [], + Parameters = [], Parent = null } }, diff --git a/TUnit.Core/PropertyInjection/ClassMetadataHelper.cs b/TUnit.Core/PropertyInjection/ClassMetadataHelper.cs index 84ffcd00c5..677a32c737 100644 --- a/TUnit.Core/PropertyInjection/ClassMetadataHelper.cs +++ b/TUnit.Core/PropertyInjection/ClassMetadataHelper.cs @@ -19,8 +19,6 @@ public static ClassMetadata GetOrCreateClassMetadata( [DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | - DynamicallyAccessedMemberTypes.PublicMethods | - DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)] Type type) { @@ -34,7 +32,7 @@ public static ClassMetadata GetOrCreateClassMetadata( Name = p.Name ?? $"param{i}", TypeInfo = new ConcreteType(p.ParameterType), ReflectionInfo = p - }).ToArray() ?? Array.Empty(); + }).ToArray() ?? []; return new ClassMetadata { diff --git a/TUnit.Core/PropertyInjection/PropertyInjectionPlanBuilder.cs b/TUnit.Core/PropertyInjection/PropertyInjectionPlanBuilder.cs index 5bc5e1962b..a0cf5ee323 100644 --- a/TUnit.Core/PropertyInjection/PropertyInjectionPlanBuilder.cs +++ b/TUnit.Core/PropertyInjection/PropertyInjectionPlanBuilder.cs @@ -45,7 +45,7 @@ public static PropertyInjectionPlan BuildSourceGeneratedPlan(Type type) { Type = type, SourceGeneratedProperties = sourceGenProps, - ReflectionProperties = Array.Empty<(PropertyInfo, IDataSourceAttribute)>(), + ReflectionProperties = [], HasProperties = sourceGenProps.Length > 0 }; } @@ -94,7 +94,7 @@ public static PropertyInjectionPlan BuildReflectionPlan(Type type) return new PropertyInjectionPlan { Type = type, - SourceGeneratedProperties = Array.Empty(), + SourceGeneratedProperties = [], ReflectionProperties = propertyDataSourcePairs.ToArray(), HasProperties = propertyDataSourcePairs.Count > 0 }; diff --git a/TUnit.Core/Services/GenericTypeResolver.cs b/TUnit.Core/Services/GenericTypeResolver.cs index 251177643e..259219f8ab 100644 --- a/TUnit.Core/Services/GenericTypeResolver.cs +++ b/TUnit.Core/Services/GenericTypeResolver.cs @@ -9,8 +9,7 @@ namespace TUnit.Core.Services; /// Implementation of generic type resolution for test methods and classes /// #if NET6_0_OR_GREATER -[RequiresDynamicCode("Generic type resolution requires runtime type generation")] -[RequiresUnreferencedCode("Generic type resolution may access types not preserved by trimming")] +[RequiresUnreferencedCode("Generic type resolution requires runtime type generation")] #endif public class GenericTypeResolver : IGenericTypeResolver { diff --git a/TUnit.Core/Sources.cs b/TUnit.Core/Sources.cs index 988c8dd73a..cb4fad8c23 100644 --- a/TUnit.Core/Sources.cs +++ b/TUnit.Core/Sources.cs @@ -15,18 +15,18 @@ public static class Sources public static readonly ConcurrentDictionary> BeforeTestHooks = new(); public static readonly ConcurrentDictionary> AfterTestHooks = new(); - public static readonly ConcurrentBag BeforeEveryTestHooks = new(); - public static readonly ConcurrentBag AfterEveryTestHooks = new(); + public static readonly ConcurrentBag BeforeEveryTestHooks = []; + public static readonly ConcurrentBag AfterEveryTestHooks = []; public static readonly ConcurrentDictionary> BeforeClassHooks = new(); public static readonly ConcurrentDictionary> AfterClassHooks = new(); - public static readonly ConcurrentBag BeforeEveryClassHooks = new(); - public static readonly ConcurrentBag AfterEveryClassHooks = new(); + public static readonly ConcurrentBag BeforeEveryClassHooks = []; + public static readonly ConcurrentBag AfterEveryClassHooks = []; public static readonly ConcurrentDictionary> BeforeAssemblyHooks = new(); public static readonly ConcurrentDictionary> AfterAssemblyHooks = new(); - public static readonly ConcurrentBag BeforeEveryAssemblyHooks = new(); - public static readonly ConcurrentBag AfterEveryAssemblyHooks = new(); + public static readonly ConcurrentBag BeforeEveryAssemblyHooks = []; + public static readonly ConcurrentBag AfterEveryAssemblyHooks = []; public static readonly ConcurrentBag BeforeTestSessionHooks = []; public static readonly ConcurrentBag AfterTestSessionHooks = []; diff --git a/TUnit.Core/StaticProperties/StaticPropertyRegistry.cs b/TUnit.Core/StaticProperties/StaticPropertyRegistry.cs index ed16e35099..e0da855d7e 100644 --- a/TUnit.Core/StaticProperties/StaticPropertyRegistry.cs +++ b/TUnit.Core/StaticProperties/StaticPropertyRegistry.cs @@ -8,7 +8,7 @@ namespace TUnit.Core.StaticProperties; /// public static class StaticPropertyRegistry { - private static readonly ConcurrentBag _registeredProperties = new(); + private static readonly ConcurrentBag _registeredProperties = []; private static readonly ConcurrentDictionary _initializedValues = new(); /// diff --git a/TUnit.Core/StaticPropertyReflectionInitializer.cs b/TUnit.Core/StaticPropertyReflectionInitializer.cs index a9c60ad217..344a5b4890 100644 --- a/TUnit.Core/StaticPropertyReflectionInitializer.cs +++ b/TUnit.Core/StaticPropertyReflectionInitializer.cs @@ -19,7 +19,7 @@ public static class StaticPropertyReflectionInitializer /// Initializes static properties with data sources for all loaded types /// #if NET6_0_OR_GREATER - [RequiresDynamicCode("Data source initialization may require dynamic code generation")] + [RequiresUnreferencedCode("Data source initialization may require dynamic code generation")] #endif public static async Task InitializeAllStaticPropertiesAsync() { @@ -50,7 +50,7 @@ public static async Task InitializeAllStaticPropertiesAsync() /// Initializes static properties with data sources for a specific type /// #if NET6_0_OR_GREATER - [RequiresDynamicCode("Data source initialization may require dynamic code generation")] + [RequiresUnreferencedCode("Data source initialization may require dynamic code generation")] #endif public static async Task InitializeStaticPropertiesForType(Type type) { @@ -85,7 +85,7 @@ private static bool HasDataSourceAttribute(PropertyInfo property) } #if NET6_0_OR_GREATER - [RequiresDynamicCode("Data source initialization may require dynamic code generation")] + [RequiresUnreferencedCode("Data source initialization may require dynamic code generation")] #endif private static async Task InitializeStaticProperty(Type type, PropertyInfo property) { diff --git a/TUnit.Core/TestContext.cs b/TUnit.Core/TestContext.cs index e2edf48255..e585327cb0 100644 --- a/TUnit.Core/TestContext.cs +++ b/TUnit.Core/TestContext.cs @@ -195,7 +195,7 @@ internal override void SetAsyncLocalContext() public object Lock { get; } = new(); - public ConcurrentBag Timings { get; } = new(); + public ConcurrentBag Timings { get; } = []; public IReadOnlyList Artifacts { get; } = new List(); diff --git a/TUnit.Engine/Building/Collectors/AotTestDataCollector.cs b/TUnit.Engine/Building/Collectors/AotTestDataCollector.cs index cfabb3b16b..a538088821 100644 --- a/TUnit.Engine/Building/Collectors/AotTestDataCollector.cs +++ b/TUnit.Engine/Building/Collectors/AotTestDataCollector.cs @@ -37,10 +37,7 @@ public async Task> CollectTestsAsync(string testSessio return [..standardTestMetadatas, ..dynamicTestMetadatas]; } - #if NET6_0_OR_GREATER - [RequiresDynamicCode("Dynamic test conversion requires expression compilation")] - [RequiresUnreferencedCode("Method extraction from expressions uses reflection")] - #endif + [RequiresUnreferencedCode("Dynamic test collection requires expression compilation and reflection")] private async IAsyncEnumerable CollectDynamicTestsStreaming( string testSessionId, [EnumeratorCancellation] CancellationToken cancellationToken = default) @@ -87,8 +84,7 @@ private async IAsyncEnumerable CollectDynamicTestsStreaming( } #if NET6_0_OR_GREATER - [RequiresDynamicCode("Dynamic test conversion requires expression compilation")] - [RequiresUnreferencedCode("Method extraction from expressions uses reflection")] + [RequiresUnreferencedCode("Dynamic test conversion requires expression compilation")] #endif private async IAsyncEnumerable ConvertDynamicTestToMetadataStreaming( AbstractDynamicTest abstractDynamicTest, @@ -107,8 +103,7 @@ private async IAsyncEnumerable ConvertDynamicTestToMetadataStreami } #if NET6_0_OR_GREATER - [RequiresDynamicCode("Dynamic test metadata creation requires expression extraction and reflection")] - [RequiresUnreferencedCode("Method extraction from expressions uses reflection")] + [RequiresUnreferencedCode("Dynamic test metadata creation requires expression extraction and reflection")] #endif private Task CreateMetadataFromDynamicDiscoveryResult(DynamicDiscoveryResult result) { @@ -158,11 +153,9 @@ private Task CreateMetadataFromDynamicDiscoveryResult(DynamicDisco }); } - #if NET6_0_OR_GREATER - [RequiresDynamicCode("Dynamic instance creation uses Activator.CreateInstance and MakeGenericType")] - [RequiresUnreferencedCode("Dynamic type instantiation requires access to constructors")] - #endif - private static Func? CreateAotDynamicInstanceFactory(Type testClass, object?[]? predefinedClassArgs) + [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with \'RequiresDynamicCodeAttribute\' may break functionality when AOT compiling.")] + [UnconditionalSuppressMessage("Trimming", "IL2055:Either the type on which the MakeGenericType is called can\'t be statically determined, or the type parameters to be used for generic arguments can\'t be statically determined.")] + private static Func? CreateAotDynamicInstanceFactory([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type testClass, object?[]? predefinedClassArgs) { // Check if we have predefined args to use as defaults var hasPredefinedArgs = predefinedClassArgs is { Length: > 0 }; @@ -179,6 +172,7 @@ private Task CreateMetadataFromDynamicDiscoveryResult(DynamicDisco { return Activator.CreateInstance(closedType)!; } + return Activator.CreateInstance(closedType, effectiveArgs)!; } @@ -186,13 +180,13 @@ private Task CreateMetadataFromDynamicDiscoveryResult(DynamicDisco { return Activator.CreateInstance(testClass)!; } + return Activator.CreateInstance(testClass, effectiveArgs)!; }; } #if NET6_0_OR_GREATER - [RequiresDynamicCode("Dynamic test invocation requires LambdaExpression.Compile")] - [RequiresUnreferencedCode("Expression compilation and MethodInfo.Invoke use reflection")] + [RequiresUnreferencedCode("Dynamic test invocation requires LambdaExpression.Compile")] #endif private static Func CreateAotDynamicTestInvoker(DynamicDiscoveryResult result) { @@ -322,7 +316,7 @@ public override Func { object instance; - + // Check if there's a ClassConstructor to use if (testContext.ClassConstructor != null) { @@ -332,7 +326,7 @@ public override Func /// Collection of test metadata ready for processing - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Reflection-based implementation uses assembly scanning")] - [RequiresDynamicCode("Reflection-based implementation uses MakeGenericType")] - #endif Task> CollectTestsAsync(string testSessionId); } diff --git a/TUnit.Engine/Building/TestDataCollectorFactory.cs b/TUnit.Engine/Building/TestDataCollectorFactory.cs index 5ffdf78545..8ae93a709d 100644 --- a/TUnit.Engine/Building/TestDataCollectorFactory.cs +++ b/TUnit.Engine/Building/TestDataCollectorFactory.cs @@ -41,10 +41,6 @@ public static ITestDataCollector Create(bool? useSourceGeneration = null, Assemb /// Attempts AOT mode first, falls back to reflection if no source-generated tests found. /// This provides automatic mode selection for optimal performance and compatibility. /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Falls back to reflection mode if no source-generated tests found")] - [RequiresDynamicCode("Falls back to reflection mode if no source-generated tests found")] - #endif public static async Task CreateAutoDetectAsync(string testSessionId, Assembly[]? assembliesToScan = null) { // Try AOT mode first (check if any tests were registered) diff --git a/TUnit.Engine/Discovery/ReflectionBasedHookDiscoveryService.cs b/TUnit.Engine/Discovery/ReflectionBasedHookDiscoveryService.cs index 673aac6bfe..2adba1e3d9 100644 --- a/TUnit.Engine/Discovery/ReflectionBasedHookDiscoveryService.cs +++ b/TUnit.Engine/Discovery/ReflectionBasedHookDiscoveryService.cs @@ -9,7 +9,6 @@ namespace TUnit.Engine.Discovery; /// #if NET6_0_OR_GREATER [RequiresUnreferencedCode("Hook discovery uses reflection to scan assemblies and types")] -[RequiresDynamicCode("Hook delegate creation requires dynamic code generation")] #endif internal sealed class ReflectionBasedHookDiscoveryService : IHookDiscoveryService { diff --git a/TUnit.Engine/Discovery/ReflectionGenericTypeResolver.cs b/TUnit.Engine/Discovery/ReflectionGenericTypeResolver.cs index 884d87605e..d6ca2d7236 100644 --- a/TUnit.Engine/Discovery/ReflectionGenericTypeResolver.cs +++ b/TUnit.Engine/Discovery/ReflectionGenericTypeResolver.cs @@ -9,14 +9,13 @@ namespace TUnit.Engine.Discovery; /// /// Handles generic type resolution and instantiation for reflection-based test discovery /// +[RequiresUnreferencedCode("Uses reflection to analyze and instantiate generic types")] +[RequiresDynamicCode("Uses reflection to analyze and instantiate generic types")] internal static class ReflectionGenericTypeResolver { /// /// Determines generic type arguments from data row values /// -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Generic type argument determination uses reflection")] -#endif public static Type[]? DetermineGenericTypeArguments(Type genericTypeDefinition, object?[] dataRow) { #if NET @@ -79,9 +78,6 @@ internal static class ReflectionGenericTypeResolver /// /// Extracts generic type information including constraints /// -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Generic type info extraction uses reflection on type parameters")] -#endif public static GenericTypeInfo? ExtractGenericTypeInfo(Type testClass) { // Handle both generic type definitions and constructed generic types @@ -129,9 +125,6 @@ internal static class ReflectionGenericTypeResolver /// /// Extracts generic method information including parameter positions /// -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Generic method info extraction uses reflection on method parameters")] -#endif public static GenericMethodInfo? ExtractGenericMethodInfo(MethodInfo method) { if (!method.IsGenericMethodDefinition) @@ -180,10 +173,6 @@ internal static class ReflectionGenericTypeResolver /// /// Creates a concrete type from a generic type definition and validates the type arguments /// -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Concrete type creation uses Type.GetGenericArguments and reflection")] - [RequiresDynamicCode("Type construction uses MakeGenericType")] -#endif public static Type CreateConcreteType(Type genericTypeDefinition, Type[] typeArguments) { var genericParams = genericTypeDefinition.GetGenericArguments(); diff --git a/TUnit.Engine/Discovery/ReflectionHookDiscoveryService.cs b/TUnit.Engine/Discovery/ReflectionHookDiscoveryService.cs index 7501cead8e..2048aa8187 100644 --- a/TUnit.Engine/Discovery/ReflectionHookDiscoveryService.cs +++ b/TUnit.Engine/Discovery/ReflectionHookDiscoveryService.cs @@ -15,7 +15,6 @@ namespace TUnit.Engine.Discovery; /// #if NET6_0_OR_GREATER [RequiresUnreferencedCode("Uses reflection to access nested members")] -[RequiresDynamicCode("Uses reflection to access nested members")] #endif internal sealed class ReflectionHookDiscoveryService { @@ -150,7 +149,6 @@ public static void DiscoverInstanceHooksForType(Type closedGenericType) #if NET6_0_OR_GREATER [RequiresUnreferencedCode("Hook discovery scans assemblies and types using reflection")] - [RequiresDynamicCode("Hook delegate creation may require dynamic code generation")] #endif public static void DiscoverHooks() { diff --git a/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs b/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs index dd76cb83f8..dce151c07d 100644 --- a/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs +++ b/TUnit.Engine/Discovery/ReflectionTestDataCollector.cs @@ -15,11 +15,16 @@ namespace TUnit.Engine.Discovery; /// Discovers tests at runtime using reflection with assembly scanning and caching [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Reflection mode isn't used in AOT scenarios")] [UnconditionalSuppressMessage("Trimming", "IL2062", Justification = "Reflection mode isn't used in AOT scenarios")] +[UnconditionalSuppressMessage("Trimming", "IL2065", Justification = "Reflection mode isn't used in AOT scenarios")] +[UnconditionalSuppressMessage("Trimming", "IL2067:Target parameter argument does not satisfy \'DynamicallyAccessedMembersAttribute\' in call to target method. The parameter of method does not have matching annotations.")] [UnconditionalSuppressMessage("Trimming", "IL2070", Justification = "Reflection mode isn't used in AOT scenarios")] [UnconditionalSuppressMessage("Trimming", "IL2072", Justification = "Reflection mode isn't used in AOT scenarios")] [UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Reflection mode isn't used in AOT scenarios")] +[UnconditionalSuppressMessage("Trimming", "IL2111:Method with parameters or return value with `DynamicallyAccessedMembersAttribute` is accessed via reflection. Trimmer can\'t guarantee availability of the requirements of the method.")] [UnconditionalSuppressMessage("AOT", "IL3000", Justification = "Reflection mode isn't used in AOT scenarios")] [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Reflection mode isn't used in AOT scenarios")] +[UnconditionalSuppressMessage("Trimming", "IL2055:Either the type on which the MakeGenericType is called can\'t be statically determined, or the type parameters to be used for generic arguments can\'t be statically determined.")] +[UnconditionalSuppressMessage("Trimming", "IL2060:Call to \'System.Reflection.MethodInfo.MakeGenericMethod\' can not be statically analyzed. It\'s not possible to guarantee the availability of requirements of the generic method.")] internal sealed class ReflectionTestDataCollector : ITestDataCollector { private static readonly ConcurrentDictionary _scannedAssemblies = new(); @@ -54,10 +59,6 @@ public static void ClearCaches() } } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Assembly scanning uses dynamic type discovery and reflection")] - [RequiresDynamicCode("Generic test instantiation requires MakeGenericType")] - #endif private async Task> ProcessAssemblyAsync(Assembly assembly, SemaphoreSlim semaphore) { await semaphore.WaitAsync().ConfigureAwait(false); @@ -85,10 +86,6 @@ private async Task> ProcessAssemblyAsync(Assembly assembly, S } } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Assembly scanning uses dynamic type discovery and reflection")] - [RequiresDynamicCode("Generic test instantiation requires MakeGenericType")] - #endif public async Task> CollectTestsAsync(string testSessionId) { #if NET @@ -144,10 +141,6 @@ public async Task> CollectTestsAsync(string testSessio } } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Reflection-based test discovery requires dynamic access to types, methods, and attributes")] - [RequiresDynamicCode("Test discovery uses MakeGenericType and dynamic code generation")] - #endif public async IAsyncEnumerable CollectTestsStreamingAsync( string testSessionId, [EnumeratorCancellation] CancellationToken cancellationToken = default) @@ -340,10 +333,6 @@ private static bool ShouldScanAssembly(Assembly assembly) return true; } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Assembly scanning uses dynamic type discovery and reflection")] - [RequiresDynamicCode("Generic test instantiation requires MakeGenericType")] - #endif private static async Task> DiscoverTestsInAssembly(Assembly assembly) { var discoveredTests = new List(100); @@ -442,10 +431,6 @@ private static async Task> DiscoverTestsInAssembly(Assembly a return discoveredTests; } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Assembly scanning uses dynamic type discovery and reflection")] - [RequiresDynamicCode("Generic test instantiation requires MakeGenericType")] - #endif private static async IAsyncEnumerable DiscoverTestsInAssemblyStreamingAsync( Assembly assembly, [EnumeratorCancellation] CancellationToken cancellationToken = default) @@ -585,10 +570,6 @@ private static async IAsyncEnumerable DiscoverTestsInAssemblyStrea } } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Generic type resolution requires reflection and dynamic type creation")] - [RequiresDynamicCode("Generic type instantiation uses MakeGenericType")] - #endif private static async Task> DiscoverGenericTests(Type genericTypeDefinition) { var discoveredTests = new List(100); @@ -678,10 +659,6 @@ private static async Task> DiscoverGenericTests(Type genericT return discoveredTests; } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Generic type resolution requires reflection and dynamic type creation")] - [RequiresDynamicCode("Generic type instantiation uses MakeGenericType")] - #endif private static async IAsyncEnumerable DiscoverGenericTestsStreamingAsync( Type genericTypeDefinition, [EnumeratorCancellation] CancellationToken cancellationToken = default) @@ -866,10 +843,6 @@ private static int CalculateInheritanceDepth(Type testClass, MethodInfo testMeth return depth; } - #if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "BuildTestMetadata calls other reflection methods that are already annotated")] - [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "BuildTestMetadata calls CreateInstanceFactory and CreateTestInvoker which are properly annotated")] - #endif private static Task BuildTestMetadata( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type testClass, @@ -960,12 +933,6 @@ private static string GenerateTestName(Type testClass, MethodInfo testMethod) return testMethod.Name; } - - - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Instance creation uses reflection and Activator.CreateInstance")] - [RequiresDynamicCode("Generic type instantiation uses MakeGenericType and Activator")] - #endif private static Func CreateInstanceFactory([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type testClass) { // For generic types, we need to handle MakeGenericType @@ -1024,27 +991,11 @@ private static string GenerateTestName(Type testClass, MethodInfo testMethod) return (_, args) => factory(args); } - - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Test invocation uses reflection and MethodInfo.Invoke")] - [RequiresDynamicCode("Generic method instantiation uses MakeGenericMethod")] - #endif private static Func CreateTestInvoker(Type testClass, MethodInfo testMethod) { return CreateReflectionTestInvoker(testClass, testMethod); } - - // Hook discovery has been separated into ReflectionHookDiscoveryService - - private static bool IsAsyncMethod(MethodInfo method) - { - return method.ReturnType == typeof(Task) || - method.ReturnType == typeof(ValueTask) || - (method.ReturnType.IsGenericType && - method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)); - } - private static bool IsCompilerGenerated([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type) { // If the type is not marked as compiler-generated, it's not compiler-generated @@ -1079,22 +1030,6 @@ private static bool HasTestMethods([DynamicallyAccessedMembers(DynamicallyAccess } } - private static ParameterInfo[] GetParametersWithoutCancellationToken(MethodInfo method) - { - var parameters = method.GetParameters(); - - // Check if last parameter is CancellationToken and exclude it - if (parameters.Length > 0 && parameters[^1].ParameterType == typeof(CancellationToken)) - { - // Optimize: Manual array copy instead of LINQ Take().ToArray() - var result = new ParameterInfo[parameters.Length - 1]; - Array.Copy(parameters, result, parameters.Length - 1); - return result; - } - - return parameters; - } - private static string? ExtractFilePath(MethodInfo method) { return method.GetCustomAttribute()?.File; @@ -1105,10 +1040,6 @@ private static ParameterInfo[] GetParametersWithoutCancellationToken(MethodInfo return method.GetCustomAttribute()?.Line; } - #if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "This is only called in error cases for assembly scanning failures")] - [UnconditionalSuppressMessage("Trimming", "IL2111", Justification = "Methods are called directly, not via reflection")] - #endif private static TestMetadata CreateFailedTestMetadataForAssembly(Assembly assembly, Exception ex) { var testName = $"[ASSEMBLY SCAN FAILED] {assembly.GetName().Name}"; @@ -1140,9 +1071,6 @@ private static TestMetadata CreateFailedTestMetadataForAssembly(Assembly assembl }; } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Failed test metadata creation uses ReflectionMetadataBuilder")] - #endif private static TestMetadata CreateFailedTestMetadata( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)] Type type, @@ -1241,10 +1169,6 @@ public override Func /// Creates a reflection-based instance factory with proper AOT attribution /// - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Reflection-based factory uses ConstructorInfo.Invoke")] - [RequiresDynamicCode("Dynamic constructor invocation may require runtime code generation")] - #endif private static Func CreateReflectionInstanceFactory(ConstructorInfo ctor) { var isPrepared = false; @@ -1443,9 +1367,6 @@ private static bool IsCovariantCompatible(Type paramType, [DynamicallyAccessedMe /// /// Creates a reflection-based test invoker with proper AOT attribution /// - #if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2060", Justification = "Reflection-based test invoker is only used in reflection mode, not in AOT")] - #endif private static Func CreateReflectionTestInvoker(Type testClass, MethodInfo testMethod) { var isPrepared = false; @@ -1694,10 +1615,6 @@ private static bool IsCovariantCompatible(Type paramType, [DynamicallyAccessedMe }; } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Dynamic test discovery uses reflection to scan assemblies and types")] - [RequiresDynamicCode("Dynamic test builders may use expression compilation")] - #endif private async Task> DiscoverDynamicTests(string testSessionId) { var dynamicTests = new List(50); @@ -1787,10 +1704,6 @@ private async Task> DiscoverDynamicTests(string testSessionId return dynamicTests; } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Dynamic test discovery uses reflection to scan assemblies and types")] - [RequiresDynamicCode("Dynamic test builders may use expression compilation")] - #endif private async IAsyncEnumerable DiscoverDynamicTestsStreamingAsync( string testSessionId, [EnumeratorCancellation] CancellationToken cancellationToken = default) @@ -1852,10 +1765,6 @@ private async IAsyncEnumerable DiscoverDynamicTestsStreamingAsync( } } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Dynamic test builder execution uses Activator.CreateInstance and MethodInfo.Invoke")] - [RequiresDynamicCode("Expression compilation is used for dynamic tests")] - #endif private async Task> ExecuteDynamicTestBuilder(Type testClass, MethodInfo builderMethod, string testSessionId) { var dynamicTests = new List(50); @@ -1887,10 +1796,6 @@ private async Task> ExecuteDynamicTestBuilder(Type testClass, return dynamicTests; } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Dynamic test metadata creation uses reflection")] - [RequiresDynamicCode("Expression compilation is used for dynamic test invocation")] - #endif private async Task> ConvertDynamicTestToMetadata(AbstractDynamicTest abstractDynamicTest) { var testMetadataList = new List(); @@ -1907,10 +1812,6 @@ private async Task> ConvertDynamicTestToMetadata(AbstractDyna return testMetadataList; } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Dynamic test builder execution uses Activator.CreateInstance and MethodInfo.Invoke")] - [RequiresDynamicCode("Expression compilation is used for dynamic tests")] - #endif private async IAsyncEnumerable ExecuteDynamicTestBuilderStreamingAsync( Type testClass, MethodInfo builderMethod, string testSessionId, [EnumeratorCancellation] CancellationToken cancellationToken = default) @@ -1992,10 +1893,6 @@ private async IAsyncEnumerable ExecuteDynamicTestBuilderStreamingA } } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Dynamic test metadata creation uses reflection")] - [RequiresDynamicCode("Expression compilation is used for dynamic test invocation")] - #endif private Task CreateMetadataFromDynamicDiscoveryResult(DynamicDiscoveryResult result) { if (result.TestClassType == null || result.TestMethod == null) @@ -2046,10 +1943,6 @@ private Task CreateMetadataFromDynamicDiscoveryResult(DynamicDisco return Task.FromResult(metadata); } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Dynamic instance factory uses Activator.CreateInstance")] - [RequiresDynamicCode("Generic type construction uses MakeGenericType")] - #endif private static Func CreateDynamicInstanceFactory(Type testClass, object?[]? predefinedClassArgs) { // For dynamic tests, we always use the predefined args (or empty array if null) @@ -2076,10 +1969,6 @@ private Task CreateMetadataFromDynamicDiscoveryResult(DynamicDisco }; } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Dynamic test invoker uses expression compilation and reflection")] - [RequiresDynamicCode("LambdaExpression.Compile requires dynamic code generation")] - #endif private static Func CreateDynamicTestInvoker(DynamicDiscoveryResult result) { return async (instance, args) => @@ -2135,10 +2024,6 @@ private Task CreateMetadataFromDynamicDiscoveryResult(DynamicDisco }; } - #if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2077", Justification = "This is only called in error cases for dynamic source failures")] - [UnconditionalSuppressMessage("Trimming", "IL2072", Justification = "Object.GetType() doesn't preserve annotations, but this is for error reporting only")] - #endif private static TestMetadata CreateFailedTestMetadataForDynamicSource(IDynamicTestSource source, Exception ex) { var testName = $"[DYNAMIC SOURCE FAILED] {source.GetType().Name}"; @@ -2159,9 +2044,6 @@ private static TestMetadata CreateFailedTestMetadataForDynamicSource(IDynamicTes }; } - #if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "This is only called in error cases for dynamic builder failures")] - #endif private static TestMetadata CreateFailedTestMetadataForDynamicBuilder( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)] Type type, diff --git a/TUnit.Engine/Framework/IRequestHandler.cs b/TUnit.Engine/Framework/IRequestHandler.cs index 4704b0040a..ff653f3168 100644 --- a/TUnit.Engine/Framework/IRequestHandler.cs +++ b/TUnit.Engine/Framework/IRequestHandler.cs @@ -8,8 +8,5 @@ namespace TUnit.Engine.Framework; /// internal interface IRequestHandler { - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Generic test instantiation requires MakeGenericType")] - #endif Task HandleRequestAsync(TestExecutionRequest request, TUnitServiceProvider serviceProvider, ExecuteRequestContext context, ITestExecutionFilter? testExecutionFilter); } diff --git a/TUnit.Engine/Framework/TUnitTestFramework.cs b/TUnit.Engine/Framework/TUnitTestFramework.cs index 95f02e927c..9c68fcc49c 100644 --- a/TUnit.Engine/Framework/TUnitTestFramework.cs +++ b/TUnit.Engine/Framework/TUnitTestFramework.cs @@ -44,8 +44,6 @@ public Task CreateTestSessionAsync(CreateTestSessionCon } #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Test data collector selection may use reflection-based discovery")] - [RequiresDynamicCode("Reflection mode test discovery uses dynamic code generation")] [UnconditionalSuppressMessage("Trimming", "IL2046", Justification = "Reflection mode is not used in AOT/trimmed scenarios")] [UnconditionalSuppressMessage("AOT", "IL3051", Justification = "Reflection mode is not used in AOT scenarios")] #endif @@ -118,10 +116,6 @@ public async Task CloseTestSessionAsync(CloseTestSession return new CloseTestSessionResult { IsSuccess = isSuccess }; } - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Test data collector selection may use reflection-based discovery")] - [RequiresDynamicCode("Reflection mode test discovery uses dynamic code generation")] - #endif private TUnitServiceProvider GetOrCreateServiceProvider(ExecuteRequestContext context) { return _serviceProvidersPerSession.GetOrAdd( diff --git a/TUnit.Engine/Framework/TestRequestHandler.cs b/TUnit.Engine/Framework/TestRequestHandler.cs index 15c75e9434..38e447c779 100644 --- a/TUnit.Engine/Framework/TestRequestHandler.cs +++ b/TUnit.Engine/Framework/TestRequestHandler.cs @@ -7,9 +7,6 @@ namespace TUnit.Engine.Framework; internal sealed class TestRequestHandler : IRequestHandler { - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Generic test instantiation requires MakeGenericType")] - #endif public async Task HandleRequestAsync(TestExecutionRequest request, TUnitServiceProvider serviceProvider, ExecuteRequestContext context, ITestExecutionFilter? testExecutionFilter) { switch (request) @@ -30,9 +27,6 @@ public async Task HandleRequestAsync(TestExecutionRequest request, TUnitServiceP } } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Generic test instantiation requires MakeGenericType")] - #endif private async Task HandleDiscoveryRequestAsync( TUnitServiceProvider serviceProvider, ExecuteRequestContext context, @@ -57,9 +51,6 @@ private async Task HandleDiscoveryRequestAsync( } } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Generic test instantiation requires MakeGenericType")] - #endif private async Task HandleRunRequestAsync( TUnitServiceProvider serviceProvider, RunTestExecutionRequest request, diff --git a/TUnit.Engine/Interfaces/ITestCoordinator.cs b/TUnit.Engine/Interfaces/ITestCoordinator.cs index 33200b7dc0..fe91169564 100644 --- a/TUnit.Engine/Interfaces/ITestCoordinator.cs +++ b/TUnit.Engine/Interfaces/ITestCoordinator.cs @@ -9,8 +9,5 @@ namespace TUnit.Engine.Interfaces; /// internal interface ITestCoordinator { - #if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Test execution involves reflection for hooks and initialization")] - #endif Task ExecuteTestAsync(AbstractExecutableTest test, CancellationToken cancellationToken); } diff --git a/TUnit.Engine/Services/ReflectionStaticPropertyInitializer.cs b/TUnit.Engine/Services/ReflectionStaticPropertyInitializer.cs index bc652c4bd9..fa1bab8b5b 100644 --- a/TUnit.Engine/Services/ReflectionStaticPropertyInitializer.cs +++ b/TUnit.Engine/Services/ReflectionStaticPropertyInitializer.cs @@ -11,7 +11,6 @@ namespace TUnit.Engine.Services; /// #if NET6_0_OR_GREATER [RequiresUnreferencedCode("Uses reflection to discover and initialize static properties")] -[RequiresDynamicCode("Static property initialization may require dynamic code generation")] #endif internal sealed class ReflectionStaticPropertyInitializer : IStaticPropertyInitializer { diff --git a/TUnit.Engine/Services/TestExecution/TestCoordinator.cs b/TUnit.Engine/Services/TestExecution/TestCoordinator.cs index 1770eaba14..692bcf8481 100644 --- a/TUnit.Engine/Services/TestExecution/TestCoordinator.cs +++ b/TUnit.Engine/Services/TestExecution/TestCoordinator.cs @@ -46,18 +46,12 @@ public TestCoordinator( _eventReceiverOrchestrator = eventReceiverOrchestrator; } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Test execution involves reflection for hooks and initialization")] - #endif public async Task ExecuteTestAsync(AbstractExecutableTest test, CancellationToken cancellationToken) { await _executionGuard.TryStartExecutionAsync(test.TestId, () => ExecuteTestInternalAsync(test, cancellationToken)); } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Test execution involves reflection for hooks and initialization")] - #endif private async Task ExecuteTestInternalAsync(AbstractExecutableTest test, CancellationToken cancellationToken) { try diff --git a/TUnit.Engine/Services/TestRegistry.cs b/TUnit.Engine/Services/TestRegistry.cs index ed58951ab4..131c9feb58 100644 --- a/TUnit.Engine/Services/TestRegistry.cs +++ b/TUnit.Engine/Services/TestRegistry.cs @@ -20,7 +20,6 @@ internal sealed class TestRegistry : ITestRegistry private readonly CancellationToken _sessionCancellationToken; private readonly string? _sessionId; - public TestRegistry(TestBuilderPipeline testBuilderPipeline, ITestCoordinator testCoordinator, string sessionId, @@ -31,8 +30,8 @@ public TestRegistry(TestBuilderPipeline testBuilderPipeline, _sessionId = sessionId; _sessionCancellationToken = sessionCancellationToken; } - [RequiresDynamicCode("Adding dynamic tests requires runtime compilation and reflection which are not supported in native AOT scenarios.")] - [RequiresUnreferencedCode("Dynamic test metadata creation uses reflection")] + + [RequiresUnreferencedCode("Adding dynamic tests requires reflection which is not supported in native AOT scenarios.")] public async Task AddDynamicTest<[DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors @@ -66,10 +65,7 @@ public TestRegistry(TestBuilderPipeline testBuilderPipeline, await ProcessPendingDynamicTests(); } -#if NET6_0_OR_GREATER - [RequiresDynamicCode("Dynamic test processing uses expression compilation")] - [RequiresUnreferencedCode("Dynamic test metadata creation uses reflection")] -#endif + [RequiresUnreferencedCode("Processing dynamic tests requires reflection which is not supported in native AOT scenarios.")] private async Task ProcessPendingDynamicTests() { var testsToProcess = new List(); @@ -102,8 +98,7 @@ private async Task ProcessPendingDynamicTests() } } - [RequiresDynamicCode("Dynamic tests require runtime compilation of lambda expressions and are not supported in native AOT scenarios.")] - [RequiresUnreferencedCode("Method metadata creation uses reflection on parameters and types")] + [RequiresUnreferencedCode("Dynamic test metadata creation requires reflection which is not supported in native AOT scenarios.")] private async Task CreateMetadataFromDynamicDiscoveryResult(DynamicDiscoveryResult result) { if (result.TestClassType == null || result.TestMethod == null) @@ -152,7 +147,7 @@ private async Task CreateMetadataFromDynamicDiscoveryResult(Dynami }); } - [RequiresDynamicCode("Dynamic test instance creation requires Activator.CreateInstance which is not supported in native AOT scenarios.")] + [RequiresUnreferencedCode("Dynamic test instance creation requires Activator.CreateInstance which is not supported in native AOT scenarios.")] [UnconditionalSuppressMessage("Trimming", "IL2067:Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call", Justification = "Dynamic tests require reflection")] @@ -173,7 +168,7 @@ private async Task CreateMetadataFromDynamicDiscoveryResult(Dynami }; } - [RequiresDynamicCode("Dynamic test invocation requires LambdaExpression.Compile() which is not supported in native AOT scenarios.")] + [RequiresUnreferencedCode("Dynamic test invocation requires LambdaExpression.Compile() which is not supported in native AOT scenarios.")] private static Func CreateRuntimeTestInvoker(DynamicDiscoveryResult result) { return async (instance, args) => diff --git a/TUnit.Engine/TestDiscoveryService.cs b/TUnit.Engine/TestDiscoveryService.cs index aee72fda4a..0d11915400 100644 --- a/TUnit.Engine/TestDiscoveryService.cs +++ b/TUnit.Engine/TestDiscoveryService.cs @@ -48,9 +48,6 @@ public TestDiscoveryService(TestExecutor testExecutor, TestBuilderPipeline testB _testFilterService = testFilterService; } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Generic test instantiation requires MakeGenericType")] - #endif public async Task DiscoverTests(string testSessionId, ITestExecutionFilter? filter, CancellationToken cancellationToken, bool isForExecution) { await _testExecutor.ExecuteBeforeTestDiscoveryHooksAsync(cancellationToken).ConfigureAwait(false); @@ -163,7 +160,7 @@ private async IAsyncEnumerable DiscoverTestsStreamAsync( /// Simplified streaming test discovery without channels - matches source generation approach /// #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresDynamicCode("Generic test instantiation requires MakeGenericType")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Generic test instantiation requires MakeGenericType")] #endif public async IAsyncEnumerable DiscoverTestsFullyStreamingAsync( string testSessionId, diff --git a/TUnit.Engine/TestExecutor.cs b/TUnit.Engine/TestExecutor.cs index 24b9521b8e..cba434941f 100644 --- a/TUnit.Engine/TestExecutor.cs +++ b/TUnit.Engine/TestExecutor.cs @@ -242,36 +242,15 @@ public IContextProvider GetContextProvider() return _contextProvider; } -#if NET6_0_OR_GREATER - [RequiresUnreferencedCode("Test disposal may use reflection on properties")] -#endif internal static async Task DisposeTestInstance(AbstractExecutableTest test) { // Dispose the test instance if it's disposable - if (test.Context.TestDetails.ClassInstance != null && test.Context.TestDetails.ClassInstance is not SkippedTestInstance) + if (test.Context.TestDetails.ClassInstance is not SkippedTestInstance) { try { var instance = test.Context.TestDetails.ClassInstance; - // Special handling for DisposalRegressionTests - dispose its properties - if (instance.GetType().Name == "DisposalRegressionTests") - { - var injectedDataProperty = instance.GetType().GetProperty("InjectedData"); - if (injectedDataProperty != null) - { - var injectedData = injectedDataProperty.GetValue(instance); - if (injectedData is IAsyncDisposable asyncDisposable) - { - await asyncDisposable.DisposeAsync().ConfigureAwait(false); - } - else if (injectedData is IDisposable disposable) - { - disposable.Dispose(); - } - } - } - switch (instance) { case IAsyncDisposable asyncDisposable: diff --git a/TUnit.Engine/TestInitializer.cs b/TUnit.Engine/TestInitializer.cs index 92e92f4c16..09e5859366 100644 --- a/TUnit.Engine/TestInitializer.cs +++ b/TUnit.Engine/TestInitializer.cs @@ -20,9 +20,6 @@ public TestInitializer(EventReceiverOrchestrator eventReceiverOrchestrator, _objectTracker = objectTracker; } - #if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Object tracking may use reflection on properties")] - #endif public async Task InitializeTest(AbstractExecutableTest test, CancellationToken cancellationToken) { var testClassInstance = test.Context.TestDetails.ClassInstance; diff --git a/TUnit.Pipeline/Modules/PublishAOTModule.cs b/TUnit.Pipeline/Modules/PublishAOTModule.cs deleted file mode 100644 index e9f4ade9bd..0000000000 --- a/TUnit.Pipeline/Modules/PublishAOTModule.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Runtime.InteropServices; -using ModularPipelines.Context; -using ModularPipelines.DotNet.Extensions; -using ModularPipelines.DotNet.Options; -using ModularPipelines.Enums; -using ModularPipelines.Extensions; -using ModularPipelines.Git.Extensions; -using ModularPipelines.Models; -using ModularPipelines.Modules; - -namespace TUnit.Pipeline.Modules; - -public class PublishAOTModule : Module -{ - protected override Task ShouldSkip(IPipelineContext context) - { - return Task.FromResult(EnvironmentVariables.IsNetFramework); - } - - protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - var testProject = context.Git().RootDirectory.FindFile(x => x.Name == "TUnit.TestProject.csproj").AssertExists(); - - return await context.DotNet().Publish(new DotNetPublishOptions(testProject) - { - RuntimeIdentifier = GetRuntimeIdentifier(), - Configuration = Configuration.Release, - OutputDirectory = "TESTPROJECT_AOT", - Properties = [new KeyValue("Aot", "true")], - Framework = "net8.0", - CommandLogging = CommandLogging.Input | CommandLogging.Error | CommandLogging.Duration | CommandLogging.ExitCode - }, cancellationToken); - } - - private string GetRuntimeIdentifier() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return "linux-x64"; - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return "win-x64"; - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return "osx-arm64"; - } - - throw new ArgumentException("Unknown platform"); - } -} diff --git a/TUnit.Pipeline/Modules/PublishSingleFileModule.cs b/TUnit.Pipeline/Modules/PublishSingleFileModule.cs deleted file mode 100644 index 459fb6a835..0000000000 --- a/TUnit.Pipeline/Modules/PublishSingleFileModule.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Runtime.InteropServices; -using ModularPipelines.Attributes; -using ModularPipelines.Context; -using ModularPipelines.DotNet.Extensions; -using ModularPipelines.DotNet.Options; -using ModularPipelines.Enums; -using ModularPipelines.Extensions; -using ModularPipelines.Git.Extensions; -using ModularPipelines.Models; -using ModularPipelines.Modules; - -namespace TUnit.Pipeline.Modules; - -[DependsOn] -public class PublishSingleFileModule : Module -{ - protected override Task ShouldSkip(IPipelineContext context) - { - return Task.FromResult(EnvironmentVariables.IsNetFramework); - } - - protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) - { - var testProject = context.Git().RootDirectory.FindFile(x => x.Name == "TUnit.TestProject.csproj").AssertExists(); - - return await context.DotNet().Publish(new DotNetPublishOptions(testProject) - { - RuntimeIdentifier = GetRuntimeIdentifier(), - Configuration = Configuration.Release, - OutputDirectory = "TESTPROJECT_SINGLEFILE", - Properties = [new KeyValue("SingleFile", "true")], - Framework = "net8.0", - CommandLogging = CommandLogging.Input | CommandLogging.Error | CommandLogging.Duration | CommandLogging.ExitCode - }, cancellationToken); - } - - private string GetRuntimeIdentifier() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return "linux-x64"; - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return "win-x64"; - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return "osx-arm64"; - } - - throw new ArgumentException("Unknown platform"); - } -} diff --git a/TUnit.Pipeline/Modules/RunEngineTestsModule.cs b/TUnit.Pipeline/Modules/RunEngineTestsModule.cs index 44d482de3d..b21eb0f597 100644 --- a/TUnit.Pipeline/Modules/RunEngineTestsModule.cs +++ b/TUnit.Pipeline/Modules/RunEngineTestsModule.cs @@ -11,8 +11,6 @@ namespace TUnit.Pipeline.Modules; [NotInParallel("DotNetTests")] -[DependsOn] -[DependsOn] [DependsOn] [DependsOn] [DependsOn] 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 770ced7811..adbd48b92a 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 @@ -1181,6 +1181,7 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + [.("Uses reflection for structural equivalency comparison")] public class NotStructuralEquivalencyAssertion : . { public NotStructuralEquivalencyAssertion(. context, object? notExpected, string? notExpectedExpression = null) { } @@ -1364,6 +1365,7 @@ namespace .Conditions [.("IsNullOrWhiteSpace", CustomName="IsNotNullOrWhiteSpace", ExpectationMessage="be null, empty, or whitespace", NegateLogic=true)] [.("IsNullOrWhiteSpace", ExpectationMessage="be null, empty, or whitespace")] public static class StringStaticMethodAssertions { } + [.("Uses reflection to compare object properties and fields.")] public class StructuralEquivalencyAssertion : . { public StructuralEquivalencyAssertion(. context, object? expected, string? expectedExpression = null) { } @@ -1868,6 +1870,7 @@ namespace .Extensions public static ..IsDefinedAssertion IsDefined(this . source) where TEnum : struct, { } public static . IsEqualTo(this . source, string? expected, comparison, [.("expected")] string? expression = null) { } + [.("Uses reflection to compare members")] public static . IsEquivalentTo(this . source, object? expected, [.("expected")] string? expression = null) { } public static . IsIn(this . source, params TValue[] collection) { } public static . IsNegative(this . source) @@ -1877,6 +1880,7 @@ namespace .Extensions public static . IsNotAssignableTo(this . source) { } public static ..IsNotDefinedAssertion IsNotDefined(this . source) where TEnum : struct, { } + [.("Uses reflection to compare members")] public static . IsNotEquivalentTo(this . source, object? expected, [.("expected")] string? expression = null) { } public static . IsNotIn(this . source, params TValue[] collection) { } public static . IsNotNull(this . source) 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 5833d318fa..a2daeaf63b 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 @@ -1178,6 +1178,7 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + [.("Uses reflection for structural equivalency comparison")] public class NotStructuralEquivalencyAssertion : . { public NotStructuralEquivalencyAssertion(. context, object? notExpected, string? notExpectedExpression = null) { } @@ -1361,6 +1362,7 @@ namespace .Conditions [.("IsNullOrWhiteSpace", CustomName="IsNotNullOrWhiteSpace", ExpectationMessage="be null, empty, or whitespace", NegateLogic=true)] [.("IsNullOrWhiteSpace", ExpectationMessage="be null, empty, or whitespace")] public static class StringStaticMethodAssertions { } + [.("Uses reflection to compare object properties and fields.")] public class StructuralEquivalencyAssertion : . { public StructuralEquivalencyAssertion(. context, object? expected, string? expectedExpression = null) { } @@ -1865,6 +1867,7 @@ namespace .Extensions public static ..IsDefinedAssertion IsDefined(this . source) where TEnum : struct, { } public static . IsEqualTo(this . source, string? expected, comparison, [.("expected")] string? expression = null) { } + [.("Uses reflection to compare members")] public static . IsEquivalentTo(this . source, object? expected, [.("expected")] string? expression = null) { } public static . IsIn(this . source, params TValue[] collection) { } public static . IsNegative(this . source) @@ -1874,6 +1877,7 @@ namespace .Extensions public static . IsNotAssignableTo(this . source) { } public static ..IsNotDefinedAssertion IsNotDefined(this . source) where TEnum : struct, { } + [.("Uses reflection to compare members")] public static . IsNotEquivalentTo(this . source, object? expected, [.("expected")] string? expression = null) { } public static . IsNotIn(this . source, params TValue[] collection) { } public static . IsNotNull(this . source) 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 99a13e4d9a..249067434e 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 @@ -1181,6 +1181,7 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + [.("Uses reflection for structural equivalency comparison")] public class NotStructuralEquivalencyAssertion : . { public NotStructuralEquivalencyAssertion(. context, object? notExpected, string? notExpectedExpression = null) { } @@ -1364,6 +1365,7 @@ namespace .Conditions [.("IsNullOrWhiteSpace", CustomName="IsNotNullOrWhiteSpace", ExpectationMessage="be null, empty, or whitespace", NegateLogic=true)] [.("IsNullOrWhiteSpace", ExpectationMessage="be null, empty, or whitespace")] public static class StringStaticMethodAssertions { } + [.("Uses reflection to compare object properties and fields.")] public class StructuralEquivalencyAssertion : . { public StructuralEquivalencyAssertion(. context, object? expected, string? expectedExpression = null) { } @@ -1868,6 +1870,7 @@ namespace .Extensions public static ..IsDefinedAssertion IsDefined(this . source) where TEnum : struct, { } public static . IsEqualTo(this . source, string? expected, comparison, [.("expected")] string? expression = null) { } + [.("Uses reflection to compare members")] public static . IsEquivalentTo(this . source, object? expected, [.("expected")] string? expression = null) { } public static . IsIn(this . source, params TValue[] collection) { } public static . IsNegative(this . source) @@ -1877,6 +1880,7 @@ namespace .Extensions public static . IsNotAssignableTo(this . source) { } public static ..IsNotDefinedAssertion IsNotDefined(this . source) where TEnum : struct, { } + [.("Uses reflection to compare members")] public static . IsNotEquivalentTo(this . source, object? expected, [.("expected")] string? expression = null) { } public static . IsNotIn(this . source, params TValue[] collection) { } public static . IsNotNull(this . source) diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt index b0597cb32e..ac536d62a6 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet10_0.verified.txt @@ -118,19 +118,14 @@ namespace public static . Convert( action) { } public static . Convert(<.> action) { } public static . Convert(<.> action) { } - [.("ConvertObject may require dynamic invocation for custom awaitable types. For AOT " + - "compatibility, use Task or ValueTask directly.")] - [.("ConvertObject uses reflection to handle custom awaitable types and F# async. For " + - "AOT compatibility, use Task or ValueTask directly.")] public static . ConvertObject(object? invoke) { } - [.("Custom awaitable handling may require dynamic invocation. For AOT, use Task/Value" + - "Task.")] - [.("GetAwaiter pattern detection requires reflection for custom awaitable types. For " + - "AOT, use Task/ValueTask.")] + [.("Trimming", "IL2075:\'this\' argument does not satisfy \'DynamicallyAccessedMembersAttribute\' in " + + "call to target method. The return value of the source method does not have match" + + "ing annotations.")] public static bool TryGetAwaitableTask(object awaitable, [.(true)] out .? task) { } } [(.Class | .Method | .Property, AllowMultiple=true)] - public abstract class AsyncDataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T> : .TypedDataSourceAttribute + public abstract class AsyncDataSourceGeneratorAttribute<[.(..PublicConstructors)] T> : .TypedDataSourceAttribute { protected AsyncDataSourceGeneratorAttribute() { } protected abstract .<<.>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata); @@ -138,7 +133,7 @@ namespace public override sealed .<<.>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class AsyncDataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2> : .TypedDataSourceAttribute<> + public abstract class AsyncDataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2> : .TypedDataSourceAttribute<> { protected AsyncDataSourceGeneratorAttribute() { } protected abstract .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata); @@ -146,7 +141,7 @@ namespace public override sealed .<<.<>>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class AsyncDataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3> : .TypedDataSourceAttribute<> + public abstract class AsyncDataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3> : .TypedDataSourceAttribute<> { protected AsyncDataSourceGeneratorAttribute() { } protected abstract .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata); @@ -154,7 +149,7 @@ namespace public override sealed .<<.<>>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class AsyncDataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T4> : .TypedDataSourceAttribute<> + public abstract class AsyncDataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3, [.(..PublicConstructors)] T4> : .TypedDataSourceAttribute<> { protected AsyncDataSourceGeneratorAttribute() { } protected abstract .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata); @@ -162,7 +157,7 @@ namespace public override .<<.<>>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class AsyncDataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T5> : .TypedDataSourceAttribute<> + public abstract class AsyncDataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3, [.(..PublicConstructors)] T4, [.(..PublicConstructors)] T5> : .TypedDataSourceAttribute<> { protected AsyncDataSourceGeneratorAttribute() { } protected abstract .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata); @@ -183,12 +178,6 @@ namespace } } [(.Class | .Method | .Property, AllowMultiple=true)] - [.("AsyncUntypedDataSourceGeneratorAttribute requires dynamic code generation for run" + - "time data source creation. Consider using strongly-typed AsyncDataSourceGenerato" + - "rAttribute overloads for AOT compatibility.")] - [.("AsyncUntypedDataSourceGeneratorAttribute may require unreferenced code for runtim" + - "e data source creation. Consider using strongly-typed AsyncDataSourceGeneratorAt" + - "tribute overloads for AOT compatibility.")] public abstract class AsyncUntypedDataSourceGeneratorAttribute : , .IDataSourceAttribute { protected AsyncUntypedDataSourceGeneratorAttribute() { } @@ -247,56 +236,36 @@ namespace public required string TestSessionId { get; init; } } [(.Class | .Method | .Property, AllowMultiple=true)] - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] public sealed class ClassDataSourceAttribute : .UntypedDataSourceGeneratorAttribute { - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] - public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type) { } - [.("Reflection")] - [.("Reflection")] + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access otherwise can break functionality when trimming application code", Justification="Non-params constructor calls params one with proper annotations.")] + public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type) { } + [.("Uses dynamically accessed types.")] public ClassDataSourceAttribute(params [] types) { } - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] - public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type2) { } - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] - public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type3) { } - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] - public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type4) { } - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] - public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type5) { } + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access otherwise can break functionality when trimming application code", Justification="Non-params constructor calls params one with proper annotations.")] + public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type2) { } + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access otherwise can break functionality when trimming application code", Justification="Non-params constructor calls params one with proper annotations.")] + public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type3) { } + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access otherwise can break functionality when trimming application code", Justification="Non-params constructor calls params one with proper annotations.")] + public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type4) { } + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access otherwise can break functionality when trimming application code", Justification="Non-params constructor calls params one with proper annotations.")] + public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type5) { } public string[] Keys { get; set; } public .SharedType[] Shared { get; set; } - [.("Reflection")] - [.("Reflection")] + [.("Trimming", "IL2062:The parameter of method has a DynamicallyAccessedMembersAttribute, but the" + + " value passed to it can not be statically analyzed.", Justification="Constructor parameter is annotated with DynamicallyAccessedMembers, so _types ele" + + "ments have the required annotations.")] protected override .<> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata) { } public . GetKeys() { } public .<.SharedType> GetSharedTypes() { } } [(.Class | .Method | .Property, AllowMultiple=true)] - public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T> : .DataSourceGeneratorAttribute + public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T> : .DataSourceGeneratorAttribute { public ClassDataSourceAttribute() { } public ClassType { get; } @@ -307,7 +276,7 @@ namespace public .<.SharedType> GetSharedTypes() { } } [(.Class | .Method, AllowMultiple=true)] - public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T2> : .DataSourceGeneratorAttribute + public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T2> : .DataSourceGeneratorAttribute where T1 : new() where T2 : new() { @@ -319,7 +288,7 @@ namespace public .<.SharedType> GetSharedTypes() { } } [(.Class | .Method, AllowMultiple=true)] - public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T3> : .DataSourceGeneratorAttribute + public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T3> : .DataSourceGeneratorAttribute where T1 : new() where T2 : new() where T3 : new() @@ -332,7 +301,7 @@ namespace public .<.SharedType> GetSharedTypes() { } } [(.Class | .Method, AllowMultiple=true)] - public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T4> : .DataSourceGeneratorAttribute + public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T4> : .DataSourceGeneratorAttribute where T1 : new() where T2 : new() where T3 : new() @@ -346,7 +315,7 @@ namespace public .<.SharedType> GetSharedTypes() { } } [(.Class | .Method, AllowMultiple=true)] - public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T5> : .DataSourceGeneratorAttribute + public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T5> : .DataSourceGeneratorAttribute where T1 : new() where T2 : new() where T3 : new() @@ -374,7 +343,7 @@ namespace public override int GetHashCode() { } } [.DebuggerDisplay("{Type}")] - public class ClassMetadata : .MemberMetadata, <.ClassMetadata> + public class ClassMetadata : <.ClassMetadata>, .IMemberMetadata { public ClassMetadata() { } public required .AssemblyMetadata Assembly { get; init; } @@ -382,14 +351,15 @@ namespace public bool HasRequiredProperties { get; init; } public []? ImplementedInterfaces { get; init; } public ? InitializeRequiredProperties { get; init; } + public required string Name { get; init; } public required string? Namespace { get; init; } public ? ParameterlessFactory { get; init; } public required .ParameterMetadata[] Parameters { get; init; } public required .ClassMetadata? Parent { get; init; } public required .PropertyMetadata[] Properties { get; init; } public int TestConstructorIndex { get; init; } - [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] - public override required Type { get; init; } + [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..PublicProperties)] + public required Type { get; init; } public required .TypeInfo TypeInfo { get; init; } public virtual bool Equals(.ClassMetadata? other) { } public override int GetHashCode() { } @@ -454,7 +424,7 @@ namespace { public DataGeneratorMetadata() { } public required object?[]? ClassInstanceArguments { get; init; } - public required .MemberMetadata[] MembersToGenerate { get; init; } + public required .IMemberMetadata[] MembersToGenerate { get; init; } public required .TestBuilderContextAccessor TestBuilderContext { get; init; } public required object? TestClassInstance { get; init; } public required .MethodMetadata? TestInformation { get; init; } @@ -478,7 +448,7 @@ namespace public string DataSourceName { get; } } [(.Class | .Method | .Property, AllowMultiple=true)] - public abstract class DataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T> : .AsyncDataSourceGeneratorAttribute + public abstract class DataSourceGeneratorAttribute<[.(..PublicConstructors)] T> : .AsyncDataSourceGeneratorAttribute { protected DataSourceGeneratorAttribute() { } protected abstract .<> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata); @@ -486,7 +456,7 @@ namespace protected override .<<.>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class DataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2> : .AsyncDataSourceGeneratorAttribute + public abstract class DataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2> : .AsyncDataSourceGeneratorAttribute { protected DataSourceGeneratorAttribute() { } protected abstract .<<>> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata); @@ -494,7 +464,7 @@ namespace protected override .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class DataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3> : .AsyncDataSourceGeneratorAttribute + public abstract class DataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3> : .AsyncDataSourceGeneratorAttribute { protected DataSourceGeneratorAttribute() { } protected abstract .<<>> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata); @@ -502,7 +472,7 @@ namespace protected override sealed .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class DataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T4> : .AsyncDataSourceGeneratorAttribute + public abstract class DataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3, [.(..PublicConstructors)] T4> : .AsyncDataSourceGeneratorAttribute { protected DataSourceGeneratorAttribute() { } protected abstract .<<>> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata); @@ -510,7 +480,7 @@ namespace protected override .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class DataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T5> : .AsyncDataSourceGeneratorAttribute + public abstract class DataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3, [.(..PublicConstructors)] T4, [.(..PublicConstructors)] T5> : .AsyncDataSourceGeneratorAttribute { protected DataSourceGeneratorAttribute() { } protected abstract .<<>> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata); @@ -538,12 +508,6 @@ namespace public static readonly .DefaultExecutor Instance; protected override . ExecuteAsync(<.> action) { } } - [.("DependencyInjectionDataSourceAttribute requires dynamic code generation for depen" + - "dency injection container access. This attribute is inherently incompatible with" + - " AOT compilation.")] - [.("DependencyInjectionDataSourceAttribute may require unreferenced code for dependen" + - "cy injection container access. This attribute is inherently incompatible with AO" + - "T compilation.")] public abstract class DependencyInjectionDataSourceAttribute : .UntypedDataSourceGeneratorAttribute { protected DependencyInjectionDataSourceAttribute() { } @@ -881,6 +845,10 @@ namespace { .<.AbstractDynamicTest> CollectDynamicTests(string sessionId); } + public interface IMemberMetadata + { + string Name { get; } + } public interface IRequiresImmediateInitialization { } public interface IScopedAttribute { @@ -920,14 +888,9 @@ namespace public MatrixAttribute(params T?[]? objects) { } } [(.Class | .Method)] - [.("MatrixDataSource may process enum types dynamically")] - [.("MatrixDataSource uses reflection to access parameter attributes and test metadata" + - ". For AOT compatibility, consider using explicit data sources.")] public sealed class MatrixDataSourceAttribute : .UntypedDataSourceGeneratorAttribute, .IAccessesInstanceData { public MatrixDataSourceAttribute() { } - [.("Matrix generation may process enum types dynamically")] - [.("Matrix generation requires reflection")] protected override .<> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] @@ -960,7 +923,7 @@ namespace { protected MemberMetadata() { } public required string Name { get; init; } - [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] + [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..PublicProperties)] public abstract Type { get; init; } public virtual bool Equals(.MemberMetadata? other) { } public override int GetHashCode() { } @@ -989,20 +952,21 @@ namespace public MethodDataSourceAttribute(string methodNameProvidingDataSource) { } } [.DebuggerDisplay("{Type}.{Name}")] - public class MethodMetadata : .MemberMetadata, <.MethodMetadata> + public class MethodMetadata : <.MethodMetadata>, .IMemberMetadata { public MethodMetadata() { } public required .ClassMetadata Class { get; init; } public required int GenericTypeCount { get; init; } + public required string Name { get; init; } public required .ParameterMetadata[] Parameters { get; init; } public ? ReturnType { get; init; } public required .TypeInfo ReturnTypeInfo { get; init; } [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] - public override required Type { get; init; } + public required Type { get; init; } public required .TypeInfo TypeInfo { get; init; } public virtual bool Equals(.MethodMetadata? other) { } public override int GetHashCode() { } - protected override bool PrintMembers(.StringBuilder stringBuilder) { } + protected virtual bool PrintMembers(.StringBuilder stringBuilder) { } } [(.Assembly | .Class | .Method)] public class NotInParallelAttribute : .SingleTUnitAttribute, .IScopedAttribute, ., . @@ -1055,9 +1019,9 @@ namespace public . OnTestRegistered(.TestRegisteredContext context) { } } [.DebuggerDisplay("{Type} {Name}")] - public class ParameterMetadata : .MemberMetadata, <.ParameterMetadata> + public class ParameterMetadata : <.ParameterMetadata>, .IMemberMetadata { - public ParameterMetadata([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] Type) { } + public ParameterMetadata([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] Type) { } public object? CachedDefaultValue { get; init; } public bool? CachedIsOptional { get; init; } public bool? CachedIsParams { get; init; } @@ -1065,14 +1029,15 @@ namespace public bool IsNullable { get; init; } public bool IsOptional { get; } public bool IsParams { get; } + public required string Name { get; init; } public int Position { get; init; } public required .ParameterInfo ReflectionInfo { get; set; } - [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] - public override Type { get; init; } + [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] + public Type { get; init; } public required .TypeInfo TypeInfo { get; init; } } [.DebuggerDisplay("{Type} {Name}")] - public class ParameterMetadata<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] T> : .ParameterMetadata, <.ParameterMetadata> + public class ParameterMetadata<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] T> : .ParameterMetadata, <.ParameterMetadata> { public ParameterMetadata() { } } @@ -1109,7 +1074,7 @@ namespace public PropertyType { get; } } [.DebuggerDisplay("{Type} {Name})")] - public class PropertyMetadata : .MemberMetadata, <.PropertyMetadata> + public class PropertyMetadata : <.PropertyMetadata>, .IMemberMetadata { public PropertyMetadata() { } public required .ClassMetadata ClassMetadata { get; set; } @@ -1120,10 +1085,11 @@ namespace public bool IsNullable { get; init; } public bool IsRequired { get; init; } public required bool IsStatic { get; init; } + public required string Name { get; init; } public required .PropertyInfo ReflectionInfo { get; init; } public ? Setter { get; init; } - [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] - public override required Type { get; init; } + [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] + public required Type { get; init; } } public static class PropertySourceRegistry { @@ -1579,12 +1545,6 @@ namespace public abstract .<<.>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata); } [(.Class | .Method | .Property, AllowMultiple=true)] - [.("UntypedDataSourceGeneratorAttribute requires dynamic code generation for runtime " + - "data source creation. Consider using strongly-typed AsyncDataSourceGeneratorAttr" + - "ibute overloads for AOT compatibility.")] - [.("UntypedDataSourceGeneratorAttribute may require unreferenced code for runtime dat" + - "a source creation. Consider using strongly-typed AsyncDataSourceGeneratorAttribu" + - "te overloads for AOT compatibility.")] public abstract class UntypedDataSourceGeneratorAttribute : .AsyncUntypedDataSourceGeneratorAttribute { protected UntypedDataSourceGeneratorAttribute() { } @@ -1677,21 +1637,6 @@ namespace .Data } namespace .DataSources { - public static class DataSourceProcessor - { - [.("This method may create types at runtime")] - [.("This method uses reflection to process data sources")] - public static .<.> ProcessGeneratorItemAsync(object? item) { } - [.("This method uses reflection for tuple processing")] - public static . ProcessMethodDataSourceResult(object? result) { } - public static .<.> ProcessTypedArrayGeneratorItemAsync(<.> taskFunc) { } - public static .<.> ProcessTypedGeneratorItemAsync(<.> taskFunc) { } - public static object? ResolveTypedValue( func) { } - public static . ResolveTypedValueAsync(<.> taskFunc) { } - [.("This method may invoke methods dynamically")] - [.("This method uses reflection to resolve values")] - public static . ResolveValueAsync(object? value) { } - } public static class TestDataFormatter { public static string CreateDisplayName(.TestMetadata metadata, object?[] arguments, .TestDataCombination? dataCombination = null) { } @@ -1918,8 +1863,6 @@ namespace .Extensions } public static class TestContextExtensions { - [.("Adding dynamic tests requires reflection which is not supported in native AOT sce" + - "narios.")] [.("Dynamic test metadata creation uses reflection")] public static . AddDynamicTest<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicFields | ..NonPublicFields | ..PublicProperties)] T>(this .TestContext context, .DynamicTest dynamicTest) where T : class { } @@ -1936,16 +1879,12 @@ namespace .Helpers public static string FormatArguments(. arguments) { } public static string GetConstantValue(.TestContext testContext, object? o) { } } - [.("AOT", "IL3050:Calling members annotated with \'RequiresDynamicCodeAttribute\' may break fu" + - "nctionality when AOT compiling.")] - [.("Trimming", "IL2072:Target parameter argument does not satisfy \'DynamicallyAccessedMembersAttr" + - "ibute\' in call to target method. The return value of the source method does not " + - "have matching annotations.")] public static class CastHelper { - public static object? Cast([.(..None | ..PublicMethods | ..NonPublicMethods)] type, object? value) { } - public static T? Cast<[.(..None | ..PublicMethods | ..NonPublicMethods)] T>(object? value) { } - public static .MethodInfo? GetConversionMethod([.(..None | ..PublicMethods | ..NonPublicMethods)] baseType, [.(..None | ..PublicMethods | ..NonPublicMethods)] targetType) { } + [.("AOT", "IL3050:Calling members annotated with \'RequiresDynamicCodeAttribute\' may break fu" + + "nctionality when AOT compiling.")] + public static object? Cast([.(..PublicParameterlessConstructor)] type, object? value) { } + public static T? Cast<[.(..PublicParameterlessConstructor)] T>(object? value) { } } public static class ClassConstructorHelper { @@ -2002,8 +1941,12 @@ namespace .Helpers public static . ProcessEnumerableDataSource(. enumerable) { } public static <.>[] ProcessTestDataSource(T data, int expectedParameterCount = -1) { } public static void RegisterTypeCreator(<.MethodMetadata, string, .> creator) { } - [.("Data source resolution may require dynamic code generation")] - [.("Property types are resolved through reflection")] + [.("AOT", "IL3050:Calling members annotated with \'RequiresDynamicCodeAttribute\' may break fu" + + "nctionality when AOT compiling", Justification="This method is only used in reflection mode. In AOT/source-gen mode, property inj" + + "ection uses compile-time generated code.")] + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access", Justification="This method is only used in reflection mode. In AOT/source-gen mode, property inj" + + "ection uses compile-time generated code.")] public static . ResolveDataSourceForPropertyAsync([.(..None | ..PublicParameterlessConstructor | ..PublicFields | ..NonPublicFields | ..PublicProperties)] containingType, string propertyName, .MethodMetadata testInformation, string testSessionId) { } public static object?[] ToObjectArray(this object? item) { } public static object?[] ToObjectArrayWithTypes(this object? item, []? expectedTypes) { } @@ -2029,6 +1972,7 @@ namespace .Helpers public static GetGenericTypeDefinition( type) { } public static bool IsConstructedGenericType( type) { } [.("MakeGenericType requires runtime code generation")] + [.("MakeGenericType requires runtime code generation")] public static MakeGenericTypeSafe( genericTypeDefinition, params [] typeArguments) { } } public class ProcessorCountParallelLimit : . @@ -2344,7 +2288,6 @@ namespace .Interfaces { [.("Adding dynamic tests requires runtime compilation and reflection which are not su" + "pported in native AOT scenarios.")] - [.("Dynamic test metadata creation uses reflection")] . AddDynamicTest<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicFields | ..NonPublicFields | ..PublicProperties)] T>(.TestContext context, .DynamicTest dynamicTest) where T : class; } @@ -2385,7 +2328,7 @@ namespace . public required ContainingType { get; init; } public required <.IDataSourceAttribute> CreateDataSource { get; init; } public required string PropertyName { get; init; } - [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] + [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] public required PropertyType { get; init; } public required SetProperty { get; init; } } @@ -2477,7 +2420,6 @@ namespace .Models namespace .Services { [.("Generic type resolution requires runtime type generation")] - [.("Generic type resolution may access types not preserved by trimming")] public class GenericTypeResolver : . { public GenericTypeResolver() { } diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt index 73c7693290..9582362ed0 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt @@ -118,19 +118,14 @@ namespace public static . Convert( action) { } public static . Convert(<.> action) { } public static . Convert(<.> action) { } - [.("ConvertObject may require dynamic invocation for custom awaitable types. For AOT " + - "compatibility, use Task or ValueTask directly.")] - [.("ConvertObject uses reflection to handle custom awaitable types and F# async. For " + - "AOT compatibility, use Task or ValueTask directly.")] public static . ConvertObject(object? invoke) { } - [.("Custom awaitable handling may require dynamic invocation. For AOT, use Task/Value" + - "Task.")] - [.("GetAwaiter pattern detection requires reflection for custom awaitable types. For " + - "AOT, use Task/ValueTask.")] + [.("Trimming", "IL2075:\'this\' argument does not satisfy \'DynamicallyAccessedMembersAttribute\' in " + + "call to target method. The return value of the source method does not have match" + + "ing annotations.")] public static bool TryGetAwaitableTask(object awaitable, [.(true)] out .? task) { } } [(.Class | .Method | .Property, AllowMultiple=true)] - public abstract class AsyncDataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T> : .TypedDataSourceAttribute + public abstract class AsyncDataSourceGeneratorAttribute<[.(..PublicConstructors)] T> : .TypedDataSourceAttribute { protected AsyncDataSourceGeneratorAttribute() { } protected abstract .<<.>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata); @@ -138,7 +133,7 @@ namespace public override sealed .<<.>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class AsyncDataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2> : .TypedDataSourceAttribute<> + public abstract class AsyncDataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2> : .TypedDataSourceAttribute<> { protected AsyncDataSourceGeneratorAttribute() { } protected abstract .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata); @@ -146,7 +141,7 @@ namespace public override sealed .<<.<>>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class AsyncDataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3> : .TypedDataSourceAttribute<> + public abstract class AsyncDataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3> : .TypedDataSourceAttribute<> { protected AsyncDataSourceGeneratorAttribute() { } protected abstract .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata); @@ -154,7 +149,7 @@ namespace public override sealed .<<.<>>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class AsyncDataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T4> : .TypedDataSourceAttribute<> + public abstract class AsyncDataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3, [.(..PublicConstructors)] T4> : .TypedDataSourceAttribute<> { protected AsyncDataSourceGeneratorAttribute() { } protected abstract .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata); @@ -162,7 +157,7 @@ namespace public override .<<.<>>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class AsyncDataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T5> : .TypedDataSourceAttribute<> + public abstract class AsyncDataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3, [.(..PublicConstructors)] T4, [.(..PublicConstructors)] T5> : .TypedDataSourceAttribute<> { protected AsyncDataSourceGeneratorAttribute() { } protected abstract .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata); @@ -183,12 +178,6 @@ namespace } } [(.Class | .Method | .Property, AllowMultiple=true)] - [.("AsyncUntypedDataSourceGeneratorAttribute requires dynamic code generation for run" + - "time data source creation. Consider using strongly-typed AsyncDataSourceGenerato" + - "rAttribute overloads for AOT compatibility.")] - [.("AsyncUntypedDataSourceGeneratorAttribute may require unreferenced code for runtim" + - "e data source creation. Consider using strongly-typed AsyncDataSourceGeneratorAt" + - "tribute overloads for AOT compatibility.")] public abstract class AsyncUntypedDataSourceGeneratorAttribute : , .IDataSourceAttribute { protected AsyncUntypedDataSourceGeneratorAttribute() { } @@ -247,56 +236,36 @@ namespace public required string TestSessionId { get; init; } } [(.Class | .Method | .Property, AllowMultiple=true)] - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] public sealed class ClassDataSourceAttribute : .UntypedDataSourceGeneratorAttribute { - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] - public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type) { } - [.("Reflection")] - [.("Reflection")] + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access otherwise can break functionality when trimming application code", Justification="Non-params constructor calls params one with proper annotations.")] + public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type) { } + [.("Uses dynamically accessed types.")] public ClassDataSourceAttribute(params [] types) { } - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] - public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type2) { } - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] - public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type3) { } - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] - public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type4) { } - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] - public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type5) { } + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access otherwise can break functionality when trimming application code", Justification="Non-params constructor calls params one with proper annotations.")] + public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type2) { } + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access otherwise can break functionality when trimming application code", Justification="Non-params constructor calls params one with proper annotations.")] + public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type3) { } + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access otherwise can break functionality when trimming application code", Justification="Non-params constructor calls params one with proper annotations.")] + public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type4) { } + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access otherwise can break functionality when trimming application code", Justification="Non-params constructor calls params one with proper annotations.")] + public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type5) { } public string[] Keys { get; set; } public .SharedType[] Shared { get; set; } - [.("Reflection")] - [.("Reflection")] + [.("Trimming", "IL2062:The parameter of method has a DynamicallyAccessedMembersAttribute, but the" + + " value passed to it can not be statically analyzed.", Justification="Constructor parameter is annotated with DynamicallyAccessedMembers, so _types ele" + + "ments have the required annotations.")] protected override .<> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata) { } public . GetKeys() { } public .<.SharedType> GetSharedTypes() { } } [(.Class | .Method | .Property, AllowMultiple=true)] - public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T> : .DataSourceGeneratorAttribute + public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T> : .DataSourceGeneratorAttribute { public ClassDataSourceAttribute() { } public ClassType { get; } @@ -307,7 +276,7 @@ namespace public .<.SharedType> GetSharedTypes() { } } [(.Class | .Method, AllowMultiple=true)] - public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T2> : .DataSourceGeneratorAttribute + public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T2> : .DataSourceGeneratorAttribute where T1 : new() where T2 : new() { @@ -319,7 +288,7 @@ namespace public .<.SharedType> GetSharedTypes() { } } [(.Class | .Method, AllowMultiple=true)] - public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T3> : .DataSourceGeneratorAttribute + public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T3> : .DataSourceGeneratorAttribute where T1 : new() where T2 : new() where T3 : new() @@ -332,7 +301,7 @@ namespace public .<.SharedType> GetSharedTypes() { } } [(.Class | .Method, AllowMultiple=true)] - public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T4> : .DataSourceGeneratorAttribute + public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T4> : .DataSourceGeneratorAttribute where T1 : new() where T2 : new() where T3 : new() @@ -346,7 +315,7 @@ namespace public .<.SharedType> GetSharedTypes() { } } [(.Class | .Method, AllowMultiple=true)] - public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T5> : .DataSourceGeneratorAttribute + public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T5> : .DataSourceGeneratorAttribute where T1 : new() where T2 : new() where T3 : new() @@ -374,7 +343,7 @@ namespace public override int GetHashCode() { } } [.DebuggerDisplay("{Type}")] - public class ClassMetadata : .MemberMetadata, <.ClassMetadata> + public class ClassMetadata : <.ClassMetadata>, .IMemberMetadata { public ClassMetadata() { } public required .AssemblyMetadata Assembly { get; init; } @@ -382,14 +351,15 @@ namespace public bool HasRequiredProperties { get; init; } public []? ImplementedInterfaces { get; init; } public ? InitializeRequiredProperties { get; init; } + public required string Name { get; init; } public required string? Namespace { get; init; } public ? ParameterlessFactory { get; init; } public required .ParameterMetadata[] Parameters { get; init; } public required .ClassMetadata? Parent { get; init; } public required .PropertyMetadata[] Properties { get; init; } public int TestConstructorIndex { get; init; } - [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] - public override required Type { get; init; } + [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..PublicProperties)] + public required Type { get; init; } public required .TypeInfo TypeInfo { get; init; } public virtual bool Equals(.ClassMetadata? other) { } public override int GetHashCode() { } @@ -454,7 +424,7 @@ namespace { public DataGeneratorMetadata() { } public required object?[]? ClassInstanceArguments { get; init; } - public required .MemberMetadata[] MembersToGenerate { get; init; } + public required .IMemberMetadata[] MembersToGenerate { get; init; } public required .TestBuilderContextAccessor TestBuilderContext { get; init; } public required object? TestClassInstance { get; init; } public required .MethodMetadata? TestInformation { get; init; } @@ -478,7 +448,7 @@ namespace public string DataSourceName { get; } } [(.Class | .Method | .Property, AllowMultiple=true)] - public abstract class DataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T> : .AsyncDataSourceGeneratorAttribute + public abstract class DataSourceGeneratorAttribute<[.(..PublicConstructors)] T> : .AsyncDataSourceGeneratorAttribute { protected DataSourceGeneratorAttribute() { } protected abstract .<> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata); @@ -486,7 +456,7 @@ namespace protected override .<<.>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class DataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2> : .AsyncDataSourceGeneratorAttribute + public abstract class DataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2> : .AsyncDataSourceGeneratorAttribute { protected DataSourceGeneratorAttribute() { } protected abstract .<<>> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata); @@ -494,7 +464,7 @@ namespace protected override .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class DataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3> : .AsyncDataSourceGeneratorAttribute + public abstract class DataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3> : .AsyncDataSourceGeneratorAttribute { protected DataSourceGeneratorAttribute() { } protected abstract .<<>> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata); @@ -502,7 +472,7 @@ namespace protected override sealed .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class DataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T4> : .AsyncDataSourceGeneratorAttribute + public abstract class DataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3, [.(..PublicConstructors)] T4> : .AsyncDataSourceGeneratorAttribute { protected DataSourceGeneratorAttribute() { } protected abstract .<<>> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata); @@ -510,7 +480,7 @@ namespace protected override .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class DataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T5> : .AsyncDataSourceGeneratorAttribute + public abstract class DataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3, [.(..PublicConstructors)] T4, [.(..PublicConstructors)] T5> : .AsyncDataSourceGeneratorAttribute { protected DataSourceGeneratorAttribute() { } protected abstract .<<>> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata); @@ -538,12 +508,6 @@ namespace public static readonly .DefaultExecutor Instance; protected override . ExecuteAsync(<.> action) { } } - [.("DependencyInjectionDataSourceAttribute requires dynamic code generation for depen" + - "dency injection container access. This attribute is inherently incompatible with" + - " AOT compilation.")] - [.("DependencyInjectionDataSourceAttribute may require unreferenced code for dependen" + - "cy injection container access. This attribute is inherently incompatible with AO" + - "T compilation.")] public abstract class DependencyInjectionDataSourceAttribute : .UntypedDataSourceGeneratorAttribute { protected DependencyInjectionDataSourceAttribute() { } @@ -881,6 +845,10 @@ namespace { .<.AbstractDynamicTest> CollectDynamicTests(string sessionId); } + public interface IMemberMetadata + { + string Name { get; } + } public interface IRequiresImmediateInitialization { } public interface IScopedAttribute { @@ -920,14 +888,9 @@ namespace public MatrixAttribute(params T?[]? objects) { } } [(.Class | .Method)] - [.("MatrixDataSource may process enum types dynamically")] - [.("MatrixDataSource uses reflection to access parameter attributes and test metadata" + - ". For AOT compatibility, consider using explicit data sources.")] public sealed class MatrixDataSourceAttribute : .UntypedDataSourceGeneratorAttribute, .IAccessesInstanceData { public MatrixDataSourceAttribute() { } - [.("Matrix generation may process enum types dynamically")] - [.("Matrix generation requires reflection")] protected override .<> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] @@ -960,7 +923,7 @@ namespace { protected MemberMetadata() { } public required string Name { get; init; } - [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] + [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..PublicProperties)] public abstract Type { get; init; } public virtual bool Equals(.MemberMetadata? other) { } public override int GetHashCode() { } @@ -989,20 +952,21 @@ namespace public MethodDataSourceAttribute(string methodNameProvidingDataSource) { } } [.DebuggerDisplay("{Type}.{Name}")] - public class MethodMetadata : .MemberMetadata, <.MethodMetadata> + public class MethodMetadata : <.MethodMetadata>, .IMemberMetadata { public MethodMetadata() { } public required .ClassMetadata Class { get; init; } public required int GenericTypeCount { get; init; } + public required string Name { get; init; } public required .ParameterMetadata[] Parameters { get; init; } public ? ReturnType { get; init; } public required .TypeInfo ReturnTypeInfo { get; init; } [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] - public override required Type { get; init; } + public required Type { get; init; } public required .TypeInfo TypeInfo { get; init; } public virtual bool Equals(.MethodMetadata? other) { } public override int GetHashCode() { } - protected override bool PrintMembers(.StringBuilder stringBuilder) { } + protected virtual bool PrintMembers(.StringBuilder stringBuilder) { } } [(.Assembly | .Class | .Method)] public class NotInParallelAttribute : .SingleTUnitAttribute, .IScopedAttribute, ., . @@ -1055,9 +1019,9 @@ namespace public . OnTestRegistered(.TestRegisteredContext context) { } } [.DebuggerDisplay("{Type} {Name}")] - public class ParameterMetadata : .MemberMetadata, <.ParameterMetadata> + public class ParameterMetadata : <.ParameterMetadata>, .IMemberMetadata { - public ParameterMetadata([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] Type) { } + public ParameterMetadata([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] Type) { } public object? CachedDefaultValue { get; init; } public bool? CachedIsOptional { get; init; } public bool? CachedIsParams { get; init; } @@ -1065,14 +1029,15 @@ namespace public bool IsNullable { get; init; } public bool IsOptional { get; } public bool IsParams { get; } + public required string Name { get; init; } public int Position { get; init; } public required .ParameterInfo ReflectionInfo { get; set; } - [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] - public override Type { get; init; } + [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] + public Type { get; init; } public required .TypeInfo TypeInfo { get; init; } } [.DebuggerDisplay("{Type} {Name}")] - public class ParameterMetadata<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] T> : .ParameterMetadata, <.ParameterMetadata> + public class ParameterMetadata<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] T> : .ParameterMetadata, <.ParameterMetadata> { public ParameterMetadata() { } } @@ -1109,7 +1074,7 @@ namespace public PropertyType { get; } } [.DebuggerDisplay("{Type} {Name})")] - public class PropertyMetadata : .MemberMetadata, <.PropertyMetadata> + public class PropertyMetadata : <.PropertyMetadata>, .IMemberMetadata { public PropertyMetadata() { } public required .ClassMetadata ClassMetadata { get; set; } @@ -1120,10 +1085,11 @@ namespace public bool IsNullable { get; init; } public bool IsRequired { get; init; } public required bool IsStatic { get; init; } + public required string Name { get; init; } public required .PropertyInfo ReflectionInfo { get; init; } public ? Setter { get; init; } - [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] - public override required Type { get; init; } + [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] + public required Type { get; init; } } public static class PropertySourceRegistry { @@ -1579,12 +1545,6 @@ namespace public abstract .<<.>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata); } [(.Class | .Method | .Property, AllowMultiple=true)] - [.("UntypedDataSourceGeneratorAttribute requires dynamic code generation for runtime " + - "data source creation. Consider using strongly-typed AsyncDataSourceGeneratorAttr" + - "ibute overloads for AOT compatibility.")] - [.("UntypedDataSourceGeneratorAttribute may require unreferenced code for runtime dat" + - "a source creation. Consider using strongly-typed AsyncDataSourceGeneratorAttribu" + - "te overloads for AOT compatibility.")] public abstract class UntypedDataSourceGeneratorAttribute : .AsyncUntypedDataSourceGeneratorAttribute { protected UntypedDataSourceGeneratorAttribute() { } @@ -1677,21 +1637,6 @@ namespace .Data } namespace .DataSources { - public static class DataSourceProcessor - { - [.("This method may create types at runtime")] - [.("This method uses reflection to process data sources")] - public static .<.> ProcessGeneratorItemAsync(object? item) { } - [.("This method uses reflection for tuple processing")] - public static . ProcessMethodDataSourceResult(object? result) { } - public static .<.> ProcessTypedArrayGeneratorItemAsync(<.> taskFunc) { } - public static .<.> ProcessTypedGeneratorItemAsync(<.> taskFunc) { } - public static object? ResolveTypedValue( func) { } - public static . ResolveTypedValueAsync(<.> taskFunc) { } - [.("This method may invoke methods dynamically")] - [.("This method uses reflection to resolve values")] - public static . ResolveValueAsync(object? value) { } - } public static class TestDataFormatter { public static string CreateDisplayName(.TestMetadata metadata, object?[] arguments, .TestDataCombination? dataCombination = null) { } @@ -1918,8 +1863,6 @@ namespace .Extensions } public static class TestContextExtensions { - [.("Adding dynamic tests requires reflection which is not supported in native AOT sce" + - "narios.")] [.("Dynamic test metadata creation uses reflection")] public static . AddDynamicTest<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicFields | ..NonPublicFields | ..PublicProperties)] T>(this .TestContext context, .DynamicTest dynamicTest) where T : class { } @@ -1936,16 +1879,12 @@ namespace .Helpers public static string FormatArguments(. arguments) { } public static string GetConstantValue(.TestContext testContext, object? o) { } } - [.("AOT", "IL3050:Calling members annotated with \'RequiresDynamicCodeAttribute\' may break fu" + - "nctionality when AOT compiling.")] - [.("Trimming", "IL2072:Target parameter argument does not satisfy \'DynamicallyAccessedMembersAttr" + - "ibute\' in call to target method. The return value of the source method does not " + - "have matching annotations.")] public static class CastHelper { - public static object? Cast([.(..None | ..PublicMethods | ..NonPublicMethods)] type, object? value) { } - public static T? Cast<[.(..None | ..PublicMethods | ..NonPublicMethods)] T>(object? value) { } - public static .MethodInfo? GetConversionMethod([.(..None | ..PublicMethods | ..NonPublicMethods)] baseType, [.(..None | ..PublicMethods | ..NonPublicMethods)] targetType) { } + [.("AOT", "IL3050:Calling members annotated with \'RequiresDynamicCodeAttribute\' may break fu" + + "nctionality when AOT compiling.")] + public static object? Cast([.(..PublicParameterlessConstructor)] type, object? value) { } + public static T? Cast<[.(..PublicParameterlessConstructor)] T>(object? value) { } } public static class ClassConstructorHelper { @@ -2002,8 +1941,12 @@ namespace .Helpers public static . ProcessEnumerableDataSource(. enumerable) { } public static <.>[] ProcessTestDataSource(T data, int expectedParameterCount = -1) { } public static void RegisterTypeCreator(<.MethodMetadata, string, .> creator) { } - [.("Data source resolution may require dynamic code generation")] - [.("Property types are resolved through reflection")] + [.("AOT", "IL3050:Calling members annotated with \'RequiresDynamicCodeAttribute\' may break fu" + + "nctionality when AOT compiling", Justification="This method is only used in reflection mode. In AOT/source-gen mode, property inj" + + "ection uses compile-time generated code.")] + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access", Justification="This method is only used in reflection mode. In AOT/source-gen mode, property inj" + + "ection uses compile-time generated code.")] public static . ResolveDataSourceForPropertyAsync([.(..None | ..PublicParameterlessConstructor | ..PublicFields | ..NonPublicFields | ..PublicProperties)] containingType, string propertyName, .MethodMetadata testInformation, string testSessionId) { } public static object?[] ToObjectArray(this object? item) { } public static object?[] ToObjectArrayWithTypes(this object? item, []? expectedTypes) { } @@ -2029,6 +1972,7 @@ namespace .Helpers public static GetGenericTypeDefinition( type) { } public static bool IsConstructedGenericType( type) { } [.("MakeGenericType requires runtime code generation")] + [.("MakeGenericType requires runtime code generation")] public static MakeGenericTypeSafe( genericTypeDefinition, params [] typeArguments) { } } public class ProcessorCountParallelLimit : . @@ -2344,7 +2288,6 @@ namespace .Interfaces { [.("Adding dynamic tests requires runtime compilation and reflection which are not su" + "pported in native AOT scenarios.")] - [.("Dynamic test metadata creation uses reflection")] . AddDynamicTest<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicFields | ..NonPublicFields | ..PublicProperties)] T>(.TestContext context, .DynamicTest dynamicTest) where T : class; } @@ -2385,7 +2328,7 @@ namespace . public required ContainingType { get; init; } public required <.IDataSourceAttribute> CreateDataSource { get; init; } public required string PropertyName { get; init; } - [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] + [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] public required PropertyType { get; init; } public required SetProperty { get; init; } } @@ -2477,7 +2420,6 @@ namespace .Models namespace .Services { [.("Generic type resolution requires runtime type generation")] - [.("Generic type resolution may access types not preserved by trimming")] public class GenericTypeResolver : . { public GenericTypeResolver() { } diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt index c2d3b83ce6..fb2c06e332 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt @@ -118,19 +118,14 @@ namespace public static . Convert( action) { } public static . Convert(<.> action) { } public static . Convert(<.> action) { } - [.("ConvertObject may require dynamic invocation for custom awaitable types. For AOT " + - "compatibility, use Task or ValueTask directly.")] - [.("ConvertObject uses reflection to handle custom awaitable types and F# async. For " + - "AOT compatibility, use Task or ValueTask directly.")] public static . ConvertObject(object? invoke) { } - [.("Custom awaitable handling may require dynamic invocation. For AOT, use Task/Value" + - "Task.")] - [.("GetAwaiter pattern detection requires reflection for custom awaitable types. For " + - "AOT, use Task/ValueTask.")] + [.("Trimming", "IL2075:\'this\' argument does not satisfy \'DynamicallyAccessedMembersAttribute\' in " + + "call to target method. The return value of the source method does not have match" + + "ing annotations.")] public static bool TryGetAwaitableTask(object awaitable, [.(true)] out .? task) { } } [(.Class | .Method | .Property, AllowMultiple=true)] - public abstract class AsyncDataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T> : .TypedDataSourceAttribute + public abstract class AsyncDataSourceGeneratorAttribute<[.(..PublicConstructors)] T> : .TypedDataSourceAttribute { protected AsyncDataSourceGeneratorAttribute() { } protected abstract .<<.>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata); @@ -138,7 +133,7 @@ namespace public override sealed .<<.>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class AsyncDataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2> : .TypedDataSourceAttribute<> + public abstract class AsyncDataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2> : .TypedDataSourceAttribute<> { protected AsyncDataSourceGeneratorAttribute() { } protected abstract .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata); @@ -146,7 +141,7 @@ namespace public override sealed .<<.<>>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class AsyncDataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3> : .TypedDataSourceAttribute<> + public abstract class AsyncDataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3> : .TypedDataSourceAttribute<> { protected AsyncDataSourceGeneratorAttribute() { } protected abstract .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata); @@ -154,7 +149,7 @@ namespace public override sealed .<<.<>>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class AsyncDataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T4> : .TypedDataSourceAttribute<> + public abstract class AsyncDataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3, [.(..PublicConstructors)] T4> : .TypedDataSourceAttribute<> { protected AsyncDataSourceGeneratorAttribute() { } protected abstract .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata); @@ -162,7 +157,7 @@ namespace public override .<<.<>>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class AsyncDataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T5> : .TypedDataSourceAttribute<> + public abstract class AsyncDataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3, [.(..PublicConstructors)] T4, [.(..PublicConstructors)] T5> : .TypedDataSourceAttribute<> { protected AsyncDataSourceGeneratorAttribute() { } protected abstract .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata); @@ -183,12 +178,6 @@ namespace } } [(.Class | .Method | .Property, AllowMultiple=true)] - [.("AsyncUntypedDataSourceGeneratorAttribute requires dynamic code generation for run" + - "time data source creation. Consider using strongly-typed AsyncDataSourceGenerato" + - "rAttribute overloads for AOT compatibility.")] - [.("AsyncUntypedDataSourceGeneratorAttribute may require unreferenced code for runtim" + - "e data source creation. Consider using strongly-typed AsyncDataSourceGeneratorAt" + - "tribute overloads for AOT compatibility.")] public abstract class AsyncUntypedDataSourceGeneratorAttribute : , .IDataSourceAttribute { protected AsyncUntypedDataSourceGeneratorAttribute() { } @@ -247,56 +236,36 @@ namespace public required string TestSessionId { get; init; } } [(.Class | .Method | .Property, AllowMultiple=true)] - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] public sealed class ClassDataSourceAttribute : .UntypedDataSourceGeneratorAttribute { - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] - public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type) { } - [.("Reflection")] - [.("Reflection")] + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access otherwise can break functionality when trimming application code", Justification="Non-params constructor calls params one with proper annotations.")] + public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type) { } + [.("Uses dynamically accessed types.")] public ClassDataSourceAttribute(params [] types) { } - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] - public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type2) { } - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] - public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type3) { } - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] - public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type4) { } - [.("ClassDataSourceAttribute may require runtime type generation. For AOT compatibili" + - "ty, use strongly-typed ClassDataSourceAttribute instead.")] - [.("ClassDataSourceAttribute uses reflection to instantiate and access test data clas" + - "ses. For AOT compatibility, use strongly-typed ClassDataSourceAttribute inste" + - "ad.")] - public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] type5) { } + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access otherwise can break functionality when trimming application code", Justification="Non-params constructor calls params one with proper annotations.")] + public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type2) { } + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access otherwise can break functionality when trimming application code", Justification="Non-params constructor calls params one with proper annotations.")] + public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type3) { } + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access otherwise can break functionality when trimming application code", Justification="Non-params constructor calls params one with proper annotations.")] + public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type4) { } + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access otherwise can break functionality when trimming application code", Justification="Non-params constructor calls params one with proper annotations.")] + public ClassDataSourceAttribute([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] type5) { } public string[] Keys { get; set; } public .SharedType[] Shared { get; set; } - [.("Reflection")] - [.("Reflection")] + [.("Trimming", "IL2062:The parameter of method has a DynamicallyAccessedMembersAttribute, but the" + + " value passed to it can not be statically analyzed.", Justification="Constructor parameter is annotated with DynamicallyAccessedMembers, so _types ele" + + "ments have the required annotations.")] protected override .<> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata) { } public . GetKeys() { } public .<.SharedType> GetSharedTypes() { } } [(.Class | .Method | .Property, AllowMultiple=true)] - public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T> : .DataSourceGeneratorAttribute + public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T> : .DataSourceGeneratorAttribute { public ClassDataSourceAttribute() { } public ClassType { get; } @@ -307,7 +276,7 @@ namespace public .<.SharedType> GetSharedTypes() { } } [(.Class | .Method, AllowMultiple=true)] - public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T2> : .DataSourceGeneratorAttribute + public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T2> : .DataSourceGeneratorAttribute where T1 : new() where T2 : new() { @@ -319,7 +288,7 @@ namespace public .<.SharedType> GetSharedTypes() { } } [(.Class | .Method, AllowMultiple=true)] - public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T3> : .DataSourceGeneratorAttribute + public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T3> : .DataSourceGeneratorAttribute where T1 : new() where T2 : new() where T3 : new() @@ -332,7 +301,7 @@ namespace public .<.SharedType> GetSharedTypes() { } } [(.Class | .Method, AllowMultiple=true)] - public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T4> : .DataSourceGeneratorAttribute + public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T4> : .DataSourceGeneratorAttribute where T1 : new() where T2 : new() where T3 : new() @@ -346,7 +315,7 @@ namespace public .<.SharedType> GetSharedTypes() { } } [(.Class | .Method, AllowMultiple=true)] - public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties | ..NonPublicProperties)] T5> : .DataSourceGeneratorAttribute + public sealed class ClassDataSourceAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties | ..NonPublicProperties)] T5> : .DataSourceGeneratorAttribute where T1 : new() where T2 : new() where T3 : new() @@ -374,7 +343,7 @@ namespace public override int GetHashCode() { } } [.DebuggerDisplay("{Type}")] - public class ClassMetadata : .MemberMetadata, <.ClassMetadata> + public class ClassMetadata : <.ClassMetadata>, .IMemberMetadata { public ClassMetadata() { } public required .AssemblyMetadata Assembly { get; init; } @@ -382,14 +351,15 @@ namespace public bool HasRequiredProperties { get; init; } public []? ImplementedInterfaces { get; init; } public ? InitializeRequiredProperties { get; init; } + public required string Name { get; init; } public required string? Namespace { get; init; } public ? ParameterlessFactory { get; init; } public required .ParameterMetadata[] Parameters { get; init; } public required .ClassMetadata? Parent { get; init; } public required .PropertyMetadata[] Properties { get; init; } public int TestConstructorIndex { get; init; } - [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] - public override required Type { get; init; } + [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..PublicProperties)] + public required Type { get; init; } public required .TypeInfo TypeInfo { get; init; } public virtual bool Equals(.ClassMetadata? other) { } public override int GetHashCode() { } @@ -454,7 +424,7 @@ namespace { public DataGeneratorMetadata() { } public required object?[]? ClassInstanceArguments { get; init; } - public required .MemberMetadata[] MembersToGenerate { get; init; } + public required .IMemberMetadata[] MembersToGenerate { get; init; } public required .TestBuilderContextAccessor TestBuilderContext { get; init; } public required object? TestClassInstance { get; init; } public required .MethodMetadata? TestInformation { get; init; } @@ -478,7 +448,7 @@ namespace public string DataSourceName { get; } } [(.Class | .Method | .Property, AllowMultiple=true)] - public abstract class DataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T> : .AsyncDataSourceGeneratorAttribute + public abstract class DataSourceGeneratorAttribute<[.(..PublicConstructors)] T> : .AsyncDataSourceGeneratorAttribute { protected DataSourceGeneratorAttribute() { } protected abstract .<> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata); @@ -486,7 +456,7 @@ namespace protected override .<<.>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class DataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2> : .AsyncDataSourceGeneratorAttribute + public abstract class DataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2> : .AsyncDataSourceGeneratorAttribute { protected DataSourceGeneratorAttribute() { } protected abstract .<<>> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata); @@ -494,7 +464,7 @@ namespace protected override .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class DataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3> : .AsyncDataSourceGeneratorAttribute + public abstract class DataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3> : .AsyncDataSourceGeneratorAttribute { protected DataSourceGeneratorAttribute() { } protected abstract .<<>> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata); @@ -502,7 +472,7 @@ namespace protected override sealed .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class DataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T4> : .AsyncDataSourceGeneratorAttribute + public abstract class DataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3, [.(..PublicConstructors)] T4> : .AsyncDataSourceGeneratorAttribute { protected DataSourceGeneratorAttribute() { } protected abstract .<<>> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata); @@ -510,7 +480,7 @@ namespace protected override .<<.<>>> GenerateDataSourcesAsync(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] - public abstract class DataSourceGeneratorAttribute<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T1, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T2, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T3, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T4, [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..PublicMethods)] T5> : .AsyncDataSourceGeneratorAttribute + public abstract class DataSourceGeneratorAttribute<[.(..PublicConstructors)] T1, [.(..PublicConstructors)] T2, [.(..PublicConstructors)] T3, [.(..PublicConstructors)] T4, [.(..PublicConstructors)] T5> : .AsyncDataSourceGeneratorAttribute { protected DataSourceGeneratorAttribute() { } protected abstract .<<>> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata); @@ -538,12 +508,6 @@ namespace public static readonly .DefaultExecutor Instance; protected override . ExecuteAsync(<.> action) { } } - [.("DependencyInjectionDataSourceAttribute requires dynamic code generation for depen" + - "dency injection container access. This attribute is inherently incompatible with" + - " AOT compilation.")] - [.("DependencyInjectionDataSourceAttribute may require unreferenced code for dependen" + - "cy injection container access. This attribute is inherently incompatible with AO" + - "T compilation.")] public abstract class DependencyInjectionDataSourceAttribute : .UntypedDataSourceGeneratorAttribute { protected DependencyInjectionDataSourceAttribute() { } @@ -881,6 +845,10 @@ namespace { .<.AbstractDynamicTest> CollectDynamicTests(string sessionId); } + public interface IMemberMetadata + { + string Name { get; } + } public interface IRequiresImmediateInitialization { } public interface IScopedAttribute { @@ -920,14 +888,9 @@ namespace public MatrixAttribute(params T?[]? objects) { } } [(.Class | .Method)] - [.("MatrixDataSource may process enum types dynamically")] - [.("MatrixDataSource uses reflection to access parameter attributes and test metadata" + - ". For AOT compatibility, consider using explicit data sources.")] public sealed class MatrixDataSourceAttribute : .UntypedDataSourceGeneratorAttribute, .IAccessesInstanceData { public MatrixDataSourceAttribute() { } - [.("Matrix generation may process enum types dynamically")] - [.("Matrix generation requires reflection")] protected override .<> GenerateDataSources(.DataGeneratorMetadata dataGeneratorMetadata) { } } [(.Class | .Method, AllowMultiple=true)] @@ -960,7 +923,7 @@ namespace { protected MemberMetadata() { } public required string Name { get; init; } - [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] + [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..PublicProperties)] public abstract Type { get; init; } public virtual bool Equals(.MemberMetadata? other) { } public override int GetHashCode() { } @@ -989,20 +952,21 @@ namespace public MethodDataSourceAttribute(string methodNameProvidingDataSource) { } } [.DebuggerDisplay("{Type}.{Name}")] - public class MethodMetadata : .MemberMetadata, <.MethodMetadata> + public class MethodMetadata : <.MethodMetadata>, .IMemberMetadata { public MethodMetadata() { } public required .ClassMetadata Class { get; init; } public required int GenericTypeCount { get; init; } + public required string Name { get; init; } public required .ParameterMetadata[] Parameters { get; init; } public ? ReturnType { get; init; } public required .TypeInfo ReturnTypeInfo { get; init; } [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] - public override required Type { get; init; } + public required Type { get; init; } public required .TypeInfo TypeInfo { get; init; } public virtual bool Equals(.MethodMetadata? other) { } public override int GetHashCode() { } - protected override bool PrintMembers(.StringBuilder stringBuilder) { } + protected virtual bool PrintMembers(.StringBuilder stringBuilder) { } } [(.Assembly | .Class | .Method)] public class NotInParallelAttribute : .SingleTUnitAttribute, .IScopedAttribute, ., . @@ -1055,9 +1019,9 @@ namespace public . OnTestRegistered(.TestRegisteredContext context) { } } [.DebuggerDisplay("{Type} {Name}")] - public class ParameterMetadata : .MemberMetadata, <.ParameterMetadata> + public class ParameterMetadata : <.ParameterMetadata>, .IMemberMetadata { - public ParameterMetadata([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] Type) { } + public ParameterMetadata([.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] Type) { } public object? CachedDefaultValue { get; init; } public bool? CachedIsOptional { get; init; } public bool? CachedIsParams { get; init; } @@ -1065,14 +1029,15 @@ namespace public bool IsNullable { get; init; } public bool IsOptional { get; } public bool IsParams { get; } + public required string Name { get; init; } public int Position { get; init; } public required .ParameterInfo ReflectionInfo { get; set; } - [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] - public override Type { get; init; } + [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] + public Type { get; init; } public required .TypeInfo TypeInfo { get; init; } } [.DebuggerDisplay("{Type} {Name}")] - public class ParameterMetadata<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] T> : .ParameterMetadata, <.ParameterMetadata> + public class ParameterMetadata<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] T> : .ParameterMetadata, <.ParameterMetadata> { public ParameterMetadata() { } } @@ -1109,7 +1074,7 @@ namespace public PropertyType { get; } } [.DebuggerDisplay("{Type} {Name})")] - public class PropertyMetadata : .MemberMetadata, <.PropertyMetadata> + public class PropertyMetadata : <.PropertyMetadata>, .IMemberMetadata { public PropertyMetadata() { } public required .ClassMetadata ClassMetadata { get; set; } @@ -1120,10 +1085,11 @@ namespace public bool IsNullable { get; init; } public bool IsRequired { get; init; } public required bool IsStatic { get; init; } + public required string Name { get; init; } public required .PropertyInfo ReflectionInfo { get; init; } public ? Setter { get; init; } - [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] - public override required Type { get; init; } + [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] + public required Type { get; init; } } public static class PropertySourceRegistry { @@ -1579,12 +1545,6 @@ namespace public abstract .<<.>> GetTypedDataRowsAsync(.DataGeneratorMetadata dataGeneratorMetadata); } [(.Class | .Method | .Property, AllowMultiple=true)] - [.("UntypedDataSourceGeneratorAttribute requires dynamic code generation for runtime " + - "data source creation. Consider using strongly-typed AsyncDataSourceGeneratorAttr" + - "ibute overloads for AOT compatibility.")] - [.("UntypedDataSourceGeneratorAttribute may require unreferenced code for runtime dat" + - "a source creation. Consider using strongly-typed AsyncDataSourceGeneratorAttribu" + - "te overloads for AOT compatibility.")] public abstract class UntypedDataSourceGeneratorAttribute : .AsyncUntypedDataSourceGeneratorAttribute { protected UntypedDataSourceGeneratorAttribute() { } @@ -1677,21 +1637,6 @@ namespace .Data } namespace .DataSources { - public static class DataSourceProcessor - { - [.("This method may create types at runtime")] - [.("This method uses reflection to process data sources")] - public static .<.> ProcessGeneratorItemAsync(object? item) { } - [.("This method uses reflection for tuple processing")] - public static . ProcessMethodDataSourceResult(object? result) { } - public static .<.> ProcessTypedArrayGeneratorItemAsync(<.> taskFunc) { } - public static .<.> ProcessTypedGeneratorItemAsync(<.> taskFunc) { } - public static object? ResolveTypedValue( func) { } - public static . ResolveTypedValueAsync(<.> taskFunc) { } - [.("This method may invoke methods dynamically")] - [.("This method uses reflection to resolve values")] - public static . ResolveValueAsync(object? value) { } - } public static class TestDataFormatter { public static string CreateDisplayName(.TestMetadata metadata, object?[] arguments, .TestDataCombination? dataCombination = null) { } @@ -1918,8 +1863,6 @@ namespace .Extensions } public static class TestContextExtensions { - [.("Adding dynamic tests requires reflection which is not supported in native AOT sce" + - "narios.")] [.("Dynamic test metadata creation uses reflection")] public static . AddDynamicTest<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicFields | ..NonPublicFields | ..PublicProperties)] T>(this .TestContext context, .DynamicTest dynamicTest) where T : class { } @@ -1936,16 +1879,12 @@ namespace .Helpers public static string FormatArguments(. arguments) { } public static string GetConstantValue(.TestContext testContext, object? o) { } } - [.("AOT", "IL3050:Calling members annotated with \'RequiresDynamicCodeAttribute\' may break fu" + - "nctionality when AOT compiling.")] - [.("Trimming", "IL2072:Target parameter argument does not satisfy \'DynamicallyAccessedMembersAttr" + - "ibute\' in call to target method. The return value of the source method does not " + - "have matching annotations.")] public static class CastHelper { - public static object? Cast([.(..None | ..PublicMethods | ..NonPublicMethods)] type, object? value) { } - public static T? Cast<[.(..None | ..PublicMethods | ..NonPublicMethods)] T>(object? value) { } - public static .MethodInfo? GetConversionMethod([.(..None | ..PublicMethods | ..NonPublicMethods)] baseType, [.(..None | ..PublicMethods | ..NonPublicMethods)] targetType) { } + [.("AOT", "IL3050:Calling members annotated with \'RequiresDynamicCodeAttribute\' may break fu" + + "nctionality when AOT compiling.")] + public static object? Cast([.(..PublicParameterlessConstructor)] type, object? value) { } + public static T? Cast<[.(..PublicParameterlessConstructor)] T>(object? value) { } } public static class ClassConstructorHelper { @@ -2002,8 +1941,12 @@ namespace .Helpers public static . ProcessEnumerableDataSource(. enumerable) { } public static <.>[] ProcessTestDataSource(T data, int expectedParameterCount = -1) { } public static void RegisterTypeCreator(<.MethodMetadata, string, .> creator) { } - [.("Data source resolution may require dynamic code generation")] - [.("Property types are resolved through reflection")] + [.("AOT", "IL3050:Calling members annotated with \'RequiresDynamicCodeAttribute\' may break fu" + + "nctionality when AOT compiling", Justification="This method is only used in reflection mode. In AOT/source-gen mode, property inj" + + "ection uses compile-time generated code.")] + [.("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic" + + " access", Justification="This method is only used in reflection mode. In AOT/source-gen mode, property inj" + + "ection uses compile-time generated code.")] public static . ResolveDataSourceForPropertyAsync([.(..None | ..PublicParameterlessConstructor | ..PublicFields | ..NonPublicFields | ..PublicProperties)] containingType, string propertyName, .MethodMetadata testInformation, string testSessionId) { } public static object?[] ToObjectArray(this object? item) { } public static object?[] ToObjectArrayWithTypes(this object? item, []? expectedTypes) { } @@ -2029,6 +1972,7 @@ namespace .Helpers public static GetGenericTypeDefinition( type) { } public static bool IsConstructedGenericType( type) { } [.("MakeGenericType requires runtime code generation")] + [.("MakeGenericType requires runtime code generation")] public static MakeGenericTypeSafe( genericTypeDefinition, params [] typeArguments) { } } public class ProcessorCountParallelLimit : . @@ -2344,7 +2288,6 @@ namespace .Interfaces { [.("Adding dynamic tests requires runtime compilation and reflection which are not su" + "pported in native AOT scenarios.")] - [.("Dynamic test metadata creation uses reflection")] . AddDynamicTest<[.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicFields | ..NonPublicFields | ..PublicProperties)] T>(.TestContext context, .DynamicTest dynamicTest) where T : class; } @@ -2385,7 +2328,7 @@ namespace . public required ContainingType { get; init; } public required <.IDataSourceAttribute> CreateDataSource { get; init; } public required string PropertyName { get; init; } - [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicMethods | ..NonPublicMethods | ..PublicProperties)] + [.(..None | ..PublicParameterlessConstructor | ..PublicConstructors | ..NonPublicConstructors | ..PublicProperties)] public required PropertyType { get; init; } public required SetProperty { get; init; } } @@ -2477,7 +2420,6 @@ namespace .Models namespace .Services { [.("Generic type resolution requires runtime type generation")] - [.("Generic type resolution may access types not preserved by trimming")] public class GenericTypeResolver : . { public GenericTypeResolver() { } diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt index f6ca8632a8..0f7cf90f4c 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -324,7 +324,7 @@ namespace public override int GetHashCode() { } } [.DebuggerDisplay("{Type}")] - public class ClassMetadata : .MemberMetadata, <.ClassMetadata> + public class ClassMetadata : <.ClassMetadata>, .IMemberMetadata { public ClassMetadata() { } public required .AssemblyMetadata Assembly { get; init; } @@ -332,13 +332,14 @@ namespace public bool HasRequiredProperties { get; init; } public []? ImplementedInterfaces { get; init; } public ? InitializeRequiredProperties { get; init; } + public required string Name { get; init; } public required string? Namespace { get; init; } public ? ParameterlessFactory { get; init; } public required .ParameterMetadata[] Parameters { get; init; } public required .ClassMetadata? Parent { get; init; } public required .PropertyMetadata[] Properties { get; init; } public int TestConstructorIndex { get; init; } - public override required Type { get; init; } + public required Type { get; init; } public required .TypeInfo TypeInfo { get; init; } public virtual bool Equals(.ClassMetadata? other) { } public override int GetHashCode() { } @@ -403,7 +404,7 @@ namespace { public DataGeneratorMetadata() { } public required object?[]? ClassInstanceArguments { get; init; } - public required .MemberMetadata[] MembersToGenerate { get; init; } + public required .IMemberMetadata[] MembersToGenerate { get; init; } public required .TestBuilderContextAccessor TestBuilderContext { get; init; } public required object? TestClassInstance { get; init; } public required .MethodMetadata? TestInformation { get; init; } @@ -820,6 +821,10 @@ namespace { .<.AbstractDynamicTest> CollectDynamicTests(string sessionId); } + public interface IMemberMetadata + { + string Name { get; } + } public interface IRequiresImmediateInitialization { } public interface IScopedAttribute { @@ -910,19 +915,20 @@ namespace public MethodDataSourceAttribute(string methodNameProvidingDataSource) { } } [.DebuggerDisplay("{Type}.{Name}")] - public class MethodMetadata : .MemberMetadata, <.MethodMetadata> + public class MethodMetadata : <.MethodMetadata>, .IMemberMetadata { public MethodMetadata() { } public required .ClassMetadata Class { get; init; } public required int GenericTypeCount { get; init; } + public required string Name { get; init; } public required .ParameterMetadata[] Parameters { get; init; } public ? ReturnType { get; init; } public required .TypeInfo ReturnTypeInfo { get; init; } - public override required Type { get; init; } + public required Type { get; init; } public required .TypeInfo TypeInfo { get; init; } public virtual bool Equals(.MethodMetadata? other) { } public override int GetHashCode() { } - protected override bool PrintMembers(.StringBuilder stringBuilder) { } + protected virtual bool PrintMembers(.StringBuilder stringBuilder) { } } [(.Assembly | .Class | .Method)] public class NotInParallelAttribute : .SingleTUnitAttribute, .IScopedAttribute, ., . @@ -975,7 +981,7 @@ namespace public . OnTestRegistered(.TestRegisteredContext context) { } } [.DebuggerDisplay("{Type} {Name}")] - public class ParameterMetadata : .MemberMetadata, <.ParameterMetadata> + public class ParameterMetadata : <.ParameterMetadata>, .IMemberMetadata { public ParameterMetadata( Type) { } public object? CachedDefaultValue { get; init; } @@ -985,9 +991,10 @@ namespace public bool IsNullable { get; init; } public bool IsOptional { get; } public bool IsParams { get; } + public required string Name { get; init; } public int Position { get; init; } public required .ParameterInfo ReflectionInfo { get; set; } - public override Type { get; init; } + public Type { get; init; } public required .TypeInfo TypeInfo { get; init; } } [.DebuggerDisplay("{Type} {Name}")] @@ -1028,7 +1035,7 @@ namespace public PropertyType { get; } } [.DebuggerDisplay("{Type} {Name})")] - public class PropertyMetadata : .MemberMetadata, <.PropertyMetadata> + public class PropertyMetadata : <.PropertyMetadata>, .IMemberMetadata { public PropertyMetadata() { } public required .ClassMetadata ClassMetadata { get; set; } @@ -1039,9 +1046,10 @@ namespace public bool IsNullable { get; init; } public bool IsRequired { get; init; } public required bool IsStatic { get; init; } + public required string Name { get; init; } public required .PropertyInfo ReflectionInfo { get; init; } public ? Setter { get; init; } - public override required Type { get; init; } + public required Type { get; init; } } public static class PropertySourceRegistry { @@ -1581,16 +1589,6 @@ namespace .Data } namespace .DataSources { - public static class DataSourceProcessor - { - public static .<.> ProcessGeneratorItemAsync(object? item) { } - public static . ProcessMethodDataSourceResult(object? result) { } - public static .<.> ProcessTypedArrayGeneratorItemAsync(<.> taskFunc) { } - public static .<.> ProcessTypedGeneratorItemAsync(<.> taskFunc) { } - public static object? ResolveTypedValue( func) { } - public static . ResolveTypedValueAsync(<.> taskFunc) { } - public static . ResolveValueAsync(object? value) { } - } public static class TestDataFormatter { public static string CreateDisplayName(.TestMetadata metadata, object?[] arguments, .TestDataCombination? dataCombination = null) { } @@ -1835,7 +1833,6 @@ namespace .Helpers { public static object? Cast( type, object? value) { } public static T? Cast(object? value) { } - public static .MethodInfo? GetConversionMethod( baseType, targetType) { } } public static class ClassConstructorHelper { diff --git a/TUnit.TestProject/Attributes/AutoDataAttribute.cs b/TUnit.TestProject/Attributes/AutoDataAttribute.cs index 8d2c22d0be..067622825e 100644 --- a/TUnit.TestProject/Attributes/AutoDataAttribute.cs +++ b/TUnit.TestProject/Attributes/AutoDataAttribute.cs @@ -20,7 +20,15 @@ private static IEnumerable GenerateRowEnumerable(DataGeneratorMetadata d { foreach (var member in dataGeneratorMetadata.MembersToGenerate) { - yield return Fixture.Create(member.Type, new SpecimenContext(Fixture)); + var type = member switch + { + PropertyMetadata prop => prop.Type, + ParameterMetadata param => param.Type, + ClassMetadata cls => cls.Type, + MethodMetadata method => method.Type, + _ => throw new InvalidOperationException($"Unknown member type: {member.GetType()}") + }; + yield return Fixture.Create(type, new SpecimenContext(Fixture)); } } } diff --git a/TUnit.TestProject/Bugs/1914/SyncHookTests.cs b/TUnit.TestProject/Bugs/1914/SyncHookTests.cs index 7659b728c8..56bc144977 100644 --- a/TUnit.TestProject/Bugs/1914/SyncHookTests.cs +++ b/TUnit.TestProject/Bugs/1914/SyncHookTests.cs @@ -7,7 +7,7 @@ namespace TUnit.TestProject.Bugs._1914; [SkipNetFramework("ExecutionContext.Restore is not supported on .NET Framework")] [UnconditionalSuppressMessage("Usage", "TUnit0042:Global hooks should not be mixed with test classes to avoid confusion. Place them in their own class.")] [UnconditionalSuppressMessage("Usage", "TUnit0047:Call `context.AddAsyncLocalValues`")] -[SuppressMessage("Usage", "TUnitAssertions0002:Assert statements must be awaited")] +[UnconditionalSuppressMessage("Usage", "TUnitAssertions0002:Assert statements must be awaited")] public class SyncHookTests { private static readonly AsyncLocal _0BeforeTestDiscoveryLocal = new(); diff --git a/TUnit.TestProject/Bugs/2075/Tests.cs b/TUnit.TestProject/Bugs/2075/Tests.cs index 30d39d1243..bc60978323 100644 --- a/TUnit.TestProject/Bugs/2075/Tests.cs +++ b/TUnit.TestProject/Bugs/2075/Tests.cs @@ -43,7 +43,18 @@ public Task InitializeAsync() yield return () => { - return dataGeneratorMetadata.MembersToGenerate.Select(x => ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider, x.Type)).ToArray(); + return dataGeneratorMetadata.MembersToGenerate.Select(x => + { + var type = x switch + { + PropertyMetadata prop => prop.Type, + ParameterMetadata param => param.Type, + ClassMetadata cls => cls.Type, + MethodMetadata method => method.Type, + _ => throw new InvalidOperationException($"Unknown member type: {x.GetType()}") + }; + return ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider, type); + }).ToArray(); }; } } diff --git a/tools/tunit-nuget-tester/TUnit.NugetTester/Directory.Build.props b/tools/tunit-nuget-tester/TUnit.NugetTester/Directory.Build.props new file mode 100644 index 0000000000..94b91fc429 --- /dev/null +++ b/tools/tunit-nuget-tester/TUnit.NugetTester/Directory.Build.props @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tools/tunit-nuget-tester/TUnit.NugetTester/TUnit.NugetTester/AotCompatibilityTests.cs b/tools/tunit-nuget-tester/TUnit.NugetTester/TUnit.NugetTester/AotCompatibilityTests.cs new file mode 100644 index 0000000000..9702c05d5e --- /dev/null +++ b/tools/tunit-nuget-tester/TUnit.NugetTester/TUnit.NugetTester/AotCompatibilityTests.cs @@ -0,0 +1,51 @@ +namespace TUnit.NugetTester; + +/// +/// Tests for issue #3321 - AOT analysis warnings when using enums in Arguments attribute +/// This test verifies that no IL3050 warnings are emitted when PublishAot is enabled +/// +/// Per the issue report, code like this was triggering warnings: +/// - [Arguments(Enum1.Value1)] with enums +/// +/// The warning was: +/// - IL3050: Using Array.CreateInstance which requires dynamic code +/// +/// This has been suppressed in CastHelper.cs with an UnconditionalSuppressMessage +/// since the array creation happens at test discovery time, not during AOT-compiled execution. +/// +/// Note: MatrixDataSource is intentionally NOT AOT-compatible and is marked with +/// RequiresUnreferencedCode/RequiresDynamicCode attributes. +/// +public class AotCompatibilityTests +{ + public enum TestEnum + { + Value1, + Value2, + Value3 + } + + // Test from issue #3321 - enum parameters in Arguments attribute + [Test] + [Arguments(TestEnum.Value1)] + [Arguments(TestEnum.Value2)] + [Arguments(TestEnum.Value3)] + public async Task EnumArguments_ShouldNotTriggerAotWarnings(TestEnum enumValue) + { + // This test verifies that using enum values in Arguments doesn't trigger IL3050 + await Assert.That(enumValue).IsDefined(); + } + + // Test property injection with AOT - verifies no warnings when using property injection in source-gen mode + [Arguments("test value")] + public required string? InjectedProperty { get; set; } + + [Test] + public async Task PropertyInjection_ShouldNotTriggerAotWarnings() + { + // This test verifies that property injection works with AOT via source generation + // The source generator creates PropertyInjectionData at compile time without using reflection + await Assert.That(InjectedProperty).IsNotNull(); + await Assert.That(InjectedProperty).IsEqualTo("test value"); + } +}